跳至主要內容

注解机制详解

Quest大约 7 分钟基础知识注解

注解的作用

注解( annotation)是JDK5引入的一个新特性,在代码中以元数据添加信息的特性,采用声明式的方式来标记和配置代码,主要有以下作用:

  • 生成文档: javadoc注解可以生成API文档
  • 提供编译时检查: 注解可以用于对代码的静态检查,比如@Override注解标记子类方法覆盖父类方法,编译器可以验证是否正确覆盖了父类方法
  • 自动生成代码: 注解可以用于生成额外的代码,比如可以通过注解生成数据表结构
  • 运行时动态处理等: 注解可以通过反射机制获取进行处理,比如Junit单元测试标记测试方法,运行时可以根据注解运行相应测试方法

内置注解

内置注解是Java提供的一组预先定义好的注解,可以直接使用,以下是Java的内置注解:

@Override:标记一个方法重写了父类中的方法

@Deprecated:标记类、方法或字段已经过时,不推荐使用,编译器会生成警告

@SuppressWarnings:抑制编译产生的警告,作用于类、方法

@FunctionalInterface:用于标识接口是函数式接口,函数式接口只包含一个抽象方法,可用Lambda表达式和函数式编程

自定义注解

元注解

元注解是用于定义其他的注解的注解,是对注解进行注解的一种机制,达到定义注解的声明、生命周期、可重复性、使用目标等进行限定目的。Java中的元注解如下:

@Retention

​ 指定注解的保留策略,提供三种策略 :

RetentionPolicy.SOURCE:注解只会在源码阶段保留,编译时会被忽略,不会进入class文件

RetentionPolicy.CLASS:没有指定@Retention注解的默认值,保留在class文件中,不会被加载到JVM

RetentionPolicy.RUNTIME:注解会被加载到JVM中,可以通过反射机制获取

@Target

可以限制注解的使用范围,指定注解可以应用的目标元素,比如类、方法、字段。取值包括:

  • ElementType.TYPE :接口、类、枚举、注解
  • ElementType.FIELD: 字段
  • ElementType.METHOD: 修饰方法
  • ElementType.PARAMETER:方法参数
  • ElementType.CONSTRUCTOR:构造函数
  • ElementType.LOCAL_VARIABLE:局部变量
  • ElementType.ANNOTATION_TYPE:注解
  • ElementType.PACKAGE:包
  • ElementType.TYPE_PARAMETER:类型参数
  • ElementType.TYPE_USE:任何类型

@Documented

指定注解是否包含在Java文档中,当定义的注解里面有@Documented注解时,会被生成在Java文档中

@Inherited

指定注解是否可以被继承,某个类被标注了@Inherited修饰的注解时,子类如果没有重新标注该注解,那么会继承父类的注解

@Repeatable

Java8引入,@Repeatable修饰的注解可以在同一元素上多次应用

public class Authority {
  
    @Role(name = "root")
    @Role(name = "manager")
    public void example(){ 
    }
  
}

定义注解

Java中,声明注解使用@interface,声明体中每个方法表示一项配置参数,方法名称即为参数名称,返回值类型即为参数类型(返回值类型限定为:基本数据类型、ClassStringEnum、Annotation类型、或为以上类型的数组),后跟default关键字可以指定该项参数默认值

@Documented
@Inherited
@Repeatable
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Role {
  
    String name(); default "admin"
      
}

编译时处理注解

代码中包含的注解可以在源码编译阶段被内置注解处理器或自定义注解处理器执行扫描、解析注解,调用围绕该注解相关的逻辑处理代码,达到静态代码检查、生成代码、生成配置等目的

注解处理器

注解处理器(Annotation Processor)是javac内置的用于扫描、处理注解的工具,注解处理器继承于javax.annotation.processing.AbstractProcessor抽象类,注解处理器中可以定义生成代码、生成配置文件以及检查代码功能,比如mapstruct工具库,通过注解就可以生成Java Bean之间转换的扩展文件,使用时只需要创建接口,添加注解即可实现字段间的映射实现,极大减少了手工编写模板代码数量,以下是注解处理器例子:

@SupportedAnnotationTypes("org.mapstruct.Mapper")
@SupportedOptions({
    MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP
})
public class MappingProcessor extends AbstractProcessor {
  
  	@Override
    public synchronized void init(ProcessingEnvironment processingEnv) { }
    
  	@Override
    public SourceVersion getSupportedSourceVersion() { }
  
 		@Override
    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) { }
  
  
}

AbstractProcessor抽象类中提供以下方法:

getSupportedOptions()

使用@SupportedOptions注解可以代替此方法,表示注解处理器支持的选项,这些选项是自定义参数,用于配置处理器的行为、控制生成等

getSupportedAnnotationTypes()

使用@SupportedAnnotationTypes注解可以代替此方法,表示注解处理器支持的注解,需要传入注解的全称,可以指定集合

getSupportedSourceVersion()

使用@SupportedSourceVersion注解可以代替此方法,表示注解处理器支持的Java版本

init()

会被注解处理工具调用,并传入ProcessingEnviroment参数,ProcessingEnviroment提供很多有用的工具类,比如ElementsTypesFiler

process()

注解处理器的具体处理逻辑实现方法,方法中包含扫描、检查、处理注解以及生成代码的具体逻辑代码,RoundEnvironment参数提供查询被注解元素

getCompletions()

提供IDE使用,用户用于使用注解时提供使用建议

自定义注解处理器

通过继承AbstractProcessor抽象类并重写方法(实现javax.annotation.processing.Processor接口也可以自定义注解处理器,但继承抽象类更简单)实现自定义注解处理器,自定义注解处理器需要定义在单独的项目中,打包为依赖文件,提供给需要使用的项目引入依赖

创建新的processor工程,在项目中创建自定义注解及注解处理器

创建自定义注解@Factory

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface Factory {
}

创建自定义注解处理器CustomProcessor

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.annotation.Factory")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomProcessor extends AbstractProcessor {

    private static final String SUFFIX = "Factory";

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                "Only interfaces can be annotated with @Factory");
        for (Element element : roundEnv.getElementsAnnotatedWith(Factory.class)) {
            if (element instanceof TypeElement) {
                TypeElement typeElement = (TypeElement) element;

                // 获取注解中的值
                Factory factoryAnnotation = typeElement.getAnnotation(Factory.class);
                String factoryName = factoryAnnotation.value();

                // 检查被注解的类是否为接口
                if (!typeElement.getKind().isInterface()) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            "Only interfaces can be annotated with @Factory", typeElement);
                    return true;  // 停止处理该注解,继续处理其他注解
                }

                try {
                    // 生成工厂类
                    generateFactoryClass(typeElement, factoryName);
                } catch (IOException e) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            "Error generating factory class for " + typeElement.getSimpleName() + ": " + e.getMessage());
                }
            }
        }

        return true;
    }

    private void generateFactoryClass(TypeElement interfaceElement, String factoryName) throws IOException {
        // 获取接口的全限定名和包名
        String interfaceQualifiedName = interfaceElement.getQualifiedName().toString();
        String packageName = processingEnv.getElementUtils().getPackageOf(interfaceElement).getQualifiedName().toString();

        // 构建工厂类名
        String factoryClassName = interfaceElement.getSimpleName() + SUFFIX;

        // 创建源文件
        JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + "." + factoryClassName);
        PrintWriter writer = new PrintWriter(sourceFile.openWriter());

        // 写入包名和导入语句
        writer.println("package " + packageName + ";");
        writer.println();
        writer.println("import " + interfaceQualifiedName + ";");
        writer.println();

        // 写入工厂类代码
        writer.println("public class " + factoryClassName + " {");
        writer.println();
        writer.println("    public static " + interfaceElement.getSimpleName() + " create" +
                interfaceElement.getSimpleName() + "() {");
        writer.println("        return new " + interfaceQualifiedName + "() {");
        writer.println("            // TODO: Implement the interface methods");
        writer.println("        };");
        writer.println("    }");
        writer.println("}");

        writer.close();
    }
}

自定义注解处理器创建完成后,需要将其进行注册,注册处理器主要有以下方法:

1.在resource文件夹中新建META-INF文件夹,目录下新建services文件夹,目录下面创建javax.annotation.processing.Processor文件,再将自定义注解处理器全限定名称写入该文件中

2.通过引入Google注册注解处理器库,在自定义注解器中添加@AutoService(Processor.class)注解,会自动生成META-INF/services/javax.annotation.processing.Processor文件

<dependency>
    <groupId>com.google.auto.service</groupId>
    <artifactId>auto-service</artifactId>
</dependency>
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Factory {
    String value();
}

完成后打包为依赖,其他项目引入该依赖,使用定义的注解在编译阶段即可触发自定义处理器中的逻辑

@Factory("Example") //使用注解
public interface ExampleInterface {
}

编译时自动生成代码文件

public class ExampleInterfaceFactory {

    public static ExampleInterface createExampleInterface() {
        return new com.quest.ExampleInterface() {
            // TODO: Implement the interface methods
        };
    }
}

运行时处理注解

反射机制处理注解

注解机制的应用场景