目的: 使用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.创建第一个Bean的时候,会执行以下步骤 (断点设置在:AnnotationAwareAspectJAutoProxyCreator::findCandidateAdvisors() )
.2. 当执行到相应的类的时候 (断点设置: AbstractAdvisorAutoProxyCreator::findEligibleAdvisors )
所以,我们可以有两种方案: 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); } }由于时间的关系,研究的不是很深入,可能很多地方还是有理解不到位的地方,还请各位大牛指正
