关于Spring项目中,AOP注解失效的原因与解决方法
前言
今天尝试使用了Spring中AOP面向切面的编程方法,遇到了一个比较坑的问题。
代码如下:
Time注解类:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) public @interface Time {}
Aspect拦截类
@Aspect @Component public class TimeAspect { @Around("@annotation(com.example.demo.annotations.Time)") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("doAround"); return proceedingJoinPoint.proceed(); } }
Controller类
@Controller public class TestController { @RequestMapping(path = "/index", method = RequestMethod.GET) public String index(){ test(); return "login"; } @Time //在此加了自定义注释 public void test() throws Exception { System.out.println("test"); } }
发现在这样一个例子中test方法上的@Time注解始终处于无效状态(但是如果将注解标注在@RequestMapping的方法上就可以正常通过Aspect拦截),查看编译后的代码,注解仍然存在,没有问题。
接着尝试使用反射调用方法,一样没有效果,并且发现获取不到方法上的注解了。
这就很神奇了,注解哪去了呢。
开始找原因
开始找丢失的注解,首先顺着Spring的源码一层一层的看,看了很久终于发现了一些线索。
首先为什么标注在@RequestMapping
注解上的自定义注解可以正常运行
根据源码显示,SpringBoot项目初始化的时候会扫描带有@Controller
,@Service
等Spring自带的注解,并且会将被@RequestMapping
标注的Method
封装在org.springframework.web.method.HandlerMethod
中,并且统一存放在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry
的Map<T, MappingRegistration<T>> registry
中,这里存储的Method
时能够获取到它的Annotation
的,然后在请求过程中会调用InvokeHandler
这个方法,所以被@RequestMapping
标记的方法上的自定义注解可以被正常拦截。
那么为什么在接着调用test()
方法自定义注解就无效了呢
又顺着Spring源码接着找,发现Spring在启动过程中,会将标注Spring注解的类进行动态代理,使用JDK的Proxy或者CGLib,由于进行了动态代理,这时在启动完成运行的状态下class已经不再是原先的class了,而是使用了被代理的class。
举个例子:
如果你想要通过类X的对象直接调用其中带注解的A方法,此注解是有效的。因为此时,Spring会判断你将要调用的方法上存在AOP注解,那么会使用类X的代理对象调用A方法。
但是假设类X中的A方法会调用带注解的B方法,而你依然想要通过类X对象调用A方法,那么B方法上的注解是无效的。因为此时Spring判断你调用的A并无注解,所以使用的还是原对象而非代理对象。接下来A再调用B时,在原对象内B方法的注解当然无效了。
简而言之:就是如果在一个类中通过A方法调用带注解的B方法就会失败,因为这时使用的是原来的类实例,而不是代理过后的类实例,所以Aspect不会生效。
解决方法
既然知道了原因,那么要解决也就比较简单了。
使用
@Autowired
注解自动装填当前对象,这时获取的是当前对象的代理对象。@Autowired private TestController testController; testController.test();
Spring提供了一个自动获取当前对象的代理对象的工具方法
AopContext.currentProxy()
((TestController)AopContext.currentProxy()).test();