众所周知,Spring AOP 是基于动态代理实现的,实现方式有两种:JDK 动态代理 和 cglib 生成代理类。JDK 动态代理这里不多说,我们都知道 cglib 是基于继承实现对目标类的代理的。
本文使用 Spring 版本:5.2.0.RELEASE
SpringAOP 自调用问题
举一个 AOP 自调用的例子:
定义切面和切点:
@Aspect
@Component
public class AopConfig {
/**
* UserManager中的所有方法执行前都加上前置通知
*/
@Before("execution(* com.dxc.opentalk.springtest.service.UserManager.*())")
public void before(){
System.out.println("before 执行");
}
}
目标类 UserManager:
@Component
public class UserManager {
/**
* 在 a() 方法中,直接调用 b()
*/
public void a(){
System.out.println("a() 方法执行");
b();
}
public void b(){
System.out.println("b() 方法执行");
}
}
在启动类中运行:
@EnableAspectJAutoProxy
@ComponentScan("com.dxc.opentalk.springtest")
public class BootStrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BootStrap.class);
UserManager userManager = (UserManager) applicationContext.getBean("userManager");
userManager.a();
}
}
程序输出结果:
before 执行
a() 方法执行
b() 方法执行
可以看到,b() 执行前没有执行AOP 前置通知。
今天在想这个问题时,想到 cglib 是基于继承目标类实现代理功能的,通过继承来实现代理应该不存在自调用问题才对,比如下面一个例子:
//使用 Father 作为目标类, Son 作为继承方式实现的代理类
public class Father {
public Father(){}
public void a(){
System.out.println("father's a()");
b();
}
public void b(){
System.out.println("father's b()");
}
}
public class Son extends Father{
public String name;
public Son(){
super();
}
public Son(String name){
super();
this.name = name;
}
@Override
public void a(){
System.out.println("son's a()");
super.a();
}
@Override
public void b(){
System.out.println("son's b()");
super.b();
}
public static void main(String[] args) {
Father s = new Son("ll");
s.a();
}
}
运行 Son 中的 main 方法,输出结果:
son's a()
father's a()
son's b()
father's b()
我们看到son's b() 正常打印了出来,证明这种继承代理的方法是不会有自调用问题的,因为实际在 a() 中调用 b()的是代理类 Son 对象,所以会进到Son.b() 方法。
结合上述情况,可以推断出Spring 使用 cglib 动态代理,肯定不是使用的上述 Father-Son 那种代理模式。
在 Spring 中,上述 UserManager 实际的情况是:
UserManager 没有实现 interface,所以 Spring 采用的是 cglib 方式生成 UserManager 的代理类。
但是在 a() 方法中调用 b() 已经是在目标类 UserManager 对象中了,不是在代理类对象中,而 UserManager 对象执行 b() 是没有AOP的。
debug 一下流程,验证一下:
我们从Spring容器中得到 userManager,可以看到它是一个用 cglib 方式实现的代理类。当执行到 a() 中方法时,此时前置通知已经执行,工作台已经打印了 “before 执行”,我们可以看到此时执行的对象是普通的 UserManager 对象,而不是代理类对象,所以它接着执行 b() 方法时,就不会有AOP前置通知了。如下图所示:
cglib 相关学习
经过查阅资料发现,cglib 中有两种方式:invokeSuper(obj, args) 和 invoke(originalObject, args) 进行方法调用,前一种方式不需要目标类实例,obj 为代理类对象;后一种方式需要目标类对象实例originalObject。第一种方式类似上面的 Father-Son 模式,后一种方式就是 Spring AOP 中采用的方式。下面我们用 cglib 重写上面的 UserManager 例子,看一下两种方式的区别:
引入 cglib jar 包:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
cglib 中的核心类:Enhancer、MethodProxy;接口:MethodIntercept
invokeSuper 和 invoke 方法定义在 MethodProxy 类中。
UserManager 类:
public class UserManager {
public void a(){
System.out.println("a() 方法执行");
// 自调用 b()
b();
}
public void b(){
System.out.println("b() 方法执行");
}
}
invokeSuper
采用 invokeSuper 方法,MyMethodIntercept 写法如下:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("before 执行");
Object res = proxy.invokeSuper(obj, args);
return res;
}
}
测试启动类:
public class CglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserManager.class);
enhancer.setCallback(new MyMethodInterceptor());
//创建代理类
UserManager userManager = (UserManager) enhancer.create();
userManager.a();
}
}
运行CglibTest.main 方法,输出结果【不会有自调用问题】:
before 执行
a() 方法执行
before 执行
b() 方法执行
invoke
采用 invoke 方法,MyMethodIntercept 写法如下:
public class MyMethodInterceptor implements MethodInterceptor {
private Object originalObj;
public MyMethodInterceptor(Object originalObj) {
super();
this.originalObj = originalObj;
}
@Override
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("before 执行");
Object res = proxy.invoke(originalObj, args);
return res;
}
}
测试启动类:
public class CglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserManager.class);
enhancer.setCallback(new MyMethodInterceptor(new UserManager()));
//创建代理类
UserManager userManager = (UserManager) enhancer.create();
userManager.a();
}
}
运行CglibTest.main 方法,输出结果【会有自调用问题】:
before 执行
a() 方法执行
b() 方法执行
Spring AOP 采用的就是 invoke 的方式。
Spring AOP 自调用的解决方法
1、把自己装配到自己当中,由装配的 bean 去调用:
@Component
public class UserManager {
@Autowired
private UserManager userManager;
public void a(){
System.out.println("a() 方法执行");
userManager.b();//从Spring容器中装配的userManager是代理类对象
}
public void b(){
System.out.println("b() 方法执行");
}
}
2、AopContext.currentProxy()
使用官方文档介绍的一种方式:AopContext.currentProxy()。注意需要使用 @EnableAspectJAutoProxy(exposeProxy = true) 启用该功能。但是官方不推荐这么做,因为这使得我们的业务代码和Spring AOP 的代码耦合在一起,这不是 AOP 的初衷。
@Component
public class UserManager {
public void a(){
System.out.println("a() 方法执行");
((UserManager) AopContext.currentProxy()).b();//ThreadLocal保存代理类
}
public void b(){
System.out.println("b() 方法执行");
}
}
3、使用AspectJ框架
AspectJ 没有此自调用问题,因为它不是基于代理的AOP框架。
最后一个小疑惑,为什么Spring中查不到 cglib 包路径下的类?
因为 cglib 中的一些类已经被spring框架打进 spring-core 包中了。
例如:org.springframework.cglib.proxy.MethodProxy 类。