Spring <tx:annotation-driven>注解 JDK动态代理和CGLIB动态代理 区别。
基于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建议放置到具体类上,不要放置到接口。
Spring <tx:annotation-driven>注解 JDK动态代理和CGLIB动态代理 区别。的更多相关文章
- Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。
借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...
- java的静态代理、jdk动态代理和cglib动态代理
Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...
- 代理模式之静态代理,JDK动态代理和cglib动态代理
代理模式,顾名思义,就是通过代理去完成某些功能.比如,你需要购买火车票,不想跑那么远到火车站售票窗口买,可以去附近的火车票代售点买,或者到携程等第三方网站买.这个时候,我们就把火车站叫做目标对象或者委 ...
- JDK动态代理和CGLib动态代理简单演示
JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...
- jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)
代理模式是一种很常见的模式,本文主要分析cglib动态代理的过程 1. 举例 使用cglib代理需要引入两个包,maven的话包引入如下 <!-- https://mvnrepository.c ...
- jdk动态代理和cglib动态代理的区别
一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件 ...
- jdk 动态代理和 cglib 动态代理
原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载 ...
- 动态代理:jdk动态代理和cglib动态代理
/** * 动态代理类:先参考代理模式随笔,了解代理模式的概念,分为jdk动态代理和cglib,jdk动态代理是通过实现接口的方式创建代理类的,cglib是通过继承类的方式实现的代理类的 * jdk动 ...
随机推荐
- Html标签及各种属性(持续更新)
<!--Html标签属性之:required (必须的)--> <input type="file" id="Images" required ...
- Nginx的安装(CentOS 7环境)
安装所需环境 Nginx 是 C语言 开发,建议在 Linux 上运行,当然,也可以安装 Windows 版本,本篇则使用 CentOS 7 作为安装环境. 一. gcc 安装安装 nginx 需要先 ...
- 使用Maven创建(eclipse)Java项目
1. 构建项目目录: 命令行方式: mvn archetype:generate -DgroupId={project-packaging} -DartifactId={project-name} \ ...
- py库: pymysql、 json (mysql数据库)
数据库查询结果,用json返回: #连接数据库 import pymysql print(pymysql.VERSION) conn = pymysql.Connect(host='localhost ...
- 笔记:js疑难复习
apply 和 call的区别 call 和 apply 的区别只在于这两个函数接受的参数形式不同 var Person = function(name,age){ this.name = name; ...
- jl1.如何设置元素的宽高包含元素的边框和内边距
方法一: 文档地址:http://www.w3school.com.cn/cssref/pr_box-sizing.asp CSS3 box-sizing属性: box-sizing: bord ...
- Weblogic重起后打开控制台登陆后响应极慢
linux下Weblogic 重启后第一次打开console登陆后响应极慢(20多分钟)(锁定并编辑第一次也极慢),以后速度就正常了,重启后又会极慢! 在网上看了看,是说linux下的java生成随机 ...
- winform 布局、容器
一.布局 属性:1.Anchor: 绑定控件到容器边缘位置保持不变 注:四周全锁定时控件随界面变化时变大 2.Dock:绑定到容器的边缘 注:下控件到边需先将下控件定义到边再将左右控件新建.到边 3 ...
- python3的命令行参数传递
#coding:utf-8#命令行参数传递,例如输入: python <文件名>.py -help#这个结果就会打印help#sys.argv[0]代表"文件名",第一 ...
- Spring Cloud (3)B Ribbon 负载均衡 IRule
package com.service.config; import com.netflix.loadbalancer.IRule;import com.netflix.loadbalancer.Ra ...