Spring 读写分离

本文探讨如何利用Spring的AOP实现数据库的读写分离,详细解析了@DataSource注解的使用以及在@Transactional存在的情况下,@Before无法生效的原因。通过分析Spring的拦截器顺序,提出两种解决方案:调整切面的Order或者自定义TransactionInterceptor。同时介绍了aspectj-autoproxy配置的作用以及Spring代理方式的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目的: 使用Spring的AOP实现 数据库的 读写分离。


研究
.1. 数据库读写分离,将会有多个数据源,比如 slaveDataSource,masterDataSource
.2. 自定义个@DataSource接口,直接在相应的方法上面注入 @DataSource(value=”master”) 或者 @DataSource(value=”slave”) 进行区分

.3. 发现直接用@Aspect的@Before不行,因为在项目中使用了@Transational
原因:

Spring中的事务跟数据库中的事务相对应,所以也可以回滚,提交。 在开始事务之前,会先获取数据源,连接数据库,所以在@Aspect中的@Before中进行相关操作就晚了,具体可以在 TransactionAspectSupport::invokeWithinTransaction 中设置断点进行观察。这一切都是在TransactionInterceptor 进行的

.4. 所以,最后的方式有两个解决方案

4.1. 方案一: 采用对切面分配 Order,让他们先后执行
4.2. 方案二: 自定义一个 TransactionInterceptor,在里面进行数据源的确定


aspectj-autoproxy 简单介绍

在spring的配置文件中,配置了如下:

    <context:annotation-config />
    <aop:aspectj-autoproxy proxy-target-class="true" />

aspectj-autoproxy 是配置切面的。 @Transactional 跟 @Aspect 都是切面,即都是AOP,所以都会自动代理,生成相应的代理类

    proxy-target-class="true"

该属性的配置的主要作用是在生成代理的时候,采用哪种代理方式:
1. 当被代理对象的是一个 类时,采用CGLIB的方式生成代理
2. 当被代理对象是一个 接口时,采用JDK的动态代理方式生成代理

如果没有该属性,所有的代理方式都是JDK的动态代理方式
源码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface()) {
                return new JdkDynamicAopProxy(config);
            }
            return CglibProxyFactory.createCglibProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }
    ....
}

为何@Transational跟@Aspect同为AOP,@Transational却在@Aspect的前面执行?

为了解开谜底,设计了一个Junit,进行debug:


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml","classpath:spring/spring-datasource.xml"})
public class LabelServiceImplTest {

  @Resource
  private LabelService myLabelService;


  @Test
  public void testGetAllLabel() {
    List<Label> labels = myLabelService.getAllLabel();
    Assert.assertTrue(labels.size() == 11);
  }
 }
@Service
@Transactional
public class LabelServiceImpl implements LabelService {
    @Autowired
    private LabelDao labelDao;
    ....
}

通过debug,可以发现:

1. 有好几个Interceptor
断点设置在:ReflectiveMethodInvocation::proceed()
2. 每一个类的方法,都有一个 Interceptor 链
断点设置在: DynamicAdvisedInterceptor::intercept()
3. 而每一个 Interceptor 的创建,都是 根据相应的Advisor来的
断点设在: DefaultAdvisorChainFactory::getInterceptorsAndDynamicInterceptionAdvice()
4. 而advisor是如何来的 (断点设置: AbstractAutoProxyCreator::wrapIfNecessary() ):

.1.创建第一个Bean的时候,会执行以下步骤 (断点设置在:AnnotationAwareAspectJAutoProxyCreator::findCandidateAdvisors() )

1.1 创建 org.springframework.aop.Advisor 的实现类
      创建出来的实现类是: BeanFactoryTransactionAttributeSourceAdvisor,而该类的注释也说明了它就是给@Transational用的
1.2. 创建有@Aspect注解的类

.2. 当执行到相应的类的时候 (断点设置: AbstractAdvisorAutoProxyCreator::findEligibleAdvisors )

2.1. 获取所有的切面
    1. Advisor子类的
    2. @Aspect切面的           
2.2. 通过beanName跟beanClass 将当前beanClass的 Advisor 给过滤出来
2.3. 对过滤之后的切面 根据 Order 进行排序
如果都没有设置Order,那么大家的Order都是Integer.MAX_VALUE
5. 所以,Interceptor的顺序跟 advisor的顺序有关,而advisor的顺序是跟以下两个因素有关:
1. 它是否是 org.springframework.aop.Advisor
2. 它的Order是否设置过

所以,我们可以有两种方案:
1. 自定义一个 TransationalInterceptor
2. 新加一个@Aspect类,并给它分配Order


采用 具体的操作过程如下:


公共区域

  1. 数据源的配置
<bean id="masterDatasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="user" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    ....
</bean>

<bean id="slaveDatasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="jdbcUrl" value="${jdbc.slave.url}"></property>
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="user" value="${jdbc.slave.username}" />
    <property name="password" value="${jdbc.slave.password}" />
    ....
</bean>
  1. 自定义 DataSource 注入接口
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
  String value();
}
  1. 定义 DynamicDataSourceHolder
public class DynamicDataSourceHolder {
  public static final ThreadLocal<String> holder = new ThreadLocal<String>();
  public static void putDataSource(String name) {
      holder.set(name);
  }
  public static String getDataSouce() {
      return holder.get();
  }
}

采用第二种方案: 新加一个@Aspect类,并给它分配Order

@Component
@Aspect
@Order(value=1)
public class TransactionAspector {

    @Before(value = "execution(* com.tfdd.service..*.*(..))")
    public void doBefore(JoinPoint point) { 

      Object target = point.getTarget();
      String method = point.getSignature().getName(); 

      Class<?>[] classz = target.getClass().getInterfaces();
      Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
              .getMethod().getParameterTypes();
      try {
          Method m = classz[0].getMethod(method, parameterTypes);
          if (m != null && m.isAnnotationPresent(DataSource.class)) {
              DataSource data = m.getAnnotation(DataSource.class);
              DynamicDataSourceHolder.putDataSource(data.value());
          }
      } catch (Exception e) {
          // TODO: handle exception
      }
  }

采用第一种方案: 自定义 TransationalInterceptor

  1. 修改spring-base.xml配置文件
    1. spring-base.xml 的讲究
    <aop:aspectj-autoproxy proxy-target-class="true" />

如果采用以上配置,则 @DataSource 要放在 LabelServiceImpl 的上头,因为运行的时候,Spring会先找 LabelServiceImpl,而它是一个类,所以会用CGLIB进行代理

    @DataSource("slave")
    public List<Label> getAllLabel() {
        return labelDao.getAllLabelInfo();
    }

而如果采用以下配置,只需要在接口LabelService上配上@DataSource就可以了:

    <aop:aspectj-autoproxy />
     @DataSource("slave")
    public List<TfLabel> getAllLabel();
  1. 修改spring-datasource.xml配置文件
    添加如下配置:

    <bean id = "transactionInterceptor" class="com.tfdd.interceptor.MyTransactionInterceptor">
     <property name="transactionManager" ref="transactionManager" />
     <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
     </property>
    </bean>
    <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
        <property name="transactionInterceptor" ref="transactionInterceptor"/>
    </bean>
  2. MyTransactionInterceptor

@SuppressWarnings("serial")
public class MyTransactionInterceptor extends TransactionInterceptor {

  @Override
  public Object invoke(final MethodInvocation invocation) throws Throwable {

    Method method = invocation.getMethod();
    try {
        if (method != null && method.isAnnotationPresent(DataSource.class)) {
            DataSource data = method.getAnnotation(DataSource.class);
            DynamicDataSourceHolder.putDataSource(data.value());
        }

    } catch (Exception e) {
        // TODO: handle exception
    }


    return super.invoke(invocation);
  }
}

由于时间的关系,研究的不是很深入,可能很多地方还是有理解不到位的地方,还请各位大牛指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值