借鑒:http://jinnianshilongnian.iteye.com/blog/1508018
基於JDK動態代理和CGLIB動態代理的實現Spring注解管理事務(@Trasactional)到底有什么區別。
我還是喜歡基於Schema風格的Spring事務管理,但也有很多人在用基於@Trasactional注解的事務管理,但在通過基於JDK動態代理和CGLIB動態代理的實現Spring注解管理事務是有區別的,我們接下來看看到底有哪些區別。
一、基礎工作
實例SpringMVC + Spring4.3.8 + Mybatis3.2.6 + Logback 的項目,如下所示:
- 將xml聲明式事務刪除
<!-- 切面 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes > <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <aop:config > <aop:pointcut expression="execution(* com.only.mate.service.*.*(..))" id="serviceCutPoint"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceCutPoint"/> </aop:config>
- 並添加注解式事務支持
<!-- 編程式即采用注解的方式,事務掃描開始(開啟注解@Tranctional) --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
- 在我們的UserService接口上添加 @Transactional 使該方法開啟事務
package com.only.mate.service; import org.springframework.transaction.annotation.Transactional; import com.only.mate.entity.User; public interface UserService { public User findOne(String username);
@Transactional public void save(User user); }package com.only.mate.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.only.mate.entity.User; import com.only.mate.repository.UserMapper; import com.only.mate.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User findOne(String username) { return userMapper.getUserByUserName(username); } @Override public void save(User user) { userMapper.insert(user); if("zhangsan".equals(user.getUserName())){ throw new RuntimeException(); } } }
- 開啟Logback日志的sql輸出
准備完畢
二、基於JDK動態代理
<!-- 編程式即采用注解的方式,事務掃描開始(開啟注解@Tranctional) --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
@Transactional注解放置在接口(抽象類或抽象方法)和具體類(實現類或實現方法)上都可以,具體解析請看"七、問題"。
運行訪問,核心日志如下:
2017-11-08 16:36:52.231 耗時:1392016 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Acquired Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] for JDBC transaction 2017-11-08 16:36:52.235 耗時:1392020 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] to manual commit Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4] JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] will be managed by Spring ==> Preparing: insert into user (id, username, password, sex, age, telphone, address) values (?, ?, ?, ?, ?, ?, ?) ==> Parameters: null, zhangsan(String), 123(String), 男(String), 13(Integer), 14444444444(String), 12313213123123(String) <== Updates: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4] 2017-11-08 16:36:52.304 耗時:1392089 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Initiating transaction rollback 2017-11-08 16:36:52.304 耗時:1392089 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] 2017-11-08 16:36:52.357 耗時:1392142 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] after transaction 2017-11-08 16:36:52.357 耗時:1392142 日志來自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內容:Returning JDBC Connection to DataSource
到此我們可以看到事務起作用了,事務有回滾,也就是說即使把@Transactional放到接口上 基於JDK動態代理也是可以工作的。
三、基於CGLIB類代理
<!-- 編程式即采用注解的方式,事務掃描開始(開啟注解@Tranctional) --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
該配置方式是基於CGLIB類代理
運行訪問,核心日志如下:
2017-11-08 16:44:58.079 耗時:8198 日志來自:org.springframework.web.servlet.DispatcherServlet 日志類型: DEBUG 日志內容:DispatcherServlet with name 'springServlet' processing POST request for [/SpringMVC/user/save] 2017-11-08 16:44:58.088 耗時:8207 日志來自:o.s.w.s.m.a.DefaultAnnotationHandlerMapping 日志類型: DEBUG 日志內容:Mapping [/user/save] to HandlerExecutionChain with handler [com.only.mate.controller.UserController@4731f9a] and 1 interceptor 2017-11-08 16:44:58.111 耗時:8230 日志來自:org.springframework.web.cors.DefaultCorsProcessor 日志類型: DEBUG 日志內容:Skip CORS processing: request is from same origin 2017-11-08 16:44:58.119 耗時:8238 日志來自:o.s.w.bind.annotation.support.HandlerMethodInvoker 日志類型: DEBUG 日志內容:Invoking request handler method: public java.util.Map com.only.mate.controller.UserController.save(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2017-11-08 16:44:58.121 耗時:8240 日志來自:com.only.mate.controller.UserController 日志類型: DEBUG 日志內容:注冊,用戶名zhangsan,密碼123 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@158e6b7f] was not registered for synchronization because synchronization is not active 2017-11-08 16:44:58.164 耗時:8283 日志來自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內容:Fetching JDBC Connection from DataSource 2017-11-08 16:44:58.453 耗時:8572 日志來自:com.alibaba.druid.pool.DruidDataSource 日志類型: INFO 日志內容:{dataSource-1} inited JDBC Connection [com.mysql.jdbc.JDBC4Connection@39d16e44] will not be managed by Spring ==> Preparing: insert into user (id, username, password, sex, age, telphone, address) values (?, ?, ?, ?, ?, ?, ?) ==> Parameters: null, zhangsan(String), 123(String), 男(String), 13(Integer), 14444444444(String), 12313213123123(String) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@158e6b7f] 2017-11-08 16:44:58.556 耗時:8675 日志來自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內容:Returning JDBC Connection to DataSource
到此我們可以看到事務沒有起作用,事務沒有回滾。
只有將注解放在具體類上或具體類的實現方法上才會起作用。
package com.only.mate.service; import com.only.mate.entity.User; public interface UserService { public User findOne(String username); public void save(User user); }
package com.only.mate.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.only.mate.entity.User; import com.only.mate.repository.UserMapper; import com.only.mate.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User findOne(String username) { return userMapper.getUserByUserName(username); } @Override
@Transactional public void save(User user) { userMapper.insert(user); if("zhangsan".equals(user.getUserName())){ throw new RuntimeException(); } } }
運行訪問,核心日志如下:
2017-11-08 16:49:54.992 耗時:3784 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Acquired Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] for JDBC transaction 2017-11-08 16:49:54.997 耗時:3789 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] to manual commit Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24] JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] will be managed by Spring ==> Preparing: insert into user (id, username, password, sex, age, telphone, address) values (?, ?, ?, ?, ?, ?, ?) ==> Parameters: null, zhangsan(String), 123(String), 男(String), 13(Integer), 14444444444(String), 12313213123123(String) <== Updates: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24] 2017-11-08 16:49:55.070 耗時:3862 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Initiating transaction rollback 2017-11-08 16:49:55.070 耗時:3862 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] 2017-11-08 16:49:55.124 耗時:3916 日志來自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內容:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] after transaction 2017-11-08 16:49:55.124 耗時:3916 日志來自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內容:Returning JDBC Connection to DataSource
運行測試類,將發現成功了。
還有一種情況如下:
package com.only.mate.service.impl; @Service public class StudentServiceImpl extends UserServiceImpl implements StudentService { @Autowired private UserMapper userMapper; @Override public User findOne(String username) { return userMapper.getUserByUserName(username); }
//沒有@Transactional @Override public void save(User user) { userMapper.insert(user); if("zhangsan".equals(user.getUserName())){ throw new RuntimeException(); } } }
這種情況也將無法織入事務。
四、基於aspectj的
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj" proxy-target-class="true"/>
在此就不演示了,我們主要分析基於JDK動態代理和CGLIB類代理兩種的區別。
五、結論
基於JDK動態代理 ,可以將@Transactional放置在接口(抽象類或抽象方法)和具體類(實現類或實現方法)上。
基於CGLIB類代理,只能將@Transactional放置在具體類(實現類或實現方法)上。
因此 在實際開發時全部將@Transactional放到具體類上,而不是接口上。
六、分析
1、 JDK動態代理
1.1、Spring使用JdkDynamicAopProxy實現代理:
package org.springframework.aop.framework; final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { //注意此處的method 一定是接口上的method(因此放置在接口上的@Transactional是可以發現的) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { } }
注意此處的method 一定是接口上的method(因此放置在接口上的@Transactional是可以發現的)
1.2、如果<tx:annotation-driven 中 proxy-target-class="true" ,Spring將使用CGLIB動態代理,而內部通過Cglib2AopProxy實現代理,而內部通過DynamicAdvisedInterceptor進行攔截:
package org.springframework.aop.framework; final class Cglib2AopProxy implements AopProxy, Serializable { private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable { //注意此處的method 一定是具體類上的method(因此只用放置在具體類上的@Transactional是可以發現的) public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { } } }
1.3、Spring使用AnnotationTransactionAttributeSource通過查找一個類或方法是否有@Transactional注解事務來返回TransactionAttribute(表示開啟事務):
package org.springframework.transaction.annotation; public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable { protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) { for (TransactionAnnotationParser annotationParser : this.annotationParsers) { TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae); if (attr != null) { return attr; } } return null; } }
而AnnotationTransactionAttributeSource又使用SpringTransactionAnnotationParser來解析是否有@Transactional注解:
package org.springframework.transaction.annotation; public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { Transactional ann = AnnotationUtils.getAnnotation(ae, Transactional.class); if (ann != null) { return parseTransactionAnnotation(ann); } else { return null; } } public TransactionAttribute parseTransactionAnnotation(Transactional ann) { } }
此處使用AnnotationUtils.getAnnotation(ae, Transactional.class); 這個方法只能發現當前方法/類上的注解,不能發現父類的注解。 Spring還提供了一個 AnnotationUtils.findAnnotation()方法 可以發現父類/父接口中的注解(但spring沒有使用該接口)。
如果Spring此處換成AnnotationUtils.findAnnotation(),將可以發現父類/父接口中的注解。
七、問題
我們之前說過,基於JDK動態代理時, method 一定是接口上的method(因此放置在接口上的@Transactional是可以發現的),但現在我們放在具體類上,那么Spring是如何發現的呢??
TransactionAttribute是通過AnnotationTransactionAttributeSource返回的(具體看步驟1.3),而AnnotationTransactionAttributeSource 繼承AbstractFallbackTransactionAttributeSource
package org.springframework.transaction.interceptor; public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource { public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) { //第一次 會委托給computeTransactionAttribute } //計算TransactionAttribute的 private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) { //省略 // Ignore CGLIB subclasses - introspect the actual user class. Class<?> userClass = ClassUtils.getUserClass(targetClass); // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. //①此處將查找當前類覆蓋的方法 Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass); // If we are dealing with method with generic parameters, find the original method. specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); // First try is the method in the target class. TransactionAttribute txAtt = findTransactionAttribute(specificMethod); if (txAtt != null) { return txAtt; } //找類上邊的注解 // Second try is the transaction attribute on the target class. txAtt = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAtt != null) { return txAtt; } //②如果子類覆蓋的方法沒有 再直接找當前傳過來的 if (specificMethod != method) { // Fallback is to look at the original method. txAtt = findTransactionAttribute(method); if (txAtt != null) { return txAtt; } // Last fallback is the class of the original method. return findTransactionAttribute(method.getDeclaringClass()); } return null; } }
//①此處將查找子類覆蓋的方法
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
// ClassUtils.getMostSpecificMethod public static Method getMostSpecificMethod(Method method, Class<?> targetClass) { Method specificMethod = null; if (method != null && isOverridable(method, targetClass) && targetClass != null && !targetClass.equals(method.getDeclaringClass())) { try {
//可以看出將找到當前類的那個方法。因此我們放置在UserService save方法上的@Transactional起作用了。 specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes()); } catch (AccessControlException ex) { // security settings are disallowing reflective access; leave // 'specificMethod' null and fall back to 'method' below } } return (specificMethod != null ? specificMethod : method); }
因此,建議大家使用基於Schema風格的事務(不用考慮這么多問題,也不用考慮是類還是方法)。而@Transactional建議放置到具體類上,不要放置到接口。