自定义注解并且进行扫描解析

[toc]

注解背景

首先我们要知道背景知识:

  1. 每一个注解其实就是一个特殊的接口(带着@符号,其实是语法糖,会被编译器自动编译成继承自Annotation接口)。我们反编译一下class文件就能看出来。
  2. 注解只是一个标记位,标记了某一个类,某一个字段或者某一个函数之后,我们就可以对被标记的属性进行我们期望的行为——比如运行时动态获取和修改被标记的属性,动态执行被标记的函数等等
  3. 基于第二点,我们在定义了自己的注解之后,还要定义自己注解的解析类,这样我们才能真正让注解发挥起作用(只标记而不做任何动作就和没标记没任何区别了)

源码解析

废话不说上我自己定义的代码,然后一个一个说明。

 1package com.springtest.demo.annotation;
 2
 3import java.lang.annotation.Documented;
 4import java.lang.annotation.ElementType;
 5import java.lang.annotation.Retention;
 6import java.lang.annotation.RetentionPolicy;
 7import java.lang.annotation.Target;
 8
 9@Target(ElementType.FIELD)
10@Retention(RetentionPolicy.RUNTIME)
11@Documented
12public @interface Fruit {
13    String value() default "";
14
15    String name() default "";
16
17    enum FruitType {
18        APPLE, PEACH, PEAR, WATERMELON
19    }
20
21    FruitType type() default FruitType.APPLE;
22}
 1package com.springtest.demo.entity.fruit;
 2
 3import com.springtest.demo.annotation.Fruit;
 4import com.springtest.demo.annotation.Scope;
 5
 6@Scope(Scope.SCOPE_PROTOTYPE)
 7public class Pear {
 8    @Fruit(value = "pear")
 9    private String name;
10
11    @Fruit(type = Fruit.FruitType.PEAR)
12    private String type;
13}
 1
 2package com.springtest.demo.entity.fruit;
 3
 4import com.springtest.demo.annotation.Fruit;
 5import com.springtest.demo.annotation.Scope;
 6
 7@Scope(Scope.SCOPE_PROTOTYPE)
 8public class Apple {
 9    @Fruit(value = "apple")
10    private String name;
11
12    @Fruit(type = Fruit.FruitType.APPLE)
13    private String type;
14}

以上就是我做的最简单的demo,定义并应用了一个注解。我们来看看Fruit注解反编译的结果是什么就能大概知道这故事背后的作用。

 1public interface Fruit
 2    extends Annotation
 3{
 4    public static final class FruitType extends Enum
 5    {
 6
 7        public static final FruitType APPLE;
 8        public static final FruitType PEACH;
 9        public static final FruitType PEAR;
10        public static final FruitType WATERMELON;
11        private static final FruitType $VALUES[];
12
13        public static FruitType[] values()
14        {
15            return (FruitType[])$VALUES.clone();
16        }
17
18        public static FruitType valueOf(String name)
19        {
20            return (FruitType)Enum.valueOf(com/springtest/demo/annotation/Fruit$FruitType, name);
21        }
22
23        static 
24        {
25            APPLE = new FruitType("APPLE", 0);
26            PEACH = new FruitType("PEACH", 1);
27            PEAR = new FruitType("PEAR", 2);
28            WATERMELON = new FruitType("WATERMELON", 3);
29            $VALUES = (new FruitType[] {
30                APPLE, PEACH, PEAR, WATERMELON
31            });
32        }
33
34        private FruitType(String s, int i)
35        {
36            super(s, i);
37        }
38    }
39
40
41    public abstract String value();
42
43    public abstract String name();
44
45    public abstract FruitType type();
46}

查看代码我们发现所谓注解的本质还是很简单的其实就是一个继承了Annotation的接口,然后内部定义了一些默认的抽象类而已。

Retention注解

由于因为我们知道其实一个注解本质上就只是一个标记,这个标记要怎么使用,什么时候使用是我们的编译器和jvm决定的,也就意味着一个注解通常会有一个目的,或者我们叫作用域。通常分为三类:

  1. 仅在编码时生效 @RetentionPolicy .SOURCE
  2. 仅在编译时生效 @RetentionPolicy .CLASS
  3. 仅在运行时生效 @RetentionPolicy .RUNTIME
 1@Documented
 2@Retention(RetentionPolicy.RUNTIME)
 3@Target(ElementType.ANNOTATION_TYPE)
 4public @interface Retention {
 5    /**
 6     * Returns the retention policy.
 7     * @return the retention policy
 8     */
 9    RetentionPolicy value();
10}
11
12/**
13 * Annotation retention policy.  The constants of this enumerated type
14 * describe the various policies for retaining annotations.  They are used
15 * in conjunction with the {@link Retention} meta-annotation type to specify
16 * how long annotations are to be retained.
17 *
18 * @author  Joshua Bloch
19 * @since 1.5
20 */
21public enum RetentionPolicy {
22    /**
23     * Annotations are to be discarded by the compiler.
24     */
25    SOURCE,
26
27    /**
28     * Annotations are to be recorded in the class file by the compiler
29     * but need not be retained by the VM at run time.  This is the default
30     * behavior.
31     */
32    CLASS,
33
34    /**
35     * Annotations are to be recorded in the class file by the compiler and
36     * retained by the VM at run time, so they may be read reflectively.
37     *
38     * @see java.lang.reflect.AnnotatedElement
39     */
40    RUNTIME
41}

Target 注解

同时由于我们的注解是可能被写在各种地方的,因此我们需要定义我们这个参数的作用域。

 1@Documented
 2@Retention(RetentionPolicy.RUNTIME)
 3@Target(ElementType.ANNOTATION_TYPE)
 4public @interface Target {
 5    /**
 6     * Returns an array of the kinds of elements an annotation type
 7     * can be applied to.
 8     * @return an array of the kinds of elements an annotation type
 9     * can be applied to
10     */
11    ElementType[] value();
12}
13
14public enum ElementType {
15    /** Class, interface (including annotation type), or enum declaration */
16    TYPE,
17
18    /** Field declaration (includes enum constants) */
19    FIELD,
20
21    /** Method declaration */
22    METHOD,
23
24    /** Formal parameter declaration */
25    PARAMETER,
26
27    /** Constructor declaration */
28    CONSTRUCTOR,
29
30    /** Local variable declaration */
31    LOCAL_VARIABLE,
32
33    /** Annotation type declaration */
34    ANNOTATION_TYPE,
35
36    /** Package declaration */
37    PACKAGE,
38
39    /**
40     * Type parameter declaration
41     *
42     * @since 1.8
43     */
44    TYPE_PARAMETER,
45
46    /**
47     * Use of a type
48     *
49     * @since 1.8
50     */
51    TYPE_USE
52}

Documented注解

最后就是一个是否需要被Javadoc记录的标记位@Documented。

小结

基于遇上三点,也就是为什么我们常见的注解,头上都会有这三个标记的原因。因此,我们再手动实现另一个注解,可以再理解一下:

 1package com.springtest.demo.annotation;
 2
 3import java.lang.annotation.Documented;
 4import java.lang.annotation.ElementType;
 5import java.lang.annotation.Retention;
 6import java.lang.annotation.RetentionPolicy;
 7import java.lang.annotation.Target;
 8
 9@Target({ElementType.FIELD, ElementType.TYPE})
10@Retention(RetentionPolicy.RUNTIME)
11@Documented
12public @interface Scope {
13    String SCOPE_SINGLETON = "singleton";
14
15    String SCOPE_PROTOTYPE = "prototype";
16
17    // 默认是单例
18    String value() default Scope.SCOPE_SINGLETON;
19}

看看上面这个注解,是不是很眼熟?~~

没错,就是我们在Spring中常用的@Scope注解的照搬版~试试分析看这个注解是怎么表达的。

接下来开始进行注解的解析,这里因为我们直接定义成运行时,所以可以在运行中通过类的反射机制,找到我们这个注解对应的作用域,如果被我们注解了,并且注解内的条件达到了,我们就对这个作用域内的对象进行某些我们想要的操作就行了。下面附上我的源代码以及编译器编译后的class的反编译结果。

 1package com.springtest.demo.annotation;
 2
 3import com.springtest.demo.config.YunyaoBeanPostProcessor;
 4
 5import javax.annotation.PostConstruct;
 6import java.lang.reflect.Field;
 7
 8/**
 9 * 注解驱动
10 */
11@Scope(Scope.SCOPE_PROTOTYPE)
12public class FruitInfoUtil {
13    public static void getFruitInfo(Object obj) {
14        String strFruitName = " 水果名称:";
15
16        Field[] fields = obj.getClass().getDeclaredFields();
17
18        for (Field field : fields) {
19            if (field.isAnnotationPresent(Fruit.class)) {
20                Fruit fruit = field.getAnnotation(Fruit.class);
21                strFruitName = strFruitName + fruit.value() + fruit.type().name();
22                System.out.println(strFruitName);
23            }
24        }
25    }
26
27    // 注:同一个对象,被实力出多个不同value的单例时,PostConstruct只会被执行一次
28    @PostConstruct
29    public void postConstruct() {
30        System.out.println("PostConstruct被执行..." + this.getClass().getName());
31    }
32}
 1
 2
 3    public static void getFruitInfo(Object obj)
 4    {
 5        String strFruitName = " \u6C34\u679C\u540D\u79F0\uFF1A";
 6        Field fields[] = obj.getClass().getDeclaredFields();
 7        Field afield[] = fields;
 8        int i = afield.length;
 9        for(int j = 0; j < i; j++)
10        {
11            Field field = afield[j];
12            if(field.isAnnotationPresent(com/springtest/demo/annotation/Fruit))
13            {
14                Fruit fruit = (Fruit)field.getAnnotation(com/springtest/demo/annotation/Fruit);
15                strFruitName = (new StringBuilder()).append(strFruitName).append(fruit.value()).append(fruit.type().name()).toString();
16                System.out.println(strFruitName);
17            }
18        }
19    }
20
21    public void postConstruct()
22    {
23        System.out.println((new StringBuilder()).append("PostConstruct\u88AB\u6267\u884C...").append(getClass().getName()).toString());
24    }

总结

其实说到最后,注解真的很容易,而且很简单易用,只要我们搞清楚:

  1. 注解的本质是什么 -- 本质上注解就是一个JDK提供给我们的标记位,我们需要自己定义这个标记会会在什么条件下触发什么动作
  2. 基于第一点,我们甚至可以自己定义执行函数,来对其他三方组件或者JDK提供的注解进行自定义补充解析。
  3. 基于第二点,我们就可以知道,注解的灵魂不在注解本身,而在于谁来解析这个注解(所谓一千个...)