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

解决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,非 AOPcglib 代理对象调用,方法B自然就不会进入到切面方法了。

解决方案

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

注:需要开启exposeProxy=true的配置,Springboot项目中,可以在启动类上面添加 
@EnableAspectJAutoProxy(exposeProxy = true)注解。
@Service
@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

问题完美解决!!!

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值