Spring 读写分离

    xiaoxiao2026-08-20  2


    目的: 使用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


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


    公共区域

    数据源的配置 <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> 自定义 DataSource 注入接口 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataSource { String value(); } 定义 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

    修改spring-base.xml配置文件 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();

    修改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>

    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); } }

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

    转载请注明原文地址: https://ju.6miu.com/read-1311387.html
    最新回复(0)