cglib 和 Spring AOP 的一些学习思考

  众所周知,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 类。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值