首先我们看下没有使用CGLIB对Controller
进行代理时代码的样子
原始程序
@SpringBootApplication
public class CglibProxyProblemMain {
public static void main(String[] args) {
SpringApplication.run(CglibProxyProblemMain.class, args);
}
}
@Service
public class TestService {
public String test(){
return "test";
}
public String test2(){
return "test2";
}
}
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/test")
private String test(){
return testService.test();
}
@GetMapping("/test2")
public String test2(){
return testService.test2();
}
}
上面的逻辑很简单,CglibProxyProblemMain
作为Spring的启动入口,TestController
中依赖了TestService
。然后我们运行一下程序,并测试一下接口是否正常。
从curl的结果上看,我们TestController应该是生效了
加入切面
然后,我们引入一个切面,并在TestController
的方法执行前先输出一个“before …”
@Component
@Aspect
public class TestAspect {
@Before("pointCut()")
public void beforeTest(){
System.out.println("before ...");
}
@Pointcut("execution(public * com..TestController.*(..))")
public void pointCut() {
//定义切点
}
}
然后我们再跑一次程序。服务启动正常:
接下来再测试一下之前的接口,然后我们的\test
接口报错了:
神奇的空指针问题就这么出现了…
问题排查
既然是空指针,那我们先调试一下吧:
从调试的堆栈信息上看,this
指向的当前对象是一个被CGLIB代理之后的对象。因为这个类没有接口,同时我们又启用了切面,所以当前对象是一个被CGLIB代理之后的对象感觉也没什么问题。
但是问题是为什么testService
是null呢?难道没有注入成功?那我们换个注入方式吧,使用构造器注入,同时将testService
使用final
修饰,这样testService
应该就不会null了。
修改之后TestController
是这样的:
稳妥起见,我们再跟踪一次服务启动,确认一下testService
确实被成功注入了。
这次应该没问题了,我们再测试一下接口:
依旧是一个空指针的报错,再调试一次程序,发现testService
依旧是null,就像下面这样:
这就很诡异了…
我们再测试一下/test2
这个接口,更加神奇的现象出现了,/test2
完全没问题
难道testService
又有了?我们接着调试一下:
从调试信息中可以看到,在执行test2
时,testService
确实是存在的,而且this
指向并不是被CGLIB代理之后的类。
问题解决
这下就找到问题的原因了
CGLIB无法代理被
private
或final
修饰的方法
由于test
方法是private
的,无法被CGLIB代理,所以当这个方法被调用时,其实调用的是代理类的同名方法。但是test2
由于是public
的,可以被正常代理,所以始终都没有问题。
引申
我们再深入一下这个问题,为什么代理类执行test
方法时,testService
是null呢?难道没有依赖注入成功吗?
要解决这个问题,首先我们得看下代理是什么时机生成的:
从调试信息中可以看到,生成代理时,bean已经实例化完毕,bean中的testService
已经有值了。但是生成的代理类中的testService
却是空的。
我们都知道CGLIB是基于继承的。也就是生成的代理类是被代理类的子类。但是我们既然写了构造器注入,为什么代理类的testService
依旧是null呢?
原因是CGLIB作弊了,通过CGLIB生成的类的构造器中没有super(...)
,所以testService
就null了。饶了这么大一圈,其实最后还是因为对CGLIB代理的原理不够清晰才导致了这样的问题,-_-||