注解处理器机制过程

这里写目录标题

  • 1.注解的本质
  • 2.如何处理注解-理解注解处理器过程
  • 3.注解处理过程代码举例
  • 4.注解是不能继承也不能实现其他类或接口的
  • 5.AnnotatedElement接口
  • 6.AnnotationInvocationHandler
  • 7.AnnotationInvocationHandler处理注解原理理解
  • 8.注解在什么阶段被执行和获取
  • Spring 框架中衍生的大量注解是被虚拟机处理还是Spring容器处理

标注注解的注解
一句话概括就是,通过程序员代码中的方法名获取方法名上方的注解属性和其赋值。获取是通过AnnotationInvocationHandler处理器中invoke获取

1.注解的本质

就是一个接口,并且继承了java.lang.annotation.Annotation,内部的定义其实就是一个带默认值的方法

public @interface 注解名 {
  修饰符 返回值 属性名() 默认值;
  //TODO
}

注解接口中定义的方法可以包含默认值,这意味着在使用注解时,如果没有为该方法提供值,则会使用默认值。这使得我们可以在注解中指定一些可选的属性。

以下是一个简单的注解示例:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "Default Value";
    int count() default 0;
}

在上面的示例中,MyAnnotation 是一个注解接口。它具有两个方法 value()count(),并且它们都有默认值。

通过使用 @MyAnnotation 注解可以在代码中标记方法,并且可以在运行时获取并处理该注解。

public class MyClass {
    @MyAnnotation(value = "Custom Value", count = 5)
    public void myMethod() {
        // 方法实现
    }
}

通过上述代码,我们将 @MyAnnotation 注解应用于 myMethod() 方法,并为注解的属性提供了自定义的值。

需要注意的是,注解在编译后并不会被直接转化为字节码的一部分,但可以通过反射机制在运行时获取注解信息。

2.如何处理注解-理解注解处理器过程

在Java中,注解在运行时会被JVM处理并生成对应的代理类。这个过程称为注解处理(Annotation Processing)。

当程序中使用了注解并且启用了注解处理器时,JVM会在运行时扫描源代码、编译后的字节码或者其他资源文件,找到包含注解的元素(如类、方法、字段等),然后根据注解的定义,生成相应的代理类或进行其他处理操作。

生成的代理类通常是一个继承自java.lang.reflect.Proxy的动态代理类。这个代理类实现了注解接口,并提供了特定的行为和逻辑,以满足注解的需求。通过这种方式,我们可以在程序运行时获取注解的信息,并根据注解的定义执行相应的逻辑。

注解处理器可以根据注解的定义执行各种任务,例如生成额外的代码、检查代码的合规性、生成配置文件等。例如,Java中的注解处理工具APT(Annotation Processing Tool)可以根据注解生成代码,而Spring框架中的注解处理器可以解析注解并完成依赖注入等功能。

需要注意的是,注解处理是在编译期和运行期进行的,编译器和JVM都参与了注解处理的过程。注解处理器会在编译阶段被调用,生成的代理类会被包含在编译后的字节码中,然后在程序运行时被JVM加载和使用。

3.注解处理过程代码举例

这个例子展示了如何在运行时使用注解处理器来获取标记了特定注解的方法信息。
假设有一个自定义的注解 MyAnnotation,并且我们想要在运行时获取标记了这个注解的方法的信息。现在我们来创建一个简单的注解处理器,它会在运行时扫描类中的方法,找到标记了 MyAnnotation 注解的方法,并输出相关信息。

首先,定义注解 MyAnnotation

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "Default Value";
    int count() default 0;
}

接下来,创建一个简单的类,其中包含一个标记了 MyAnnotation 注解的方法:

public class MyClass {
    @MyAnnotation(value = "Custom Value", count = 5)
    public void myMethod() {
        // 方法实现
    }
}

然后,我们编写一个注解处理器 MyAnnotationProcessor,它会在运行时扫描 MyClass 类中的方法,找到标记了 MyAnnotation 注解的方法,并输出相关信息:

import java.lang.reflect.Method;

public class MyAnnotationProcessor {
    public static void main(String[] args) {
        Class<MyClass> clazz = MyClass.class;
        Method[] methods = clazz.getMethods();

        for (Method method : methods) {
            if (method.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                System.out.println("Method Name: " + method.getName());
                System.out.println("Annotation Value: " + annotation.value());
                System.out.println("Annotation Count: " + annotation.count());
            }
        }
    }
}

在这个例子中,我们通过反射机制获取了 MyClass 类中的所有方法,并检查每个方法是否标记了 MyAnnotation 注解。如果找到标记了该注解的方法,我们就获取注解的属性值并输出到控制台。

当我们运行 MyAnnotationProcessor 类时,它会输出以下信息:

Method Name: myMethod
Annotation Value: Custom Value
Annotation Count: 5

这个例子展示了如何在运行时使用注解处理器来获取标记了特定注解的方法信息。

4.注解是不能继承也不能实现其他类或接口的

5.AnnotatedElement接口

getAnnotation:返回指定的注解
isAnnotationPresent:判定当前元素是否被指定注解修饰
getAnnotations:返回所有的注解
getDeclaredAnnotation:返回本元素的指定注解
getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的

// 判断该元素是否包含指定注解,包含则返回true
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 返回该元素上对应的注解,如果没有返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组
Annotation[] getAnnotations();
// 返回指定类型的注解,如果没有返回空数组
T[] getAnnotationsByType(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T getDeclaredAnnotation(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T[] getDeclaredAnnotationsByType
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组,只包含直接标注的注解,不包含inherited的注解
Annotation[] getDeclaredAnnotations();

6.AnnotationInvocationHandler

是 JAVA 中专门用于处理注解的 Handler处理器


 public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

7.AnnotationInvocationHandler处理注解原理理解

AnnotationInvocationHandler 是 Java 标准库中提供的一个类,它是动态代理的一种应用。它可以用于处理注解类型的代理对象,并且提供了一些便捷的方法,使得我们可以方便地获取注解的属性值。

在 Java 中,注解本身并没有实现类,只有定义接口和注解属性的元数据。当我们使用注解时,Java 编译器会自动生成一个实现了该接口的代理类,该代理类提供了注解接口中所有属性的默认值。然后,我们可以在运行时获取这个代理类的实例,并通过调用其方法(invoke)来获取注解的属性值。

AnnotationInvocationHandler 就是用来处理这个代理类的。它是 java.lang.reflect.InvocationHandler 接口的一个实现,可以代理注解接口,并提供相关的方法来访问注解属性

下面是一个简单的例子,展示了如何使用 AnnotationInvocationHandler 来获取注解的属性值:

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AnnotationDemo {
    public static void main(String[] args) {
        MyAnnotation myAnnotation = createAnnotationProxy(MyAnnotation.class, "test value", 42);
        System.out.println("value: " + myAnnotation.value());
        System.out.println("count: " + myAnnotation.count());
    }

    private static <T extends Annotation> T createAnnotationProxy(Class<T> annotationClass, Object... values) {
        InvocationHandler handler = new AnnotationInvocationHandler(annotationClass, values);
        return (T) Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[]{annotationClass}, handler);
    }

    public static class AnnotationInvocationHandler implements InvocationHandler {
        private final Class<? extends Annotation> annotationType;
        private final Object[] values;

        public AnnotationInvocationHandler(Class<? extends Annotation> annotationType, Object... values) {
            this.annotationType = annotationType;
            this.values = values;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            switch (methodName) {
                case "annotationType":
                    return annotationType;
                case "toString":
                    return annotationType.getName() + "@" + Integer.toHexString(hashCode());
                case "hashCode":
                    return hashCode();
                case "equals":
                    return proxy == args[0];
                default:
                    for (Method m : annotationType.getDeclaredMethods()) {
                        if (m.getName().equals(methodName)) {
                            return values[m.getAnnotation(MyAnnotationProperty.class).index()];
                        }
                    }
                    throw new RuntimeException("No such property: " + methodName);
            }
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyAnnotation {
        MyAnnotationProperty[] properties() default {};

        String value() default "";
        int count() default 0;
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyAnnotationProperty {
        int index() default 0;
    }
}

在这个例子中,我们定义了一个 MyAnnotation 注解,它包含两个属性:valuecount。然后,我们使用 createAnnotationProxy() 方法来创建注解的代理类,并将注解属性值传递给它。这个方法返回一个注解对象,我们可以通过调用它的方法来获取注解属性值。

AnnotationInvocationHandler 类实现了 InvocationHandler 接口,它会在代理对象上调用方法,并将其转发到实现类的对应方法中。在这个例子中,我们使用 invoke() 方法来获取注解属性值。该方法根据方法名查找注解属性,然后返回相应的值。

8.注解在什么阶段被执行和获取

在 Java 中,方法上的注解可以在编译期、运行期或两者同时被处理和获取。

  1. 编译期处理:在编译 Java 源代码时,编译器会扫描并解析方法上的注解。编译器可以根据注解的定义进行静态检查、生成相关的代码或报告警告/错误等。例如,Java 内置的 @Override 注解就是在编译期进行检查,用于确保当前方法覆盖了父类的方法。

  2. 运行期处理:在 Java 程序运行时,可以使用反射机制获取方法上的注解,并通过注解的属性值来做特定的处理。这通常需要开发人员编写额外的代码来使用反射进行注解的解析。例如,Spring 框架中的 @RequestMapping 注解就是在运行期处理的,它用于映射 HTTP 请求到具体的处理方法。

  3. 编译期和运行期同时处理:有些注解可以在编译期和运行期都进行处理。在编译期,注解可以被编译器读取并进行相应的处理,生成相关的代码或报告警告/错误。在运行期,注解可以通过反射机制被获取并进行进一步的处理。这通常需要使用特定的工具或框架来实现。例如,Hibernate 框架中的 @Entity 注解可以在编译期进行代码生成,并在运行期通过反射来访问实体类的元数据。

需要注意的是,大部分情况下,注解的处理是由开发人员自行实现的。编译器和虚拟机对注解的处理方式是预定义的,并且有一些内置的注解用于特定目的(例如 @Override@Deprecated 等)。但对于自定义的注解,需要开发人员编写相应的代码来解析和处理注解的属性值。

总结来说,方法上的注解在编译期和/或运行期被处理和获取,具体的处理方式取决于注解的定义和使用场景。

Spring 框架中衍生的大量注解是被虚拟机处理还是Spring容器处理

在 Spring 框架中,大量的注解是由 Spring 容器(而不是虚拟机)进行处理和解析的。Spring 框架使用了反射和自定义的处理机制来解析注解,并根据注解的定义执行相应的逻辑。

Spring 框架中的注解主要用于配置和控制应用程序的行为,例如声明 Bean、依赖注入、AOP 等。以下是一些常用的 Spring 注解及其作用:

  • @Component:用于标记一个类为 Spring 组件,会被 Spring 自动扫描并创建实例。
  • @Autowired:用于自动注入依赖对象。
  • @RequestMapping:用于映射 HTTP 请求到处理方法。
  • @Transactional:用于声明事务管理。
  • @Aspect@Before@After 等:用于声明切面和切点,实现 AOP 功能。

当使用了这些注解时,Spring 容器会在应用程序启动时扫描所配置的包,并解析这些注解。它会通过反射机制获取注解信息,并根据注解的定义执行相应的逻辑,例如创建 Bean 实例、自动注入依赖、生成代理对象等。

Spring 框架还提供了一些工具类和接口,用于处理和解析注解,例如 AnnotationUtilsAnnotatedElementUtilsAnnotationConfigApplicationContext 等。这些工具和接口可以让开发人员更方便地处理和访问注解信息。

需要注意的是,虽然 Spring 框架在运行时使用了反射来处理注解,但这并不是虚拟机自动处理的。相反,Spring 框架提供了一套机制,用于解释和执行这些注解,并在应用程序运行时根据注解的定义来完成相应的功能。

你可能感兴趣的:(Java,java)