最近有同事在开发Spring Boot的时候遇到了注解失效问题,和解决失效问题后出现空指针的问题。
首先介绍下问题场景,在业务层类A中对a方法进行拆分,把数据库操作拆到了b方法中,并加上了@Transactional注解,但是实际使用中
并没有实现事务,异常不会回滚,也就是说注解失效了。随后在网上查阅了部分资料,他打算显示的获取代理,然后再执行,这时却抛出
了空指针异常,这让他相当的困惑。
@Service
@Slf4j
public class A {
@Autowired
private Service1 service1;
public void a() {
//业务逻辑处理
b();
}
@Transactional
private void b() {
//数据库dao层的操作集合
}
}
开始分析问题
我们都知道Spring的注解实际上是用的Aop编程,动态代理的形式加入的。注解失效的问题,大概率就是在代理的过程中出了问题,
这里先简单介绍下关于Spring的动态代理的流程
Spring会针对类A产生一个代理类A',A'是继承自类A,同时代理A'中有一个拦截器的实例,它会将所有对自己方法的调用都转为调用到拦截器中
而拦截器Handler里存在着类A的一个实例,这个实例已经被Spring的容器注入了@Autowired申明的对象(后面有用),当外部调用到A'时,实际上是由
拦截器调用了自己维护的实例执行的。
在以上的流程里,一般动态代理生成的注解实际上都是把逻辑加到了拦截器中,在执行实例前后加入要织入的逻辑。
但是在问题代码里,a方法调用b方法实际上,是用了this.b(),那获取的是当前对象实例(拦截器中维护的实例),这根本没走到代理,也就是说在同一个类里方法间的直接相互调用
会导致加入的注解失效
解决方法:用如下方法,获取当前类的代理,再调用b方法,从而保证了代理流程被执行了。
public void a() {
//业务逻辑处理
A o =(A) AopContext.currentProxy();//获取当前类的代理
o.b();
}
这里有一个错误写法,就是b方法一般抽出来的时候会写成private私有的,在IDEA中会提示你有问题的,但并不能告诉你问题在哪里,你执行的话
可能会导致NullPointerException空指针异常(如果没有用到Spring容器注入的对象倒是可以正常运行)。
正如最开始我所介绍的,Spring注入的对象,是加到拦截器维护的实例里了,你这里用多态的写法,调用的b方法,b作为私有方法根本无法继承,只能
调用父类的b方法,而这里的实例(代理类实例)根本就没有被注入容器管理的对象,它没有被赋值,就是个null,你只要调用了就直接NullPointerException。
解决方法:把private改掉,让子类可以继承方法。这样代多态的调用b方法,会走到代理类A'的b方法,然后进入正常代理流程,注解就生效了。问题得到解决
以上是我对问题的解析和解决方法,希望能帮助到你,也欢迎大佬们批评指导。