文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

为什么说在SpringAOP中,不要使用This调用方法?

2024-12-01 01:28

关注

SpringAOP是Spring中除了依赖注入以外最为核心的功能,其原理是利用CGlib和JDK动态代理等方式来实现运行期动态方法增强,从而降低系统耦合,提升代码的复用性。

不过,在享受AOP强大功能便利的同时,我们也会经常遇到一些看起来莫名其妙的bug。

今天,我们来聊一聊,为什么说在AOP方法中,不要轻易使用this调用方法?

使用了this会出现什么样的情况?背后的原理是什么?又该如何解决?​

废话不多说,直接实战上代码。

场景复现

假设我们有一个核心支付类,其中有pay()支付功能,同时会通过record()方法记录都有哪些用户访问过这个核心功能。

@Service
public class PayService {

public void pay(){
System.out.println("执行一些核心支付业务操作");
//记录用户访问日志
this.record();
}

public void record(){
try {
System.out.println("模拟将操作记录投递到日志系统,耗时100ms");
TimeUnit.MICROSECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

随着业务的不断扩大,我们需要统计一下保存访问日志这个动作的耗时情况,看看是否会对核心支付功能有较大的影响。

所以,我们使用SpringAOP进行了切面处理。

@Aspect
@Component
public class LogAspect {

@Around(value = "execution(* com.shishan.demo2023.service.PayService.record()) )")
public Object record(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object proceed = proceedingJoinPoint.proceed();
System.out.println("记录日志耗时:" + (System.currentTimeMillis() - begin));
return proceed;
}
}

切面类很简单,通过@Around方法对record方法进行切入切出,并记录该方法的执行时间。

​看起来很完美是不是?

老代码不用改动,只需新增一个切面就可以实现新的需求。

我们新建一个controller,看看我们的切面类有没有生效。

@RestController
@RequestMapping(value = "/demo")
public class DemoController {

@Resource
private PayService payService;

@RequestMapping(value = "/pay")
public ResponseEntity<Object> pay(){
this.payService.pay();
return ResponseEntity.ok().build();
}
}

启动服务,访问
http://localhost:8080/demo/pay。

问题出现了。

按照上面的代码,在打印完业务日志之后,应该打印一行记录日志耗时的日志。然而控制台却空空如也,说明我们的切面类并没有生效。

​为什么定义的切面没有执行呢?

问题就出现在pay()方法中的this调用上。​

我们可以看到,图中的this指向的是一个普通的PayService对象,而不是被Spring增强后的bean。

而SpringAOP起作用的原理是什么:Spring通过JDK动态代理和CGlib代理对目标类生成一个代理类,在代理类中做功能增强。

我们看一下在controller中的PayService:

可以看到,在controller中的payService是一个被SpringCLlib增强后的代理类,而我们通过this引用到的,对于Spring来说只是一个普通的bean对象,自然无法实现AOP的功能。

那Spring在什么时候会对一个对象进行代理呢?

Spring会在一个bean创建的时候判断是否要进行代理,核心类是
AnnotationAwareAspectJAutoProxyCreator,其本质是一个BeanPostProcessor。当需要使用到AOP时,它会把创建的原始的Bean对象wrap成代理对象作为Bean返回。

所以,最终结论是:只有被动态代理出来的对象,才可以被Spring增强,具备AOP的能力。

解决办法

既然问题找到了,那么如何解决因为this调用带来的AOP失效的问题呢?

有两种办法。

一,自己引用自己

直接在当前类中注入自己,这样Spring会对类中的属性进行代理,生成一个payService代理类。

需要注意的是,这样其实是人为的制造了循环依赖。在高版本的Springboot中,循环依赖是默认关闭的。如果想开启循环依赖,需要配置
spring.main.allow-circular-references=true。

二,通过AopContext

AopContext内部维护了一个保存proxy的ThreadLocal,简单说就是通过一个ThreadLocal将proxy和当前线程绑定起来,这样就可以随时拿出当前线程绑定的 Proxy。

如果使用这样的方式,需要在@EnableAspectJAutoProxy 里加一个配置项 exposeProxy = true。

通过方式一修改下代码,看看AOP是否生效。

可以看到,成功的打印出来日志耗时的log。

总结

SpringAOP实际上会自动为我们创建一个Proxy,使得调用者能无感知地调用指定方法,本质上就是一个动态代理。我们只有访问这些代理对象的方法,才能获得AOP实现的功能,所以通过this引用是无法去正确使用 AOP 功能的。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯