基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Trasactional注解的事务管理,但在通过基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务是有区别的,我们接下来看看到底有哪些区别。

一、基础工作

实例SpringMVC + Spring4.3.8 + Mybatis3.2.6 + Logback 的项目,如下所示:

  1. 将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>
  2. 并添加注解式事务支持
    <!-- 编程式即采用注解的方式,事务扫描开始(开启注解@Tranctional) -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
  3. 在我们的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();
    }
    }
    }
  4. 开启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建议放置到具体类上,不要放置到接口。

What a meaningless sense if losing myself,though owning all of the world.

最新文章

  1. 【.net 深呼吸】细说CodeDom(5):类型成员
  2. Codeforces Round #363 LRU(概率 状压DP)
  3. hdu---------(1026)Ignatius and the Princess I(bfs+dfs)
  4. HTML第五天学习笔记
  5. CentOS7安装和配置FTP
  6. Hibernate的Session会话中get()和load()方法的区别
  7. insert当 sql语句里面有变量 为字符类型的时候 要3个单引号
  8. 原生JS判断密码强弱
  9. ThinkPadT440 Ubuntu14.04 RTL8192EE 链接无线网
  10. 联系InfoSphere Streams和OpenMI时对水利模型联系的设计模式的一些考虑
  11. arcEngine添加标注(上)
  12. hdu 3911 Black And White(线段树)
  13. Intellij 快捷键
  14. 关于web程序快速开发个人见解以及经历
  15. 前端MVC Vue2学习总结(一)——MVC与vue2概要、模板、数据绑定与综合示例
  16. Java集合-----java集合框架常见问题
  17. Activity和Window的View的移动的一些思考与体会,腾讯悬浮小火箭的实现策略
  18. Matlab以MEX方式调用C源代码
  19. 不同系统下的字长------typedef的意义
  20. java 线程理解

热门文章

  1. linux path 与 classpath 区别
  2. Mybatis十( mybatis其他使用)
  3. 1. ip正则表达式验证
  4. flex学习笔记 显示数字步进
  5. 利用pyusb来查询当前所以usb设备
  6. 选择、操作web元素
  7. [转]使用Ubuntu Live CD修复Grub引导教程
  8. J2SE 8的流库 --- 转换流, 得到的还是流
  9. APP-9.1-百度应用-文字识别
  10. Django 数据库的迁移