目录

spring注解驱动之组合注解

在Java中实现组合注解

查看自动装配的几个注解@Controller、@Service、@Repository这几个注解都继承了@Component注解,以@Service为例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

spring在启动时进行类扫描的时候,也是扫描候选类上是否有携带@Component注解,有携带的话标记为候选资源,否则的话不进行扫描;

但是,我们的原生java是不支持直接获取当前类的所实现的注解的注解的;也就是说,我使用注解A标记了注解B,然后将注解B添加到类或者是方法上后,通过反射获取到当前类,无法直接获取到当前类是否有被注解A所标记;示例程序如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.eh.cloud2020.config;

import javax.annotation.*;
import java.lang.annotation.*;
import java.util.Arrays;

public class TestMain {


    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyComponent {
        String a1() default "a1";
    }

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @MyComponent
    @interface MyService {
        String a2() default "a2";
    }

    @MyService
    class Test {
    }

    public static void main(String[] args) {
        Annotation[] annotations = Test.class.getAnnotations();
        Arrays.stream(annotations).forEach(a -> System.out.println(a.annotationType()));
    }
}

输出

1
interface com.eh.cloud2020.config.TestMain$MyService

分析

获取Test的注解使用getAnnotations方法,只能获取Test上标记的注解以及Test父类上标注的注解,但是不能获取这些注解上标记的注解,那么我们可以通过遍历的方式,先获取注解实例Annotation对象(动态代理对象),再获取这个代理对象上的注解即可实现注解继承,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.eh.cloud2020.config;

import javax.annotation.*;
import java.lang.annotation.*;
import java.util.Arrays;

public class TestMain {


    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyComponent {
        String a1() default "a1";
    }

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @MyComponent
    @interface MyService {
        String a2() default "a2";
    }

    @MyService
    class Test {
    }

    public static void main(String[] args) {
        iterateAnnotations(Test.class);
    }

    public static void iterateAnnotations(Class<?> clazz) {
        Annotation[] annotations = clazz.getAnnotations();
        Arrays.stream(annotations).forEach(a -> {
            // interface java.lang.annotation.Documented 等存在循环,导致内存溢出,所以需要排除java的源注解
            if (a.annotationType() != Deprecated.class &&
                    a.annotationType() != SuppressWarnings.class &&
                    a.annotationType() != Override.class &&
                    a.annotationType() != PostConstruct.class &&
                    a.annotationType() != PreDestroy.class &&
                    a.annotationType() != Resource.class &&
                    a.annotationType() != Resources.class &&
                    a.annotationType() != Generated.class &&
                    a.annotationType() != Target.class &&
                    a.annotationType() != Retention.class &&
                    a.annotationType() != Documented.class &&
                    a.annotationType() != Inherited.class) {
                System.out.println(a.annotationType());
                iterateAnnotations(a.annotationType());
            }
        });
    }
}

输出:

1
2
interface com.eh.cloud2020.config.TestMain$MyService
interface com.eh.cloud2020.config.TestMain$MyComponent

通过上面的分析我们获取了注解继承的注解,在spring中其实也是采用了类似的机制实现注解的扫描,spring还提供了一些工具类eg: AnnotatedElementUtils.getMergedAnnotation和AnnotationUtils.findAnnotation来处理组合组合注解。

组合注解实现属性值覆盖

类似子类覆盖父类属性方法

如果spring只实现了以上那功能,其实作用也不太大,并且实现也就很简单了,自己便可轻易实现,通过直接查找元素上是否含有该直接注解,没有则遍历该元素其他注解,然后递归遍历查找注解的元注解是否含有该元素,直到找到返回即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.eh.cloud2020.config;

import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class TestMain {


    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface A {
        String a1() default "a1";

        String a2() default "a2";
    }

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @A
    @interface B {
        String a1() default "b1";
    }

    @B
    class Test {
    }

    public static void main(String[] args) {
        A a = AnnotatedElementUtils.getMergedAnnotation(Test.class, A.class);
        System.out.println(a.a1());
        System.out.println(a.a2());
    }
}

毫无疑问,输出

1
2
a1
a2

接下来就介绍下spring组合注解更强大的属性覆盖功能,即更低层次的注解属性方法覆盖高层次注解的属性方法,啥意思呢,具体见代码就比较清晰明了了

将Test上的注解@A编程@B也即使用组合注解,并且使用一个相同的方法a1,那么B的a1会覆盖A的a1,也即A的a1值变成了a2,代码示例如下:

1
2
3
@B
class Test {
}

输出:

1
2
b1
a2

如果想使用别名实现注解覆盖还需spring提供的另外一个注解即@AliasFor配合完成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.eh.cloud2020.config;

import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class TestMain {


    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface A {
        String a1() default "a1";

        String a2() default "a2";
    }

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @A
    @interface B {
        String a1() default "b1";

        @AliasFor(annotation = A.class, attribute = "a2")
        String b2() default "b2";
    }

    @B
    class Test {
    }

    public static void main(String[] args) {
        A a = AnnotatedElementUtils.getMergedAnnotation(Test.class, A.class);
        System.out.println(a.a1());
        System.out.println(a.a2());
    }
}

输出

1
2
b1
b2

之后想使用属性覆盖就可以这样做:

1
2
3
    @B(a1 = "test1", b2 = "test2")
    class Test {
    }

以上是属性覆盖的最简单两层覆盖,原则上可以支持无限层覆盖,用法都是一致的。实现该功能的主要原理其实就是通过jdk的动态代理。在实例化代理对象的时候往代理对象的AnnotationInvokeHandler的成员变量memberValues中存放属性名和属性值,属性名和属性值是存放在标注注解的类例如Test的常量池中。具体实现方式有兴趣的可以参考AnnotatedElementUtils工具类的实现细节。

那么组合注解有啥用呢,其实个人感觉用处是非常大的可以不改变原注解的代码,就可以定义新注解,并通过覆盖原则来覆盖原注解的一些属性值来实现更多的其他功能扩展,也不会影响原注解的使用。Spring的大量注解都使用这些原则,随便翻翻源码注解随处可见的都是这类组合注解。

以上就是属性覆盖的内容,小结一下

spring中大量使用了组合注解,如果需要属性覆盖有以下两种方法

  1. 低层级注解使用相同的属性名进行覆盖
  2. 低层级使用@AliasFor取属性别名进行覆盖