Java 注解解析获取及自定义

发布时间:2024年01月15日

上一篇文章 Java 注解简介 提到,注解本质是一个继承了Annotation 的特殊接口,起到说明、配置的作用。
今天我们就进一步了解它的原理以及如何自定义。

注解原理概述:(理解)

  1. 读取xml配置文件。(这里不做重点解读)
  2. 实例化bean。(这里不做重点解读)
  3. 遍历所有bean对象,再获取bean对象的所有方法或字段或属性,并进行遍历,判定其是否包含指定注解,若包含则进行注解的获取(通过反射获取注解,获取过程中会进行注解信息的解析,返回的是Java 运行时生成的动态代理对象$Proxy1)。
  4. 获取注解的代理对象后,就可以调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。

测试案例如下:(熟悉)

准备 Fruit 注解类

package com.gump.annotation.custom;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD,METHOD})
@Retention(RUNTIME)
@Documented
public @interface Fruit {
    /**
     * 名称
     */
    String name() default "";

    /**
     * 产地
     */
    String address() default "";
}

准备 Apple 类

package com.gump.annotation.custom;
import lombok.Setter;
public class Apple {

    @Fruit(name="新疆阿克苏糖心苹果",address="新疆阿克苏")
    @Setter
    private String apple;

    @Fruit(name="gump",address = "中国")
    public void sell(){
    }
}

准备 FruitRun 测试类

package com.gump.annotation.custom;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class FruitRun {
    public static void main(String[] args) throws IntrospectionException {

        //通过方法注入
        Method[] methods = Apple.class.getDeclaredMethods();
        for (Method method:methods) {
            if(method.isAnnotationPresent(Fruit.class)){
                Fruit annotation = method.getAnnotation(Fruit.class);
                System.out.println(annotation.name() + "在" + annotation.address());
            }
        }

        //通过字段注入
        Field[] fields = Apple.class.getDeclaredFields();
        for (Field field:fields) {
            if(field.isAnnotationPresent(Fruit.class)){
                Fruit annotation = field.getAnnotation(Fruit.class);
                System.out.println("水果名称:" + annotation.name());
                System.out.println("水果产地:" + annotation.address());
            }
        }

        //通过属性注入,参数bean 为实例化的bean对象
//        PropertyDescriptor[] properties = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
//        for (PropertyDescriptor property:properties) {
//            //获取属性的setter方法
//            Method setter = property.getWriteMethod();
//            if (setter != null && setter.isAnnotationPresent(Fruit.class)) {
//                Fruit annotation = setter.getAnnotation(Fruit.class);
//                System.out.println("水果名称:" + annotation.name());
//                System.out.println("水果产地:" + annotation.address());
//            }
//        }

    }
}

下面将通过方法注入的源码进行分析。

源码解析如下:(了解)

首先,通过Class 中的 getDeclaredMethods 方法获取bean对象的所有方法数组对象,本案例中包含 sell 和 setApple(@setter注解添加) 两个方法:

//获取对象的所有方法数组对象,getDeclaredMethods方法可不作过多了解
Method[] methods = Apple.class.getDeclaredMethods();

然后,遍历方法数组对象,判断该方法上是否包含指定类型的注解Fruit,存在则返回true,否则返回false:

for (Method method:methods) {
    if(method.isAnnotationPresent(Fruit.class)){
        Fruit annotation = method.getAnnotation(Fruit.class);
    }
}

跟踪method.isAnnotationPresent(Fruit.class) 发现,method 会调用 AccessibleObject 类中的 isAnnotationPresent 方法,AccessibleObject 类实现了AnnotatedElement 接口,并重写了isAnnotationPresent 方法。super调用,实际上调用的AnnotatedElement的isAnnotationPresent 方法:

public class AccessibleObject implements AnnotatedElement {
  @Override
  public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
      return AnnotatedElement.super.isAnnotationPresent(annotationClass);
  }
}

而AnnotatedElement 的isAnnotationPresent 方法判断是否存在,则是通过 getAnnotation 获取注解是否为null 进行判定:

public interface AnnotatedElement {
  //判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。
  default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
      return getAnnotation(annotationClass) != null;
  }
  
  //返回该程序元素上存在的、指定类型的注解。如果没有则返回null。
  <T extends Annotation> T getAnnotation(Class<T> annotationClass);
}

通过debug发现,AnnotatedElement 中的getAnnotation 方法的具体实现,是由Method 中的getAnnotation 方法来实现的,Method 继承自Executable ,super调用Executable 中的getAnnotation 方法:

public final class Method extends Executable {
    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        return super.getAnnotation(annotationClass);
    }
}

Executable 中的getAnnotation 实际是从 declaredAnnotations 方法中获取注解(注解解析一次后就存入了declaredAnnotations 对应的Map对象中,后续直接从该Map中获取,不再进行解析):

public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
    Objects.requireNonNull(annotationClass);
    return annotationClass.cast(declaredAnnotations().get(annotationClass));
}

private synchronized  Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
        if (declaredAnnotations == null) {
            Executable root = getRoot();
            if (root != null) {
                declaredAnnotations = root.declaredAnnotations();
            } else {
                //解析并获取注解信息
                declaredAnnotations = AnnotationParser.parseAnnotations(
                    getAnnotationBytes(),
                    sun.misc.SharedSecrets.getJavaLangAccess().
                    getConstantPool(getDeclaringClass()),
                    getDeclaringClass());
            }
        }
        return declaredAnnotations;
    }

从下图可以看出,declaredAnnotations 的方法返回结果为一个Map,里面的key对应Fruit,value对应一个代理对象(AnnotationInvocationHandler),自定义注解Fruit包含的信息就在这个代理对象里面。(图片后续补上)
图片!!!!!

进入 AnnotationParser 的 parseAnnotations 方法,parseAnnotations 方法中调用了 parseAnnotations2 方法,parseAnnotations2 方法中循环调用了 parseAnnotation2 方法获取注解信息,并将 RetentionPolicy.RUNTIME 的注解放入到Map中(这也是为什么可以使用反射机制读取该注解信息的原因

private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(byte[] var0, ConstantPool var1, Class<?> var2, Class<? extends Annotation>[] var3) {
    LinkedHashMap var4 = new LinkedHashMap();
    ByteBuffer var5 = ByteBuffer.wrap(var0);
    int var6 = var5.getShort() & '\uffff';

    for(int var7 = 0; var7 < var6; ++var7) {
        Annotation var8 = parseAnnotation2(var5, var1, var2, false, var3);
        if (var8 != null) {
            Class var9 = var8.annotationType();
            if (AnnotationType.getInstance(var9).retention() == RetentionPolicy.RUNTIME && var4.put(var9, var8) != null) {
                throw new AnnotationFormatError("Duplicate annotation for class: " + var9 + ": " + var8);
            }
        }
    }
    return var4;
}

进入parseAnnotation2 方法结尾调用了 annotationForMap(var6, var10) 方法,annotationForMap方法中调用了Proxy 的 newProxyInstance 创建代理对象实例,源码如下:

public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
    return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
        public Annotation run() {
            return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
        }
    });
}

至此,解析并获取注解信息的动作完成,有兴趣的可以进一步了解 AnnotationParser.parseAnnotations 方法的参数含义。
获取到注解的代理对象后,就可以调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。

小结:
注解代表的是某种业务意义,注解背后处理器的工作原理如上源码实现:首先解析所有属性,判断属性上是否存在指定注解,如果存在则根据搜索规则取得bean,然后利用反射原理注入。如果标注在字段上面,也可以通过字段的反射技术取得注解,根据搜索规则取得bean,然后利用反射技术注入。

注解的自定义:(掌握)

为了更熟练的使用自定义注解,先了解Annotation和interface的异同:(了解)

  • annotition的类型使用关键字@interface而不是interface。它继承了java.lang.annotition.Annotition接口,并非申明了一个interface。
  • Annotation类型、方法定义是独特的、受限制的。Annotation类型的方法必须申明为无参数、无异常抛出的。这些方法定义了Annotation的成员:方法名称为了成员名,而方法返回值称为了成员的类型。而方法返回值必须为primitive类型、Class类型、枚举类型、Annotation类型或者由前面类型之一作为元素的一位数组。方法的后面可以使用default和一个默认数值来申明成员的默认值,null不能作为成员的默认值,这与我们在非Annotation类型中定义方法有很大不同。Annotation类型和他的方法不能使用Annotation类型的参数,成员不能是generic。只有返回值类型是Class的方法可以在Annotation类型中使用generic,因为此方法能够用类转换将各种类型转换为Class。
  • Annotation类型又与接口有着近似之处。它们可以定义常量、静态成员类型(比如枚举类型定义)。Annotation类型也可以如接口一般被实现或者继承。

以及自定义注解类编写的一些规则:(了解)

  • Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
  • 参数成员只能用public 或默认(default) 这两个访问权修饰。
  • 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组。
  • 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法。
  • 注解也可以没有定义成员,,不过这样注解就没啥用了。

现在可以开始你的注解自定义了(会用就行),案例如本文的@Fruit。
可以阅读笔者之前的文章,帮助你更好的完成注解的自定义:
Java 标准注解和元注解
注解与反射接口AnnotatedElement

文章来源:https://blog.csdn.net/glb168/article/details/135606564
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。