目的: 使用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);
}
}
由于时间的关系,研究的不是很深入,可能很多地方还是有理解不到位的地方,还请各位大牛指正