问题重现

最后我定义了一个注解,心愿能够不便对立的对一些数据库操作做缓存。于是就有了以下代码:

首先,定义一个注解:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Cacheable {    /**     * 策略名称,须要保障惟一     *     * @return     */    public String keyName();    /**     * 超时时长,单位:秒     *     * @return     */    public int expireTime();}复制代码

而后自定义一个切面,对所有应用了该注解的办法进行切面解决:

@Aspect@Componentpublic class StrategyCacheAspect {    private static final Logger LOGGER = LoggerFactory.getLogger(FacadeAspect.class);    @Around("@annotation(com.hollis.cache.StrategyCache)")    public Object cache(ProceedingJoinPoint pjp) throws Throwable {        // 先查缓存,如果缓存中有值,间接返回。如果缓存中没有,先执行办法,再将返回值存储到缓存中。    }}复制代码

而后就能够应用该注解了,应用办法如下:

@Componentpublic class StrategyService extends BaseStrategyService  {    public PricingResponse getFactor(Map<String, String> pricingParams) {        // 做一些参数校验,以及异样捕捉相干的事件        return this.loadFactor(tieredPricingParams);    }    @Override    @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)    private PricingResponse loadFactor(Map<String, String> pricingParams) {        //代码执行    }}复制代码

以上,对loadFactor办法减少了切面,为了方便使用,咱们还定义了一个getFactor办法,设置为public,不便内部调用。

然而,在调试过程中,我发现咱们设置在loadFactor办法下面的切面并没有胜利,无奈执行切面类。

于是开始排查问题具体是什么。

问题排查

为了排查这个问题,首先是把所有的代码查看一遍,看看切面的代码是不是有问题,有没有可能有手误打错了字之类的。

然而发现都没有。于是就想方法找找问题。
《2020最新Java根底精讲视频教程和学习路线!》

接下来我把loadFactor的拜访权限从private改成public,发现没有成果。

而后我尝试着在办法外间接调用loadFactor而不是getFactor。

发现这样做就能够胜利的执行到切面外面了。

发现这一景象的时候,我忽然豁然开朗,直捶大腿。原来如此,原来如此,就应该是这样的。

我忽然就想到了问题的起因。其实起因挺简略的,也是我之前理解到过的原理,然而在问题刚刚产生的时候我并没有想到这里,而是通过debug,发现这个景象之后我才忽然想到这个原理。

那么,就来说说为什么会产生这样的问题。

代理的调用形式

咱们发现下面的问题关键在于loadFactor办法被调用的形式不同。咱们晓得,办法的调用通常有以下几种形式:

1、在类外部,通过this进行自调用:

public class SimplePojo implements Pojo {    public void foo() {        // this next method invocation is a direct call on the 'this' reference        this.bar();    }    public void bar() {        // some logic...    }}复制代码

2、在类内部,通过该类的对象进行调用

public class Main {    public static void main(String[] args) {        Pojo pojo = new SimplePojo();        // this is a direct method call on the 'pojo' reference        pojo.foo();    }}复制代码

类关系及调用过程中如下图:

如果是静态方法,也能够通过类间接调用。

3、在类内部,通过该类的代理对象进行调用:

public class Main {    public static void main(String[] args) {        ProxyFactory factory = new ProxyFactory(new SimplePojo());        factory.addInterface(Pojo.class);        factory.addAdvice(new RetryAdvice());        Pojo pojo = (Pojo) factory.getProxy();        // this is a method call on the proxy!        pojo.foo();    }}复制代码

类关系及调用过程中如下图:

那么,Spring的AOP其实是第三种调用形式,就是通过代理对象调用,只有这种调用形式,才可能在真正的对象的执行前后,可能让代理对象也执行相干代码,能力起到切面的作用。

而对于应用this的形式调用,这种只是自调用,并不会应用代理对象进行调用,也就无奈执行切面类。

问题解决

那么,咱们晓得了,想要真正的执行代理,那么就须要通过代理对象进行调用而不是应用this调用的形式。

那么,这个问题的解决办法也就是想方法通过代理对象来调用指标办法即可。

这种问题的解决网上有很多种方法,这里介绍一个绝对简略的。其余的更多的方法大家能够在网上找到一些案例。搜寻关键词"AOP 自调用"即可。

获取代理对象进行调用

咱们须要批改一下后面的StrategyService的代码,批改成以下内容:

@Componentpublic class StrategyService{    public PricingResponse getFactor(Map<String, String> pricingParams) {        // 做一些参数校验,以及异样捕捉相干的事件        // 这里不应用this.loadFactor而是应用AopContext.currentProxy()调用,目标是解决AOP代理不反对办法自调用的问题        if (AopContext.currentProxy() instanceof StrategyService) {            return ((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams);        } else {            // 局部实现没有被代理过,则间接进行自调用即可            return loadFactor(tieredPricingParams);        }    }    @Override    @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)    private PricingResponse loadFactor(Map<String, String> oricingParams) {        //代码执行    }}复制代码

即应用AopContext.currentProxy()获取到代理对象,而后通过代理对象调用对应的办法。

还有个中央须要留神,以上形式还须要将Aspect的expose-proxy设置成true。如果是配置文件批改:

 <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>复制代码

如果是SpringBoot,则批改利用启动入口类的注解:

@EnableAspectJAutoProxy(exposeProxy = true)public class Application {}复制代码

总结

以上,咱们剖析并解决了一个Spring AOP不反对办法自调用的问题。

AOP失败这个问题,其实还是很重大的,因为如果产生非预期的生效,那么间接问题就是没有执行切面办法,更重大的结果可能是诸如事务未失效、日志未打印、缓存未查问等各种问题。

所以,还是倡议大家看完此文之后,统查一下本人的代码,是否存在办法自调用的状况。这种状况下,任何切面都是无奈失效的!