背景
最近在做一个小的工程项目,通过使用 AOP 和自定义注解的方式实现了操作日志的记录,并单独封装以供其他模块的调用。
如上所示 itf.service
定义 api 接口规范,然后 controller
实现所接口并完善方法;所以最初为了代码上的好看,将自定义的注解直接标注在了 itf.service
层的方法上。然而实际操作发现,这样的注解并不会被扫描到,并未发挥日志记录的作用。
这是为什么呢?
首先在说之前,我们先聊一下注解的继承问题。
注解的继承问题
对于接口,在接口中的注解无论如何都不能被继承,不论是子接口继承父接口的情况还是接口的实现类的情况,不论是对接口上还是接口中的方法上的注解,都不能被继承。以上经过测试所得。
在说注解继承时,我们先说下注解里一个叫 @Inherited
的元注解。
如果一个类用上了 @Inherited
修饰的注解,那么其子类也会继承这个注解,与方法上的注解的继承性无关。
需要注意的是:
-
接口用上个
@Inherited
修饰的注解,其实现类不会继承这个注解 -
父类的方法用了
@Inherited
修饰的注解,子类也不会继承这个注解
对于第二条为什么呢?其实当我们把注解用在方法中,就没有没有所谓继承问题,只有方法的重写问题。
即对于类中方法上的注解,若子类重写了父类带注解方法,从子类无法获取到注解。
子类没有重写父类带注解方法,可以通过子类获得注解。这个与注解是否被标注 @Inherited
无关。
那为啥注解在接口上没作用?
因为我们使用了 AOP 特性,与之相关联的便是 Spring 动态代理 了。Spring 的动态代理主要分为两种,一种是JDK 动态代理 ;一种是CGLIB 动态代理
-
使用 JDK 动态代理
JDK 动态代理主要是针对实现了某个接口的类。该方式基于反射的机制实现,会生成一个实现相同接口的代理类,然后通过对方法的充写,实现对代码的增强。
在该方式中接口中的注解无法被实现类继承,AOP 中的切点无法匹配上实现类,所以也就不会为实现类创建代理,所以我们使用的类其实是未被代理的原始类,自然也就不会被增强了。
-
使用 CGLIB 动态代理
在不存在切点注解继承的情况,AOP 可进行有效拦截(CGLIB 动态代理)。但是还要考虑以下存在注解继承的情况:
有父类
Parent
,和子类Sub
,切点注解在父类方法。则根据上边提到的只有方法的重写问题,可知,被重写的方法将不会被拦截,而未重写的方法则走Parent
路线,可以被 AOP 感知拦截。
以上内容属于个人理解,欢迎大家点击“阅读原文”讨论指正~