当前位置: 首页 > 科技观察

一个SpringAOP的坑!很多人都犯了!

时间:2023-03-13 22:07:43 科技观察

前几天刚发了一篇文章《自定义注解!绝对是程序员装逼的利器!!》,介绍了如何使用SpringAOP+自定义注解来提高代码的优雅度。很多读者看了之后都说用起来很爽,但是后台有人留言说配置了Spring的AOP之后发现切面没有生效。其实我在使用的过程中也遇到过这个问题,一天遇到两次同样的问题。说明这个问题很容易被忽视,而且这个问题的后果可能极其严重。那么,让我们简单回顾一下问题是什么。问题复现最开始我定义了一个注解,希望方便统一缓存一些数据库操作。于是就有了如下代码:首先定义一个注解:@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceCacheable{/***策略名需要唯一*@return*/publicStringkeyName();/***超时时间,单位:秒*@return*/publicintexpireTime();}然后自定义一个切面来处理所有使用这个注解的方法:@Aspect@ComponentpublicclassStrategyCacheAspect{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(FacadeAspect.class);@Around("@annotation(com.hollis.cache.StrategyCache)")publicObjectcache(ProceedingJoinPointpjp)throwsThrowable{//先检查缓存,如果缓存中有值,直接返回。如果不在缓存中,则先执行该方法,然后将返回值存入缓存中。}}然后就可以使用这个注解了,如下:@ComponentpublicclassStrategyServiceextendsBaseStrategyService{publicPricingResponsegetFactor(MappricingParams){//做一些参数校验,和异常捕获相关的东西returnthis.loadFactor(tieredPricingParams);}@Override@StrategyCache(keyName="key0001",expireTime=60*60*2)privatePricingResponseloadFactor(MappricingParams){//代码执行}}上面,我们在loadFactor方法中添加了一个方面。为了方便使用,我们还定义了一个getFactor方法,设置为public供外部调用。但是在调试过程中,发现我们在loadFactor方法上设置的切面没有成功,切面类无法执行。所以我开始调查问题出在哪里。故障排查为了排查这个问题,首先要检查所有的代码,看切面的代码有没有问题,有没有可能是错别字。但是没有找到。所以我想办法找到问题所在。接下来我把loadFactor的访问权限从private改成了public,发现没有任何效果。然后我尝试直接在方法外调用loadFactor而不是getFactor。发现这样做,可以成功实现到切面中。当我发现这个现象时,我恍然大悟,捶了自己的大腿。就是这样,就应该这样。我突然想到了问题的原因。其实道理很简单,也是我之前学过的原理,只是刚出问题的时候没有想到这个,而是通过调试,发现这个现象后突然想到了这个原理。那么,让我们来谈谈为什么会出现这个问题。代理的调用方式我们发现,上述问题的关键在于loadFactor方法的调用方式。我们知道调用一个方法通常有以下几种方式:1.在类内部,通过this自调用:publicclassSimplePojoimplementsPojo{publicvoidfoo(){//thisnextmethodinvocationisadirectcallonthe'this'referencethis.bar();}publicvoidbar(){//somelogic...}}2.在类外,通过this的对象调用publicclassMain{publicstaticvoidmain(String[]args){Pojopojo=newSimplePojo();//thisisadirectmethodcallonthe'pojo'referencepojo.foo();}}类class的关系和调用过程如下图所示:如果是静态方法,也可以直接通过类调用。3、类外,通过本类的代理对象调用:publicclassMain{publicstaticvoidmain(String[]args){ProxyFactoryfactory=newProxyFactory(newSimplePojo());factory.addInterface(Pojo.class);factory.addAdvice(newRetryAdvice()));Pojopojo=(Pojo)factory.getProxy();//thisisamethodcallontheproxy!pojo.foo();}}类关系和调用过程如下:那么,Spring的AOP其实就是第三种调用方式,也就是通过代理对象调用,只有这种调用方式才能使代理对象在真实对象执行前后执行相关代码,从而起到横切的作用。至于用this调用,this只是自调用,不会用代理对象调用,所以切面类是执行不了的。问题解决那么,我们知道,如果要真正执行代理,需要通过代理对象来调用,而不是使用这个调用。那么,这个问题的解决办法就是想办法通过代理对象来调用目标方法。网上有很多解决这个问题的方法,这里介绍一种比较简单的。更多其他方法大家可以在网上找一些案例。只需搜索关键字“AOPself-invocation”即可。要让代理对象调用,我们需要将之前的StrategyService代码修改为如下内容:@ComponentpublicclassStrategyService{publicPricingResponsegetFactor(MappricingParams){//做一些参数校验和异常捕获相关的事情//这个。这里没有使用loadFactor而是调用AopContext.currentProxy()来解决AOP代理不支持方法自调用if(AopContext.currentProxy()instanceofStrategyService){return((StrategyService)AopContext.currentProxy())的问题loadFactor(tieredPricingParams);}else{//部分实现没有代理,直接调用returnloadFactor(tieredPricingParams);}}@Override@StrategyCache(keyName="key0001",expireTime=60*60*2)privatePricingResponseloadFactor(MaporicingParams){//代码执行}}即使用AopContext.currentProxy()获取代理对象,然后通过代理对象调用相应的方法。还有一个地方需要注意。上述方法还需要将Aspect的expose-proxy设置为true。如果是配置文件修改:如果是SpringBoot,修改应用启动入口类的注解:@EnableAspectJAutoProxy(exposeProxy=true)publicclassApplication{}综上所述,我们分析解决了一个SpringAOP不支持方法自调用的问题。AOP失败的问题其实很严重,因为如果出现意外失败,直接的问题就是切面方法没有执行。更严重的后果可能是事务不生效、日志不打印、缓存不查询等各种问题。.因此,建议大家看完本文后检查一下自己的代码,看看是否有方法自调用。在这种情况下,任何部分都将无效!【本文为专栏作家霍利斯原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读作者更多好文