本篇文章的主要内容关于Java自定义注解。

什么是注解

Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。

Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的 Annotion对象,然后通过 Annotion对象来获取注解里面的元数据。

元注解

Java元注解是用于定义和处理其他注解的特殊注解。元注解本身并不直接应用于代码的元素(类、方法、字段等),而是用于修改或解释其他注解。Java中有四个标准的元注解,它们可以用于自定义注解,并对注解的行为进行控制:

  1. @Retention: 用于指定注解的保留策略,即注解在何时可见。它有以下三个策略:
    • RetentionPolicy.SOURCE: 注解仅保留在源代码中,编译后不会包含在class文件中。
    • RetentionPolicy.CLASS: 注解保留在class文件中,但在运行时不可访问。
    • RetentionPolicy.RUNTIME: 注解保留在class文件中,并在运行时可通过反射访问。
  2. @Target: 用于指定注解可以应用的目标元素类型。它有以下常用选项:
    • ElementType.TYPE: 可以应用于类、接口或枚举。
    • ElementType.METHOD: 可以应用于方法。
    • ElementType.FIELD: 可以应用于字段或属性。
    • ElementType.PARAMETER: 可以应用于方法参数。
    • ElementType.CONSTRUCTOR: 可以应用于构造函数。
    • ElementType.LOCAL_VARIABLE: 可以应用于局部变量。
    • ElementType.ANNOTATION_TYPE: 可以应用于其他注解。
    • ElementType.PACKAGE: 可以应用于包声明。
  3. @Documented: 用于指定注解是否包含在Java文档中。如果一个注解被@Documented标记,它将包含在生成的Java文档中,否则不会出现在文档中。
  4. @Inherited: 用于指定注解是否可以被继承。如果一个注解被@Inherited标记,且应用于一个类,那么这个类的子类将自动继承该注解。但要注意,只有在类级别上使用的注解才会被继承,对方法或字段的注解不会被继承。

自定义注解

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package annotation;

import java.lang.annotation.*;

// 使用@Retention元注解指定注解在运行时可见
@Retention(RetentionPolicy.RUNTIME)
// 使用@Target元注解指定注解只能应用于方法和类
@Target({ElementType.METHOD, ElementType.TYPE})
// 使用@Documented元注解将注解包含在Java文档中
@Documented
// 使用@Inherited元注解指定注解可以被继承
@Inherited
public @interface MyCustomAnnotation {
// 定义注解的元素(成员变量),这里定义了一个String类型的元素
String value() default "default value";
}

解析

以下是注解的功能:

主要步骤就是

  1. 通过反射获取类型 : Class<?> clazz = obj.getClass();
  2. 通过 clazz.isAnnotationPresent(MyCustomAnnotation.class)来检查是否有指定注解,
  3. 获取注解实例: MyCustomAnnotation classAnnotation = clazz.getAnnotation(MyCustomAnnotation.class);
  4. do something …
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
53
package annotation;
import java.lang.reflect.Method;

public class AnnotationProcessor {

/**
* 处理类级别的注解
*
* @param clazz 要处理注解的类的Class对象
*/
public static void processClassAnnotations(Class<?> clazz) {
// 检查类是否存在指定的注解
if (clazz.isAnnotationPresent(MyCustomAnnotation.class)) {
// 获取类上的MyCustomAnnotation注解实例
MyCustomAnnotation classAnnotation = clazz.getAnnotation(MyCustomAnnotation.class);
// 输出类上的注解值
System.out.println("Class Annotation: " + classAnnotation.value());
}
}

/**
* 处理方法级别的注解
*
* @param clazz 要处理注解的类的Class对象
*/
public static void processMethodAnnotations(Class<?> clazz) {
// 获取类中所有声明的方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历方法数组
for (Method method : methods) {
// 检查方法是否存在指定的注解
if (method.isAnnotationPresent(MyCustomAnnotation.class)) {
// 获取方法上的MyCustomAnnotation注解实例
MyCustomAnnotation methodAnnotation = method.getAnnotation(MyCustomAnnotation.class);
// 输出方法上的注解值
System.out.println("Method Annotation for " + method.getName() + ": " + methodAnnotation.value());
}
}
}

public static void main(String[] args) {
// 创建一个示例类的实例
MyClass obj = new MyClass();
// 获取示例类的Class对象
Class<?> clazz = obj.getClass();

// 处理类级别的注解
processClassAnnotations(clazz);
// 处理方法级别的注解
processMethodAnnotations(clazz);
}
}

测试类:

1
2
3
4
5
6
7
8
9
package annotation;
@MyCustomAnnotation(value = "MyClass annotation")
public class MyClass {
@MyCustomAnnotation(value = "myMethod annotation")
public void myMethod() {
System.out.println("Hello, I'm an annotated method!");
}
}

输出:

1
2
3
Class Annotation: MyClass annotation
Method Annotation for myMethod: myMethod annotation
Disconnected from the target VM, address: '127.0.0.1:52534', transport: 'socket'

注解自动扫描

现在这个注解的使用,是在AnnotationProcessor中通过创建一个示例类obj ,再获取这个obj 的Class对象进行解析。那么如何实现自动的扫描和解析呢?

实现自动的扫描和解析注解通常涉及两个主要步骤:类路径扫描和注解解析。

类路径扫描是为了找到所有包含自定义注解的类,而注解解析则是在找到这些类后,对它们进行具体的注解处理。

我们可以通过反射和类路径扫描技术来实现自动的注解扫描和解析。

以下是一个类路径扫描技术实现思路:

  1. 获取当前线程的类加载器:首先,我们通过Thread.currentThread().getContextClassLoader()方法获取当前线程的类加载器。类加载器用于从类路径中加载类和资源文件。
  2. 将包名转换为资源路径:将包名中的点号.替换为斜杠/,形成资源文件的路径形式。例如,将包名com.example.myapp转换为com/example/myapp
  3. 在类路径下搜索资源文件:使用类加载器的getResources()方法,在类路径下搜索包含指定包名的所有资源文件。这些资源文件可能是目录,也可能是Jar文件。
  4. 遍历资源文件:遍历找到的资源文件,对于每个资源文件,进行如下处理:
    • 如果资源文件是目录,则递归处理该目录,继续寻找其他资源文件。
    • 如果资源文件是Jar文件,则读取该Jar文件,遍历其中的所有类,并将包含指定包名的类进行处理。
  5. 加载并处理类:对于找到的每个类,使用Class.forName()方法加载类,然后进行注解解析或其他处理逻辑。