关于java:深坑啊同一个Spring-AOP的坑我一天踩了两次

2次阅读

共计 3429 个字符,预计需要花费 9 分钟才能阅读完成。

问题重现

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

首先,定义一个注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {

    /**
     * 策略名称,须要保障惟一
     *
     * @return
     */
    public String keyName();

    /**
     * 超时时长,单位:秒
     *
     * @return
     */
    public int expireTime();}
复制代码 

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

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

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

@Component
public 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 的代码,批改成以下内容:

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

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

正文完
 0