AOP方法嵌套调用为何失效和解决方案

嵌套方法调用失效

业务类

package liu.york.aspect.demo3;

@Component
public class OrderService{
    public void fun1(){
        System.out.println("fun1 ...");
        fun2();
    }
    public void fun2(){
        System.out.println("fun2 ...");
    }
}

业务代理类

package liu.york.aspect.demo3;
@Aspect
@Component
public class OrderServiceAop {

    @Before("execution(* liu.york.aspect.demo3.OrderService.fun1())")
    public void fun1JoinPoint(JoinPoint joinPoint){
        System.out.println("join point fun1 ...");
    }
    
    @Before("execution(* liu.york.aspect.demo3.OrderService.fun2())")
    public void fun2JoinPoint(JoinPoint joinPoint){
        System.out.println("join point fun2 ...");
    }
}

通过 OrderService orderService = ApplicationContext.getBean(OrderService.class) 获取实例,那么单独调用方法fun1,发现只有fun1被正确代理了,fun2并没有被代理,原因就是我们获取的实例orderService 根本就不是 OrderService.class 真正的实例,而是被AOP代理过的实例,所以用代理对象去调用 fun1 或者 fun2 都是可以配正确执行的。

真正理解这点,需要理解业务逻辑代码和代理逻辑代码概念:

  1. 业务逻辑代码:就是上面OrderService类中的业务代码
  2. 代理逻辑代码:就是上面OrderServiceAop类中的增强代码

Spring Aop 代理调用其实都是组合调用,所谓组合就是代理类和被代理类组合一起来完成 业务逻辑代码 和 代理逻辑代码 的执行,以@Before 为例,我们都知道在执行 OrderService#fun1 的时候,肯定会被拦截,先调用 OrderServiceAop#fun1JoinPoint,但是执行fun1JoinPoint方法后,接着就要执行 fun1 方法,那这个方法是代理对象(OrderServiceAop)执行还是被代理对象(OrderService)执行呢?答案是被代理对象,也就是OrderService对象。

现在问题就迎刃而解了,由于是 OrderService 对象执行的fun1,那么fun1里面再调用fun2,就注定fun2没有被代理,因为OrderService根本就没有代理逻辑代码,这个代理逻辑代码在 OrderServiceAop 里面。

OrderService orderService = ApplicationContext.getBean(OrderService.class) 这个返回的对象可以使用debug查看其真正的类型。Spring Aop 中有两种动态代理,分别是JDK动态代理和Cglib动态代理,前者是基于接口,后者是基于继承。

如果 OrderService 实现了一个接口,那么Spring会自动为其使用 JDK 动态代理,否则使用Cglib。(这里有一个非常有意思效果,如果是 Cglib 代理,那么同时fun1被标识为final,那么就不能被继承了,而Cglib是采用的继承模式,这时候会出现神奇的事,fun1不会被代理,但是fun2却被正确代理了!!!思考这是为什么呢???)

解决方案

这个时候就需要看看 aop 的一个核心配置类 ProxyConfig.class,这个类和Advised一样,都是用于装载代理类的信息,但是前者是针对通用代理配置信息,后者是针对单独某个代理对象配置信息

public class ProxyConfig implements Serializable {

    // 这个参数是用来控制当前是否指定只使用Cglib代理
    private boolean proxyTargetClass = false;

    // 标记是否对代理进行优化。启动优化通常意味着在代理对象被创建后,增强的修改将不会生效,因此默认值为false。
    // 如果exposeProxy设置为true,即使optimize为true也会被忽略。
    private boolean optimize = false;
    
    // 标记是否需要阻止通过该配置创建的代理对象转换为Advised类型,默认值为false,表示代理对象可以被转换为Advised类型
    boolean opaque = false;

    // 标记代理对象是否应该被aop框架通过AopContext以ThreadLocal的形式暴露出去。
    // 当一个代理对象需要调用它自己的另外一个代理方法时,这个属性将非常有用。默认是是false,以避免不必要的拦截。
    boolean exposeProxy = false;

    // 标记该配置是否需要被冻结,如果被冻结,将不可以修改增强的配置。
    // 当我们不希望调用方修改转换成Advised对象之后的代理对象时,这个配置将非常有用。
    private boolean frozen = false;
...
}

我们需要关注的就是 exposeProxy 属性,如果这个属性值 true,那么 Spring 在代理的时候就会将当前这个代理对象放在 ThreadLoacl 中,我们在使用fun1方法的时候,调用fun2就可以改为:

public void fun1(){
    System.out.println("fun1 ...");
    ((OrderService)AopContext.currentProxy()).fun2();
}

点开 AopContext.currentProxy() 对象无非就是从 ThreadLoacl 中获取。需要注意的是,如果采用这种方式,但是没有设置 exposeProxy = true,那么会抛出 IllegalStateException 异常。

你可能感兴趣的:(Spring,Aop)