解决AOP切面在嵌套方法调用时不生效问题

一、背景

在使用AOP切面编程中,通常会遇到一个方法嵌套调用,导致AOP不生效的问题。
如下面所说明的:
在一个实现类中,有2个方法,方法A,方法B,其中方法B上面有个注解切面,当方法B被外部调用的时候,会进入切面方法。
但当方法B是被方法A调用时,并不能从方法B的注解上,进入到切面方法,即我们经常碰到的方法嵌套时,AOP注解不生效的问题。

二、问题描述

场景1:外部调用AOP方法正常进入

通过外部,调用方法B,可以正常进入切面方法,这个场景的代码如下:
注解类:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoAnno {

}

切面类:

@Aspect
@Order(-1)
@Component
public class DemoAspect {

    @Before("@annotation(da)")
    public void beforDoSomething(JoinPoint point, DemoAnno da) throws Exception {
        System.out.println("before method B, print 'hello,world' " );
    }
}

接口类:

public interface DemoService {
    void methodDemoA();

    void methodDemoB();
}

服务实现类:

@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public void methodDemoA(){
        System.out.println("this is method A");
    }

    @Override
    @DemoAnno
    public void methodDemoB() {
        System.out.println("this is method B");
    }
}

测试方法:

	@Autowired
    DemoService demoService;
    @Test
    public void testMethod(){
        demoService.methodDemoA();
        demoService.methodDemoB();
    }

输出结果:

this is method A
before method B, print 'hello,world' 
this is method B

在这个场景测试过程中,我们通过在单元测试类中,直接调用方法B,可以正常进入切面中;

场景2:方法嵌套调用,AOP不生效

上面的代码,做下修改。在DemoServiceImpl实现类中,通过方法A去调用方法B,然后再单元测试类中,调用方法A。代码修改后如下:
服务实现类:

@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public void methodDemoA(){
        System.out.println("this is method A");
        methodDemoB();
    }

    @Override
    @DemoAnno
    public void methodDemoB() {
        System.out.println("this is method B");
    }
}

单元测试类:

	@Autowired
    DemoService demoService;
    @Test
    public void testMethod(){
        demoService.methodDemoA();
        //demoService.methodDemoB();
    }

通过修改调用方式后,打印的结果如下:

this is method A
this is method B

结果显示,方法B上面的切面,并未生效,没有进入到切面方法中。

原因分析

场景1中,通过外部调用方法B,是由于spring在启动时,根据切面类及注解,生成了DemoService的代理类,在调用方法B时,实际上是代理类先对目标方法进行了业务增强处理(执行切面类中的业务逻辑),然后再调用方法B本身。所以场景1可以正常进入切面方法;
下图断点时可以看到demoService对象,是一个cglib的代理对象。
在这里插入图片描述
场景2中,通过外部调用的是方法A,虽然spring也会创建一个cglib的代理类去调用方法A,但当方法A调用方法B的时候,属于类里面的内部调用,使用的是实例对象本身去去调用方法B,非aop的cglib代理对象调用,方法B自然就不会进入到切面方法了。

三、解决方案

对于场景2,我们在业务开发过程中经常会碰到,但我们期望的是,方法A在调用方法B的时候,仍然能够进入切面方法,即需要AOP切面生效。
这种情况下,我们在调用方法B的时候,需要使用AopContext.currentProxy()获取当前的代理对象,然后使用代理对象调用方法B。

注:需要开启exposeProxy=true的配置,springboot项目中,可以在启动类上面,添加 @EnableAspectJAutoProxy(exposeProxy = true)注解。

@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public void methodDemoA(){
        System.out.println("this is method A");
        DemoService service = (DemoService) AopContext.currentProxy();
        service.methodDemoB();
    }

    @Override
    @DemoAnno
    public void methodDemoB() {
        System.out.println("this is method B");
    }
}

改造后,再运行单元测试类:

	@Autowired
    DemoService demoService;
    @Test
    public void testMethod(){
        demoService.methodDemoA();
        //demoService.methodDemoB();
    }

打印结果如下:

this is method A
before method B, print 'hello,world' 
this is method B

问题完美解决!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值