#为什么使用this调用@Transaction注解的事务方法失效?
@Transaction
注解定义事务方法时是基于Spring的AOP特性,即@Transaction
底层使用的是Spring框架的AOP。通过AOP才能在实际方法前后增加其他逻辑,比如事务的启动及回滚。
当一个类UserService
使用AOP,即面向切面编程时,Spring会创建UserService
的代理对象来拦截对原始对象的调用,这样才能对目标对象的方法进行增强,因此Spring启动时会生成代理对象的bean并放入容器中。后续自动注入UserService
类的对象时,获取的也是UserService
的代理对象。但当使用new
来创建UserService的对象获取的还是原始对象,因为它没有经过spring的管理。
当类UserService
中有任何一个方法使用了切面时(如使用了@Transaction
),Spring就会创建UserService
的代理对象:
- 打印
userService.getClass()
的结果(UserService
类中方法save
使用了@Transaction
),结果如下:
> com.sun.test.userService$$EnhancerBySpringCGLIB$$37dac4e3
- 清除
@Transaction
后打印结果为:
> com.sun.test.userService
可见userService
的类为CGLIB代理,此外Spring还支持JDK动态代理:
- JDK动态代理:用于接口,创建的代理对象实现了目标类的接口,只能调用接口中的方法,类是proxy
- CGLIB代理:用于没有实现接口的类,通过子类化(继承)来创建代理对象,类是EnhancerBySpringCGLIB
Spring默认使用JDK动态代理,但是如果目标类没有实现接口,就会切换到CGLIB代理,上面的userService
没有接口,所以代理类是CGLIB。
之所以通过this
调用事务方法失效,是因为this
调用时并未使用代理对象,而是使用原始对象(直接在同一个实例中调用另一个方法,spring无法拦截这个调用),原始对象仅仅使用了@Transaction
注解,并未主动启用事务语句或进行回滚处理,而注解不通过代理无法生效,因此事务无法正常生效。只有使用代理对象调用事务方法时,事务管理才会生效,因为spring已经处理好相关逻辑。
但是,spring不是已经注入了代理对象吗,怎么又会出现原始对象呢?
代理对象和原始对象:
- 代理对象:如果某个类UserService需要使用AOP特性时,Spring就会创建它的代理对象来包装原对象,后续通过
@Autowired
注入的就是这个代理对象。代理对象负责处理切面逻辑(事务、日志),并在特定点调用原始对象的方法。使用userService.method()
就会走spring的代理。 - 原始对象:没有经过代理的对象,即未使用AOP,那么注入时直接返回原始对象。在实例中使用
this
调用方法时,也是调用原始实例中的方法。
当bean被创建时,spring会维护一个原始对象和一个代理对象(如果类存在AOP),代理对象是对原始对象的包装,当实例内部使用this
调用时会作用于原始对象,而通过对象.方法
的方式会经过代理,这就是this
调用事务方法失效的原因,因为没有通过spring代理。
验证:UserService
有两个方法save
和add
,save
方法调用add
方法,只有add
方法添加了@Transaction
,controller中注入UserService
实例,然后在userService.save
之前打印userService
的class名字,在save
方法中打印this
的class名字,两次输出如下:
>com.sun.test.userService$$EnhancerBySpringCGLIB$$37dac4e3
>com.sun.test.userService
与上述描述一致。
更简单一点的理解,当使用代理对象调用非事务方法时,该方法并不需要任何切面操作,那么代理对象会在代理方法中直接调用原始对象的目标方法,那么在这个目标方法中使用this
调用时,使用的自然就是原始对象,所以即使用this调用使用注解的事务方法也没用。
而若调用事务方法,存在切面操作,会在调用原始对象目标方法之前之后设定事务启动和出错回滚,这样即使目标方法内通过this
调用了其他方法,只要出错就会一起全部回滚,因为都在最外层的事务处理当中,看起来好像是this调用的事务方法奏效,但其实是代理对象调用的事务方法奏效(对应下方解决方案3)。
解决方案:
本质上来说就是得通过代理对象.方法
来实现事务方法的正常调用:
- 可以将事务方法移出到另一个service中,然后通过新的service调用事务方法
- 使用
@Autowired
注入本身,比如在UserService
中注入userService
,然后通过userService.
调用事务方法 - 把
@Transaction
往上移一级,使得代理对象能直接调用事务方法