SpringBoot框架:两个方法同时调用时父方法使内部方法的DataSource注解失效的解决办法
一、问题如下:
使用的是SpringBoot框架:通过AOP和自定义注解完成druid连接池的动态数据源切换(三)中的两个数据库spring_boot_demo和other_data。
在UserController中同时调用两个方法getAgeOfUser()和getAgeOfUser2(),这里方法里都是使用UserService中的同一方法接收数据。
不同的是在getAgeOfUser2()上使用了DataSource(DataSourceName.SECOND)自定义注解来切换数据源,当两个方法同时被调用时,代码如下:
package com.example.demo.controller; import com.example.demo.annotation.DataSource; import com.example.demo.enums.DataSourceName; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 我命倾尘 */ @RestController public class UserController { @Autowired UserService userService; /** * 从spring_boot_demo数据库中得到数据 * @return int */ public int getAgeOfUser(){ return userService.getAgeByUsername("springbootdemo"); } /** * 从other_data数据库中得到数据 * @return */ @DataSource(DataSourceName.SECOND) public int getAgeOfUser2(){ return userService.getAgeByUsername("springbootdemo"); } /** * 两个方法同时执行 * @return String */ @RequestMapping("/user/age") public String getAge(){ int age1=getAgeOfUser(); int age2=getAgeOfUser2(); return "spring_boot_demo:"+age1+";other_data:"+age2; } }
得到的运行结果如下:
很显然,切换数据源没有成功,加了切换注解的方法得到的显示结果还是主数据源的数据。
二、问题思考:
相比于之前使用单方法切换数据源成功的测试结果,这次的测试和之前的差别无非以下两点:
1、不使用注解(即默认数据源)的方法和使用注解(切换辅数据源)的方法同时被调用; 2、方法被嵌套在一个不使用注解的父方法中调用。
那么针对以上两个情况再进行测试,去掉使用默认数据源的方法,只在父方法中嵌套一个进行调用:
@RequestMapping("/user/age") public String getAge(){ int age2=getAgeOfUser2(); return "other_data:"+age2; }
得到的结果如下所示:
结果得到的还是主数据源的数据,也就是说,切换数据源的注解失效与另一个无注解的方法无关,而是因为被嵌套在一个无注解的父方法上。
在无注解的父方法上加上切换数据源的注解,再次进行测试如下:
@RequestMapping("/user/age") @DataSource(DataSourceName.SECOND) public String getAge(){ int age2=getAgeOfUser2(); return "other_data:"+age2; }
得到的结果是:
到这儿,基本就清楚了。
之所以切换数据源的注解失效,是因为方法嵌套时,子方法的DataSource注解被父方法覆盖了。
三、问题解决
1、使用代理对象:
(1)引入spring-aspects依赖包:
<!-- spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.2.RELEASE</version> </dependency>
(2)修改入口类的@EnableAspectJAutoProxy注解为:
@EnableAspectJAutoProxy(exposeProxy = true)
exposeProxy = true的作用是暴露当前代理对象到当前线程绑定。
(3)修改controller层中方法的调用方式:
AopContext.currentProxy()使用ThreadLocal保存了代理对象,所以直接把方法之间的调用方式改为代理对象之间的调用即可。
/** * 两个方法同时执行 * @return String */ @RequestMapping("/user/age") public String getAge(){ int age1=((UserController)AopContext.currentProxy()).getAgeOfUser(); int age2=((UserController)AopContext.currentProxy()).getAgeOfUser2(); return "spring_boot_demo:"+age1+";other_data:"+age2; }
代码如上所示,使用((UserController)AopContext.currentProxy()).getAgeOfUser()的方式,通过代理对象调用方法,所得结果为:
两个数据库的结果同时获取并显示了。
2、把DataSource注解放到service层的方法中:
(1)修改Service层的方法,加上注解:
@Override public int getAgeByUsername(String username) { return userMapper.getAgeByUsername(username); } @Override @DataSource(DataSourceName.SECOND) public int getAgeByUsername2(String username) { return userMapper.getAgeByUsername(username); }
(2)在controller中直接调用即可:
/** * 两个方法同时执行 * @return String */ @RequestMapping("/user/age") public String getAge(){ int age1=userService.getAgeByUsername("springbootdemo"); int age2=userService.getAgeByUsername2("springbootdemo"); return "spring_boot_demo:"+age1+";other_data:"+age2; }
结果如下: