问题发生场景: 在同一个接口中,需要使用多个数据源进行数据查询。在
queryA
、queryB
方法上的多数据源失效,虽然在selectData方法上使用注解生效,但是这个方法需要两个数据源查询,同时DataSource注解会触发环绕切面DataSourceAspect[@Around("dsPointCut()")]
,这样子显然是行不通的。
Spring相关说明
Spring 如何管理 @Service 注解的类
-
Bean 定义:当 Spring 扫描到带有 @Service 注解的类时,会将其注册为 Spring 容器中的一个 Bean。这个 Bean 的名字默认是类名首字母小写,也可以通过 @Service(“beanName”) 来显式指定。
-
代理对象:如果这个类需要某些功能增强(例如事务处理、
AOP
等),Spring 会使用代理模式来生成代理对象。代理对象可以是 JDK 动态代理(如果该类实现了接口)或 CGLIB 代理(如果该类没有实现任何接口)。 -
依赖注入:一旦这个类被注册为 Bean,便可以通过 @Autowired 注解或其他依赖注入方式将其注入到其他组件中。
代理对象的工作原理
- Spring 使用代理对象来增强 Bean 的功能,这并不意味着 Spring 会创建一个新的类来继承当前类。以下是代理对象的两种主要创建方式:
-
JDK 动态代理:如果你的类实现了一个或多个接口,Spring 会使用 JDK 动态代理来创建一个代理对象。这个代理对象实现了相同的接口,并委托实际的实现类来处理方法调用。
-
CGLIB 代理:如果你的类没有实现接口,Spring 会使用 CGLIB 库来生成一个子类代理。CGLIB 通过创建实际类的子类并覆盖其方法来实现代理。
发生上述问题的原因
内部方法调用:当你在同一个类内部调用另一个方法时,Spring 不会通过代理对象进行调用,而是直接通过 this 进行调用。这意味着 AOP 切面无法拦截这些调用。
使用 this 进行调用:当你在同一个类中使用 this 调用其他方法时,实际上是直接调用了当前对象的方法,而没有经过 Spring 代理对象。
方案一、使用若依提供封装好的共通类SpringUtils
@Service
public class MyServiceImpl implements MyService {
@Override
public List<Entity> selectData(Entity entity) {
List<EntityA> aLists = SpringUtils.getAopProxy(this).queryA(entity);
List<EntityB> bLists = SpringUtils.getAopProxy(this).queryB(entity);
.......
}
@DataSource(value = DataSourceType.SLAVE1)
public List<EntityA> queryA(entity) {
return mapper.queryA(entity);
}
@DataSource(value = DataSourceType.SLAVE2)
public List<EntityB> queryB(entity) {
return mapper.queryB(entity);
}
}
方案二、使用ApplicationContext
@Service
public class MyServiceImpl implements MyService {
@Autowired
private ApplicationContext context;
private MyServiceImpl proxy;
@PostConstruct
public void init() {
proxy = context.getBean(MyServiceImp.class);
}
@Override
public List<Entity> selectData(Entity entity) {
List<EntityA> aLists = proxy.queryA(entity);
List<EntityB> bLists = proxy.queryB(entity);
.......
}
@DataSource(value = DataSourceType.SLAVE1)
public List<EntityA> queryA(entity) {
return mapper.queryA(entity);
}
@DataSource(value = DataSourceType.SLAVE2)
public List<EntityB> queryB(entity) {
return mapper.queryB(entity);
}
}
SpringUtils
import org.springframework.aop.framework.AopContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.StringUtils; /** * spring工具类 方便在非spring管理环境中获取bean * */ @Component public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException * */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ public static String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } /** * 获取配置文件中的值 * * @param key 配置文件的key * @return 当前的配置文件的值 * */ public static String getRequiredProperty(String key) { return applicationContext.getEnvironment().getRequiredProperty(key); } }
通过上述两种方式,都可以解决在selectData
方法中,使用两种不同的数据源进行数据查询