自定义Spring AOP切面使用时导致失效踩坑
一.背景
我司有一个爬虫的项目需要将爬到的数据回推到公司内部系统,原先是直接通过http请求同步回推数据的,因为爬虫项目是部署在公司外部的,请求公司内部系统的时候没有加任何验证,现在需要加上一个校验,当然只是一个简单的加解密的过程,由于各种客观原因并不会搞得特别复杂,简而言之就是在请求的header中添加一个key-vlue.
二.过程
2.1 如何获取到请求头中的header属性呢?
由于公司内部的接口调用都是基于公司的框架操作的,所有的请求都是被框架包装了一层,但是没有暴露获取header的API,因为有专门的一套检验的过程,但是我们又想自己获取到header的属性,本着解决问题的思路,这个时候其实就需要根据源码去看下框架是如果流转的,既然是自己封装的肯定需要拦截到请求,通过看代码发现,框架自定义了一个servlet继承了httpServlet在自定义的servlet中重写了service方法,然后将拦截住的请求存入一个HttpContext上下文中,然后通过存入ThreadLocal中,因为每个线程的本地内存都是不一样的这样每一个请求都被隔离开来,既然存了当然再请求完成后就需要销毁,就这样找到了怎么获取到请求并获取请求中的header属性.
2.2 自定义注解
/**
* @Author jmle
* @Date 2022/6/21 13:37
* @Version 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestAnnotation {
}
2.3 自定义切面
/**
* @Author jmle
* @Date 2022/6/21 13:39
* @Version 1.0
*/
@Aspect
@Component
public class RequestAspect {
@Pointcut("@annotation(requestAnnotation)")
public void requestPointCut(RequestAnnotation requestAnnotation){
}
@Around(value = "requestPointCut(requestAnnotation)")
public void around(ProceedingJoinPoint joinPoint,RequestAnnotation requestAnnotation) throws Throwable {
HttpRequestWrapper request = HttpRequestContext.getInstance().request();
//验证过程省略
System.out.println(request.getHeader("token"));
joinPoint.proceed();
}
}
2.4 模拟场景
抽象基类代码
public abstract class AbstractBaseRequest<TReqeust, TResponse> {
public TResponse handle(TReqeust request) {
return process(request);
}
/**
* 处理方法
*
* @param request
* @return
*/
protected abstract TResponse process(TReqeust request);
}
子类实现
@Component
public class TestRequestService extends AbstractBaseRequest<TestRequest, TestResponse> {
@Override
protected TestResponse process(TestRequest request) {
//业务逻辑处理
}
}
2.5 发现问题
从2.4可以看出,项目中定义了一个抽象的基类,然后每个不一样的请求继承这个基类,比如取消订单,查询订单,各自实现基类的process然后按各自的业务逻辑进行处理,这个时候按照正常的逻辑将RequestAnnotation这个注解加入到TestRequestService类的process方法中就可以实现代码的检验逻辑拦截,但是实际上是没有生效的,当时真的是百思不得其解,只能根据’子类继承父类并实现父类方法导致Spring AOP切面失效’关键字去Google,但是查出来的答案却不是想要的.
2.6 猜想
找不到想要的答案就只能根据自己的经验进行猜想了,这个时候需要做的就是验证切面是否生效,此时我将注解加到基类的handle方法中,发现是可以拦截到的,这就间接的证明了AOP是有效的,只是用的地方不对导致AOP失效了,这个时候换了一个思路查询谷歌,那就用’导致Spring AOP失效的几种原因’从搜索到答案中,找到了一条this关键字调用内部方法导致Spring AOP失效感觉有点像.
2.7 验证猜想
既然找到了this关键字调用内部方法导致Spring AOP失效的这条答案那就要结合所学知识进行佐证,从代码结构中可以看出,子类继承父类并实现了父类的方法,并且在父类中调用子类的方法满足this调用,因为子类也拥有父类的handle方法,所以是满足this调用的这种场景的。
2.8 this关键字为什么会导致AOP失效呢?
this代表的是当前对象,使用当前对象调用内部方法是不会走代理的,因为AOP拦截的方法是被代理的对象所调用,其实导致AOP失效的场景有很多种,之前记得背面试题的时候背过,但当真正的遇到的时候可能一时不知道怎么分析.
2.9 解决方案
其实网上解决的方案比较多,第一种就是将本类的对象注入本类中,然后通过Srping的对象调用process方法就能绕开this调用,第二种就从ApplicationContext.getBean()获取到子类对象,然后再进行调用,其实原因都一样,都是获取到代理对象然后绕开this调用,当然还有第三种方案,网上有很多答案,我最终这三种都没有用,因为不符合实际情况,最终采用的是将注解加在最顶层的入口,这样就完美避开了this调用了.
三.总结
很多时候真的经验很重要,单纯的背过知识点,却没有遇到真实的场景感受是不一样的,当你真的遇到场景并理解为什么会这样,可能对你的帮助会更大,脚踏实地,一步一步走才是当下最应该做的事。