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


文章目录

  • 注解背景
  • 源码解析
    • Retention注解
    • Target 注解
    • Documented注解
    • 小结
  • 总结

注解背景 首先我们要知道背景知识:
  1. 每一个注解其实就是一个特殊的接口(带着@符号,其实是语法糖,会被编译器自动编译成继承自Annotation接口)。我们反编译一下class文件就能看出来。
  2. 注解只是一个标记位,标记了某一个类,某一个字段或者某一个函数之后,我们就可以对被标记的属性进行我们期望的行为——比如运行时动态获取和修改被标记的属性,动态执行被标记的函数等等
  3. 基于第二点,我们在定义了自己的注解之后,还要定义自己注解的解析类,这样我们才能真正让注解发挥起作用(只标记而不做任何动作就和没标记没任何区别了)
源码解析 废话不说上我自己定义的代码,然后一个一个说明。
package com.springtest.demo.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Fruit { String value() default ""; String name() default ""; enum FruitType { APPLE, PEACH, PEAR, WATERMELON }FruitType type() default FruitType.APPLE; }

package com.springtest.demo.entity.fruit; import com.springtest.demo.annotation.Fruit; import com.springtest.demo.annotation.Scope; @Scope(Scope.SCOPE_PROTOTYPE) public class Pear { @Fruit(value = "https://www.it610.com/article/pear") private String name; @Fruit(type = Fruit.FruitType.PEAR) private String type; }

package com.springtest.demo.entity.fruit; import com.springtest.demo.annotation.Fruit; import com.springtest.demo.annotation.Scope; @Scope(Scope.SCOPE_PROTOTYPE) public class Apple { @Fruit(value = "https://www.it610.com/article/apple") private String name; @Fruit(type = Fruit.FruitType.APPLE) private String type; }

以上就是我做的最简单的demo,定义并应用了一个注解。我们来看看Fruit注解反编译的结果是什么就能大概知道这故事背后的作用。
public interface Fruit extends Annotation { public static final class FruitType extends Enum {public static final FruitType APPLE; public static final FruitType PEACH; public static final FruitType PEAR; public static final FruitType WATERMELON; private static final FruitType $VALUES[]; public static FruitType[] values() { return (FruitType[])$VALUES.clone(); }public static FruitType valueOf(String name) { return (FruitType)Enum.valueOf(com/springtest/demo/annotation/Fruit$FruitType, name); }static { APPLE = new FruitType("APPLE", 0); PEACH = new FruitType("PEACH", 1); PEAR = new FruitType("PEAR", 2); WATERMELON = new FruitType("WATERMELON", 3); $VALUES = (new FruitType[] { APPLE, PEACH, PEAR, WATERMELON }); }private FruitType(String s, int i) { super(s, i); } }public abstract String value(); public abstract String name(); public abstract FruitType type(); }

查看代码我们发现所谓注解的本质还是很简单的其实就是一个继承了Annotation的接口,然后内部定义了一些默认的抽象类而已。
Retention注解 由于因为我们知道其实一个注解本质上就只是一个标记,这个标记要怎么使用,什么时候使用是我们的编译器和jvm决定的,也就意味着一个注解通常会有一个目的,或者我们叫作用域。通常分为三类:
  1. 仅在编码时生效 @RetentionPolicy .SOURCE
  2. 仅在编译时生效 @RetentionPolicy .CLASS
  3. 仅在运行时生效 @RetentionPolicy .RUNTIME
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }/** * Annotation retention policy.The constants of this enumerated type * describe the various policies for retaining annotations.They are used * in conjunction with the {@link Retention} meta-annotation type to specify * how long annotations are to be retained. * * @authorJoshua Bloch * @since 1.5 */ public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE,/** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time.This is the default * behavior. */ CLASS,/** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }

Target 注解 同时由于我们的注解是可能被写在各种地方的,因此我们需要定义我们这个参数的作用域。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE,/** Field declaration (includes enum constants) */ FIELD,/** Method declaration */ METHOD,/** Formal parameter declaration */ PARAMETER,/** Constructor declaration */ CONSTRUCTOR,/** Local variable declaration */ LOCAL_VARIABLE,/** Annotation type declaration */ ANNOTATION_TYPE,/** Package declaration */ PACKAGE,/** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER,/** * Use of a type * * @since 1.8 */ TYPE_USE }

Documented注解 最后就是一个是否需要被Javadoc记录的标记位@Documented。
小结 基于遇上三点,也就是为什么我们常见的注解,头上都会有这三个标记的原因。因此,我们再手动实现另一个注解,可以再理解一下:
package com.springtest.demo.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { String SCOPE_SINGLETON = "singleton"; String SCOPE_PROTOTYPE = "prototype"; // 默认是单例 String value() default Scope.SCOPE_SINGLETON; }

看看上面这个注解,是不是很眼熟?~~
【自定义注解并且进行扫描解析】没错,就是我们在Spring中常用的@Scope注解的照搬版~试试分析看这个注解是怎么表达的。
接下来开始进行注解的解析,这里因为我们直接定义成运行时,所以可以在运行中通过类的反射机制,找到我们这个注解对应的作用域,如果被我们注解了,并且注解内的条件达到了,我们就对这个作用域内的对象进行某些我们想要的操作就行了。下面附上我的源代码以及编译器编译后的class的反编译结果。
package com.springtest.demo.annotation; import com.springtest.demo.config.YunyaoBeanPostProcessor; import javax.annotation.PostConstruct; import java.lang.reflect.Field; /** * 注解驱动 */ @Scope(Scope.SCOPE_PROTOTYPE) public class FruitInfoUtil { public static void getFruitInfo(Object obj) { String strFruitName = " 水果名称:"; Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Fruit.class)) { Fruit fruit = field.getAnnotation(Fruit.class); strFruitName = strFruitName + fruit.value() + fruit.type().name(); System.out.println(strFruitName); } } }// 注:同一个对象,被实力出多个不同value的单例时,PostConstruct只会被执行一次 @PostConstruct public void postConstruct() { System.out.println("PostConstruct被执行..." + this.getClass().getName()); } }

public static void getFruitInfo(Object obj) { String strFruitName = " \u6C34\u679C\u540D\u79F0\uFF1A"; Field fields[] = obj.getClass().getDeclaredFields(); Field afield[] = fields; int i = afield.length; for(int j = 0; j < i; j++) { Field field = afield[j]; if(field.isAnnotationPresent(com/springtest/demo/annotation/Fruit)) { Fruit fruit = (Fruit)field.getAnnotation(com/springtest/demo/annotation/Fruit); strFruitName = (new StringBuilder()).append(strFruitName).append(fruit.value()).append(fruit.type().name()).toString(); System.out.println(strFruitName); } } }public void postConstruct() { System.out.println((new StringBuilder()).append("PostConstruct\u88AB\u6267\u884C...").append(getClass().getName()).toString()); }

总结 其实说到最后,注解真的很容易,而且很简单易用,只要我们搞清楚:
  1. 注解的本质是什么 – 本质上注解就是一个JDK提供给我们的标记位,我们需要自己定义这个标记会会在什么条件下触发什么动作
  2. 基于第一点,我们甚至可以自己定义执行函数,来对其他三方组件或者JDK提供的注解进行自定义补充解析。
  3. 基于第二点,我们就可以知道,注解的灵魂不在注解本身,而在于谁来解析这个注解(所谓一千个…)

    推荐阅读