基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别
一、基础工作
如下所示:
将xml声明式事务删除
1 2 3 4 5 | <aop:config expose-proxy= "true" > <!-- 只对业务逻辑层实施事务 --> <aop:pointcut id= "txPointcut" expression= "execution(* cn.javass..service..*.*(..))" /> <aop:advisor advice-ref= "txAdvice" pointcut-ref= "txPointcut" /> </aop:config> |
并添加注解式事务支持:
1 | <tx:annotation-driven transaction-manager= "txManager" /> |
在我们的BaseService接口上添加 @Transactional 使该方法开启事务
1 2 3 4 5 | package cn.javass.common.service; public interface IBaseService<M extends java.io.Serializable, PK extends java.io.Serializable> { @Transactional //开启默认事务 public int countAll(); } |
在我们的log4j.properties中添加如下配置,表示输出spring的所有debug信息
1 | log4j.logger.org.springframework=INFO,CONSOLE |
在我们的resources.properties里将hibernate.show_sql=true 改为true,为了看到hibernate的sql。
单元测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package cn.javass.ssonline.spider.service.impl; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import cn.javass.demo.service.UserService; @RunWith (SpringJUnit4ClassRunner. class ) @ContextConfiguration (locations = { "classpath:spring-config.xml" }) public class UserServiceTest2 { @Autowired private UserService userService; @Test public void testCreate() { userService.countAll(); } } |
基础工作做好,接下来我们详细看看 Spring基于 JDK动态代理 和 CGLIB类级别代理到底有什么区别。
二、基于JDK动态代理:
1 | <tx:annotation-driven transaction-manager= "txManager" /> |
该配置方式默认就是JDK动态代理方式
运行单元测试,核心日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Creating new transaction with name [cn.javass.common.service.impl.BaseService.countAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' //开启事务 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Opened new Session 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Bound value [org.springframework.orm.hibernate4.SessionHolder @1184a4f ] for key [org.hibernate.internal.SessionFactoryImpl @107b56e ] to thread [main] //绑定session到ThreadLocal 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Initializing transaction synchronization 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor - Getting transaction for [cn.javass.common.service.impl.BaseService.countAll] 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.hibernate4.SessionHolder @1184a4f ] for key [org.hibernate.internal.SessionFactoryImpl @107b56e ] bound to thread [main] 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Found thread-bound Session 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.hibernate4.SessionHolder @1184a4f ] for key [org.hibernate.internal.SessionFactoryImpl @107b56e ] bound to thread [main] Hibernate: select count(*) as col_0_0_ from tbl_user usermodel0_ 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Committing Hibernate transaction on Session //提交事务 2012 - 03 - 07 09 : 58 : 44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Removed value [org.springframework.orm.hibernate4.SessionHolder @1184a4f ] for key [org.hibernate.internal.SessionFactoryImpl @107b56e ] from thread [main] //解除绑定session到ThreadLocal |
到此我们可以看到事务起作用了,也就是说即使把@Transactional放到接口上 基于JDK动态代理也是可以工作的。
三、基于CGLIB类代理:
1 | <tx:annotation-driven transaction-manager= "txManager" proxy-target- class = "true" /> |
该配置方式是基于CGLIB类代理
启动测试会报错,No Session found for current thread,说明事务没有起作用
1 2 3 4 5 6 7 8 9 10 11 12 | org.hibernate.HibernateException: No Session found for current thread at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java: 97 ) at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java: 1024 ) at cn.javass.common.dao.hibernate4.BaseHibernateDao.getSession(BaseHibernateDao.java: 63 ) at cn.javass.common.dao.hibernate4.BaseHibernateDao.aggregate(BaseHibernateDao.java: 238 ) at cn.javass.common.dao.hibernate4.BaseHibernateDao.countAll(BaseHibernateDao.java: 114 ) at cn.javass.common.service.impl.BaseService.countAll(BaseService.java: 60 ) at cn.javass.common.service.impl.BaseService$$FastClassByCGLIB$$5b04dd69.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java: 149 ) at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java: 618 ) at cn.javass.demo.service.impl.UserServiceImpl$$EnhancerByCGLIB$$7d46c567.countAll(<generated>) at cn.javass.ssonline.spider.service.impl.UserServiceTest2.testCreate(UserServiceTest2.java: 20 ) |
如果将注解放在具体类上或具体类的实现方法上才会起作用。
1 2 3 4 5 6 7 8 9 | package cn.javass.common.service.impl; public abstract class BaseService<M extends java.io.Serializable, PK extends java.io.Serializable> implements IBaseService<M, PK> { @Transactional () //放在抽象类上 @Override public int countAll() { return baseDao.countAll(); } } |
运行测试类,将发现成功了。
因为我们的UserService继承该方法,但如果UserService覆盖该方法,如下所示,也将无法织入事务(报错):
1 2 3 4 5 6 7 8 | package cn.javass.demo.service.impl; public class UserServiceImpl extends BaseService<UserModel, Integer> implements UserService { //没有@Transactional @Override public int countAll() { return baseDao.countAll(); } } |
四、基于aspectj的
1 | <tx:annotation-driven transaction-manager= "txManager" mode= "aspectj" proxy-target- class = "true" /> |
在此就不演示了,我们主要分析基于JDK动态代理和CGLIB类代理两种的区别。
五、结论:
基于JDK动态代理 ,可以将@Transactional放置在接口和具体类上。
基于CGLIB类代理,只能将@Transactional放置在具体类上。
因此 在实际开发时全部将@Transactional放到具体类上,而不是接口上。
六、分析
1、 JDK动态代理
1.1、Spring使用JdkDynamicAopProxy实现代理:
1 2 3 4 5 6 | 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进行拦截:
1 2 3 4 5 6 7 8 | 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(表示开启事务):
1 2 3 4 5 6 7 8 9 10 11 12 | 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注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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(),将可以发现父类/父接口中的注解。
这里还一个问题,描述如下:
在接口中删除@Transactional //开启默认事务
1 2 3 4 | package cn.javass.common.service; public interface IBaseService<M extends java.io.Serializable, PK extends java.io.Serializable> { public int countAll(); } |
在具体类中添加@Transactional
1 2 3 4 5 6 7 8 9 | package cn.javass.common.service.impl; public abstract class BaseService<M extends java.io.Serializable, PK extends java.io.Serializable> implements IBaseService<M, PK> { @Transactional () //开启默认事务 @Override public int countAll() { return baseDao.countAll(); } } |
问题:
我们之前说过,基于JDK动态代理时, method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的),但现在我们放在具体类上,那么Spring是如何发现的呢??
还记得发现TransactionAttribute是通过AnnotationTransactionAttributeSource吗?具体看步骤1.3:
而AnnotationTransactionAttributeSource 继承AbstractFallbackTransactionAttributeSource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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 {
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);
}
可以看出将找到当前类的那个方法。因此我们放置在BaseService countAll方法上的@Transactional起作用了。
//②如果子类覆盖的方法没有 再直接找当前传过来的
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());
}
查找子类失败时直接使用传过来的方法。
因此,建议大家使用基于Schema风格的事务(不用考虑这么多问题,也不用考虑是类还是方法)。而@Transactional建议放置到具体类上,不要放置到接口。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 数据库服务器 SQL Server 版本升级公告
· 程序员常用高效实用工具推荐,办公效率提升利器!
· C#/.NET/.NET Core技术前沿周刊 | 第 23 期(2025年1.20-1.26)