用AOP实现业务service的重新调用(三)
承接 用AOP实现业务service的重新调用(二),我们继续......
代码看似不多,但实现上需要考虑很多问题,因为哪怕只有一个问题没搞定,整个实现就是失败的.
问题列表:
1>事务完整性的问题
前后两次,是否能保证事务的完整性,我们的事务正好也是通过spring的aop实现的,所以要注意我们新加的ServiceRetryAdvice拦截器要在spring事务拦截器之前调用,也就是包在事务拦截器外面,这样才不会影响spring的事务提交/回滚机制.
<value>serviceRetryInterceptor</value> <value>transactionInterceptor</value>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"><ref bean="transactionManager"/></property> <property name="transactionAttributeSource"><ref bean="txAttribute"/></property> </bean> <bean id="transactionManagerAutoProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <value>serviceRetryInterceptor</value> <value>transactionInterceptor</value> </list> </property> <property name="beanNames"> <list> <value>testRootService</value> <value>testService</value> </list> </property> </bean>
2>事务传播行为PROPAGATION
通常最简单的情况是一个service方法对应一个事务,按照前面的配置,这种情况是没有问题的,前一次调用失败后,重新调用service,spring会重新开始一个事务,所以新事务会获取一个新的连接,执行成功返回前端.
还有一种情况是service方法里面又调用了子service方法,我们遇到的情况用的是PROPAGATION_REQUIRED(子service方法里面判断当前线程是否存在transaction,如果存在就复用,不存在再创建新transaction),所以这里仅讨论PROPAGATION_REQUIRED的情况,其它情况可以自己去详细考证,如果exception是发生在父service方法里面,没有问题.如果发生在子service方法里面,我们重调的是子service方法,这种情况下retry调用的时候,因为transaction还在,所以继续复用原来的transaction可是transaction里面的connection还是原来的无效连接,所以retry仍然会百分之百失败,所以这种case我们无能为力,直接把异常抛给前端.因为事务的commit/rollback只在父service方法里面进行,所以不会破坏事务的完整性.
3>多线程的问题(retryNum)
为了提高效率,spring内部对interceptor进行了cache,同一个service class+method,会共用同一个interceptor,所以多线程环境下,retryNum成员变量要保证线程安全,解决方法是:我们用了ThreadLocal<Integer>,这样就不用担心多线程访问的问题了.
4>ThreadLocal的注意事项
关于ThreadLocal,还需要注意内存泄露的问题,因为ThreadLocal内部对每一个线程都留了一个map,所以我们在使用完以后要主动remove掉里面的内容.
} finally { retryNum.remove(); }