组合注解
注解的作用就不用介绍了吧,主要就是用来简化配置,通过自定义注解或者其他框架提供的注解,只要往方法或者类上一加,就可以实现许多神奇的功能。
spring 4.2之后就提供了组合注解的实现方式,啥是组合注解呢,其实就是将多个注解作用于一个注解,用一个注解就可以来实现那多个注解的功能,使作用的元素(即方法或类等)看上去更简洁美观,当然主要还是更强大的属性覆盖功能。
举个最常见的组合注解吧,即spring的@RestController,它将@ResponseBody和@Controller两个注解组合为一个,我们在Controller类上只要加@RestController即可实现之前要加两个注解才能实现的功能。代码如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller //组合Controller使其实现Bean注册
@ResponseBody //组合ResponseBody使其支持将结果转化为json
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
自定义注解(不使用Spring自带的注解)实现组合注解功能
自定义注解(即自己要实现某些功能但不使用spring提供的一些注解,并非网上大部分那种将spring提供的多个注解作用于一个自己定义的注解然后将该注解作用于元素,让其实现Spring提供的组合功能)时,想使用spring那神奇般的组合注解该如何实现呢。接下来就介绍spring神奇的工具类AnnotatedElementUtils
,使用它就可以让自己的注解实现组合注解般的功能。直接上代码:
/**
* @author meilin.huang
* @version 1.0
* @date 2019-06-06 21:11
*/
public class SynthesizedAnnotationTest {
@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@interface Test1 {
String test1() default "test1";
}
@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@interface Test2 {
String test2() default "test2";
}
@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@Test2
@interface Test3 {
String tset3() default "test3";
}
/**
* 只有@Test3注解,但是Test3注解上组合了@Test2注解,故就可以通过Spring的工具类获取到Test2注解的内容,详见main方法
* 当然也可以将组合注解作用于更高层次,如Test3组合Test2,Test2组合Test1,然后将Test3作用于元素,通过工具类获取Test1注解功能
*/
@Test3
static class Element {}
public static void main(String[] args) {
Test2 test2 = AnnotatedElementUtils.getMergedAnnotation(Element.class, Test2.class);
System.out.println(test2);// 输出'@mayfly.sys.common.utils.SynthesizedAnnotationTest$Test2(test2=test2)'
}
}
组合注解实现属性值覆盖(有点类似子类覆盖父类属性方法)
如果spring只实现了以上那功能,其实作用也不太大,并且实现也就很简单了,自己便可轻易实现,通过直接查找元素上是否含有该直接注解,没有则遍历该元素其他注解,然后递归遍历查找注解的元注解是否含有该元素,直到找到返回即可。
接下来就介绍下spring组合注解更强大的属性覆盖功能,即更低层次的注解属性方法覆盖高层次注解的属性方法,啥意思呢,具体见代码就比较清晰明了了,实现该功能还需要spring提供的另外一个注解即@AliasFor
配合完成。
/**
* @author meilin.huang
* @version 1.0
* @date 2019-06-06 21:11
*/
public class SynthesizedAnnotationTest {
@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@interface Test1 {
String test1() default "test1";
}
@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@interface Test2 {
String test2() default "test2";
}
@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@Test2
@interface Test3 {
/**
* AliasFor注解用来表示要覆盖Test2注解中的test2()属性方法,
* annotation属性声明的注解类必须存在于该注解的元注解上
* attribute属性声明的值必须存在于Test2注解属性方法中(即Test2注解的test2方法)
*/
@AliasFor(annotation = Test2.class, attribute = "test2")
String test3() default "test3";
}
/**
* 只有@Test3注解,但是Test3注解上组合了@Test2注解,并将该注解的test3方法值用来覆盖Test2注解中的test2方法
* 即更低层次声明的覆盖规则,会覆盖更高层次的属性方法值,即调用高层次的注解方法值实际显示的是低层所赋的值
* 当然也可以将组合注解作用于更高层次,如Test3组合Test2,Test2组合Test1,然后将Test3作用于元素,通过工具类获取Test1注解覆盖的属性值
*/
@Test3(test3 = "覆盖Test2属性中的test2方法")
static class Element {}
public static void main(String[] args) {
Test2 test2 = AnnotatedElementUtils.getMergedAnnotation(Element.class, Test2.class);
// 虽然调用了Test2注解的test2方法,但是实际显示的是Test3注解中的test3属性声明的值
// 则说明Test2的test2属性被覆盖了
System.out.println(test2.test2());// out '覆盖Test2属性中的test2方法'
}
}
以上就是属性覆盖的最简单两层覆盖,当然原则上是可以支持无限层覆盖的,但是用法都是一致的。实现该功能的主要原理其实就是通过jdk的动态代理。具体实现方式有兴趣的可以参考AnnotatedElementUtils
工具类的实现细节。
那么组合注解有啥用呢,其实个人感觉用处是非常大的可以不改变原注解的代码,就可以定义新注解,并通过覆盖原则来覆盖原注解的一些属性值来实现更多的其他功能扩展,也不会影响原注解的使用。Spring的大量注解都使用这些原则,随便翻翻源码注解随处可见的都是这类组合注解。当然更多的用处还是需要根据自己的需求自己发挥啦,哈哈哈。
我参考了spring该工具类的源码,大大简化了其实现方式(毕竟spring考虑的比较周到,代码比较复杂),但是在能实现其最基本功能的原则上个人的实现感觉也是够用了,且性能是spring的两倍。具体代码在本人的项目[ https://gitee.com/objs/mayfly ]的AnnotationUtils里,有兴趣的可以参考参考该实现方式蛤,多提宝贵意见~~~
网友评论