Java 注解 注解(Annotation)是Java语言中一种特殊的修饰符,它可以用于类、方法、参数、变量、构造器以及包声明中,用于为Java代码提供元数据。 相对于其他修饰符如public、final等,注解并不直接影响代码的语义,但却能被某些工具软件(如编译器、框架)所读取和利用。 注解为Java提供了一种元级编程的方式,它允许开发者在不修改代码逻辑的前提下,向源码中添加一些额外的信息,这些信息可以被编译器或其他工具所读取并使用。 ====================================================================================================================== 注解的主要作用 注解是一种用于在代码中添加元数据的机制。它们不会直接影响代码的执行,但可以通过工具或框架在编译时、运行时或部署时读取和处理这些元数据。 注解的主要作用包括: 代码配置:通过注解简化配置文件,减少重复代码。 代码生成:在编译时生成额外的代码,如代理类、序列化代码等。 运行时处理:在运行时读取注解信息,实现动态行为,如依赖注入、日志记录等。 文档生成:自动生成文档,如Javadoc。 ====================================================================================================================== 注解的基本语法 注解的声明类似于接口的声明,但是前面多了一个@符号: public @interface MyAnnotation { String value() default ""; //定义value属性 } 使用注解: @MyAnnotation(value="This is my custom annotation") public class MyClass { // class body } ====================================================================================================================== 元注解 用于注解其他注解的注解称为元注解。Java提供了以下几种元注解: @Target: 表明该注解可以被应用于什么地方(如方法、类、字段等)。 @Retention: 表明该注解的生命周期(仅源代码、编译期、运行期)。 @Documented: 表示使用该注解的元素应被Javadoc或其他工具文档化。 @Inherited: 表示该注解可以被子类继承。 1.@Target: 作用:描述定义的注解可以在哪里(包、类、接口、类成员方法、类成员变量、方法参数、局部变量、枚举类型)使用。不写就是表示可以用在所有位置上。 JDK定义了ElementType枚举类型用于描述注解可以出现的位置,ElementType有如下枚举值: ElementType.TYPE:能修饰类、接口或枚举类型,但是不能放在属性等其他位置 ElementType.FIELD:注解仅用于添加到成员变量 ElementType.METHOD:注解仅用于添加到成员方法 ElementType.PARAMETER:注解仅用于添加到方法参数 ElementType.CONSTRUCTOR:注解仅用于添加到构造方法 ElementType.LOCAL_VARIABLE:注解仅用于添加到局部变量 ElementType.ANNOTATION_TYPE:注解仅用于添加到注解(元注解) ElementType.PACKAGE:注解仅用于添加到包 2.@Retention 作用:描述注解的作用范围,即注解的有效范围。表示生命周期。告诉注解信息保留到哪个阶段,如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS。 JDK定义了三种有效范围,分别是:注解作用于源代码(编译器可读取注解)、注解作用于类文件(在类文件中可读取注解)、注解作用于运行过程(用反射技术读取注解)。 JDK定义了RetentionPolicy枚举类型用于描述注解作用范围,RetentionPolicy有如下枚举值: RetentionPolicy.SOURCE:注解作用于源代码(编译器要丢弃的注解) RetentionPolicy.CLASS:注解作用于类文件(编译器将把注解记录在类文件中,但在运行时 JVM 不需要保留注释) RetentionPolicy.RUNTIME:注解作用于运行时(编译器将把注解记录在类文件中,在运行时JVM 将保留注解,因此可以反射性地读取) 3.@Documented 作用:表示是否将我们的注解生成在Javadoc中。如果一个注解定义时间使用了该元注解,那么产生的javadoc文档就会把注解显示出来。 4.@Inherited 作用:表示该注解具有继承性。用于子类是可以继承父类中的注解 ====================================================================================================================== 自定义注解 在 Java 中,注解是一种为代码添加元数据的方式。当我们使用 @interface 关键字定义新的注解时,背后的一些细节会由编译器自动完成,例如自动继承 java.lang.annotation.Annotation 接口 注解的定义规则: 在定义新注解时,不能继承其他注解或接口。 @interface 关键字用于声明一个新的注解。 在注解内,每个方法都代表一个配置参数。 方法名为参数名。 返回值类型定义了参数的类型。 方法可以没有参数。 可以使用 default 关键字为参数设定默认值。 基本数据类型:如 int, float, boolean, byte, double, char, long, short String Class 枚举类型 (Enum) 其他注解 (Annotation) 以上所有类型的数组形式 @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Tag { //定义一个名字为value的元素,当名字为value时,使用注解可以直接赋值,不需要指定元素名 例如:@Tag("this is value"); String value(); // 定义一个名为name的元素,它的默认值为"undefined" String name() default "undefined"; // 定义一个名为description的元素,使用该注解时,这个元素是必须要设置的,因为它没有默认值 String description(); } public @interface MyAnnotation { String value(); int count() default 1; } @MyAnnotation(value = "example", count = 5) public class MyClass { @MyAnnotation("field") private String myField; @MyAnnotation(value = "method", count = 10) public void myMethod() { // Method implementation } } ====================================================================================================================== 注解处理器 注解处理器用于在编译时或运行时读取和处理注解信息。Java提供了两种主要的注解处理机制:编译时注解处理器(Annotation Processor)和运行时注解处理器(Reflection)。 注解处理器的用途 自动化代码生成:允许开发者定义规则和模式,然后让机器自动为我们生成代码,避免了人为错误和重复劳动。 编译时检查:通过注解处理器,我们可以在编译时进行更多的检查,确保代码的质量和准确性。 性能:由于大部分工作在编译时完成,运行时的性能开销被最小化。 代码整洁:自动生成的代码可以分离到另外的文件中,保持主要业务逻辑的代码清晰和整洁。 应用场景 类属性自动赋值: 自动注入对象或值,例如Spring框架中的@Autowired和@Value。 验证对象属性完整性: 例如,使用Java Bean Validation API(如@NotNull, @Size, @Min, @Max等)进行对象属性验证。 代替配置文件功能: 通过注解配置,如Spring的@Component, @Service, @Repository, @Controller等。 生成文档: Javadoc工具使用注解来生成API文档。 编译时检查: 例如,@Override用于告诉编译器子类中的某个方法是否正确地重写了父类中的方法。 代码生成: 编译时注解处理器可以用于生成额外的源代码。 运行时处理: 通过反射API在运行时读取注解信息,如Spring AOP、JUnit等。 如何工作 在编译阶段,javac会扫描每个源文件中的注解。 对于每个找到的注解,javac会查询是否有已注册的注解处理器可以处理它。 如果有,javac将调用该处理器来处理注解。处理器可以分析注解、读取注解的值,并根据这些值生成额外的源代码或其他文件。 编译时注解处理器 编译时注解处理器在编译阶段读取和处理注解信息,生成额外的代码或执行其他操作。编译时注解处理器通过Java的javax.annotation.processing包实现。 AbstractProcessor AbstractProcessor 是一个提供便捷实现注解处理器功能的抽象类,实现了 Processor 接口,都位于 javax.annotation.processing 包中。 自定义注解处理器的主要步骤: 实现 Processor 接口: 通过继承 AbstractProcessor 类来实现自定义的注解处理器。主要的工作在于实现 process 方法,进行你想要完成的注解处理功能。 在 init() 方法中,你可以获得: Elements: 一个处理 Element 的工具类。 Types: 用来处理 TypeMirror 的工具类。 Filer: 用于创建文件。 在注解处理过程中,会扫描所有的 Java 源文件。每个部分都是一个特定类型的 Element,代表程序的元素,例如包、类或者方法。主要的子类有: PackageElement: 代表一个包。 TypeElement: 代表一个类或接口。 VariableElement: 表示变量,如成员变量、枚举常量、方法或构造方法参数等。 ExecutableElement: 表示类或接口的方法、构造方法。 TypeParameterElement: 表示类型参数。 注册注解处理器: 有两种方法: 手动注册: 在项目的 resources/META-INF/services 目录下新建文件 javax.annotation.processing.Processor,内容是处理器的全称。 自动注册: 使用 Google 提供的库,添加 com.google.auto.service:auto-service 依赖,并在处理器上添加 @AutoService(Processor.class) 注解。这会自动在 META-INF 目录下生成配置信息文件。 定义注解处理器 import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import java.util.Set; @SupportedAnnotationTypes("com.example.MyAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) { MyAnnotation annotation = element.getAnnotation(MyAnnotation.class); System.out.println("Annotation found: " + annotation.value() + ", count: " + annotation.count()); } return true; } } 注意:在 Java 6 及以上版本中,可以使用 @SupportedAnnotationTypes 注解和 @SupportedSourceVersion 注解来分别替代 getSupportedAnnotationTypes() 方法和 getSupportedSourceVersion() 方法。 /** * 自定义注解处理器 */ public class CustomProcessor extends AbstractProcessor { private Filer mFiler; private Messager mMessager; private Elements mElementUtils; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFiler = processingEnvironment.getFiler(); mMessager = processingEnvironment.getMessager(); mElementUtils = processingEnvironment.getElementUtils(); } @Override public Set getSupportedAnnotationTypes() { Set annotataions = new LinkedHashSet(); annotataions.add(Tag.class.getCanonicalName()); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set annotations, RoundEnvironment roundEnvironment) { Set tagElements = roundEnvironment.getElementsAnnotatedWith(Tag.class); for (Element element : tagElements) { // 1.获取包名 PackageElement packageElement = mElementUtils.getPackageOf(element); String pkName = packageElement.getQualifiedName().toString(); printMessage(String.format("package = %s", pkName)); // 2.获取包装类类型 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String enclosingName = enclosingElement.getQualifiedName().toString(); printMessage(String.format("enclosindClass = %s", enclosingName)); // 3.获取注解的成员变量名 String tagFiledName = element.getSimpleName().toString(); // 4.获取注解的成员变量类型 String tagFiledClassType = element.asType().toString(); // 5.获取注解元数据 Tag tag = element.getAnnotation(Tag.class); String name = tag.name(); printMessage(String.format("%s %s = %s", tagFiledClassType, tagFiledName, name)); // 6.生成文件 createFile(enclosingElement, tagFiledClassType, tagFiledName, name); return true; } return false; } private void createFile(TypeElement enclosingElement, String tagFiledClassType, String tagFiledName, String name) { String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString(); try { JavaFileObject javaFileObject = mFiler.createSourceFile(pkName + ".Tag"); Writer writer = javaFileObject.openWriter(); writer.write(generateCode(pkName, tagFiledClassType, tagFiledName, name)); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } private void printMessage(String msg) { mMessager.printMessage(Diagnostic.Kind.NOTE, msg); } private String generateCode(String pkName, String tagFiledClassType, String tagFiledName, String name) { StringBuilder builder = new StringBuilder(); builder.append("package " + pkName + ";\n\n"); builder.append("//Auto generated by apt,do not modify!!\n\n"); builder.append("public class Tag { \n\n"); builder.append("public static void main(String[] args){ \n"); String info = String.format("%s %s = %s", tagFiledClassType, tagFiledName, name); builder.append("System.out.println(\"" + info + "\");\n"); builder.append("}\n"); builder.append("}"); return builder.toString(); } } 注册注解处理器 在META-INF/services目录下创建一个名为javax.annotation.processing.Processor的文件,内容为注解处理器的全限定名。 com.example.MyAnnotationProcessor 运行时注解处理器 运行时注解处理器在运行阶段通过反射读取和处理注解信息。反射是Java提供的一种机制,允许在运行时检查和操作类、方法、字段等程序元素。 使用反射读取注解 import java.lang.reflect.Method; public class AnnotationReader { public static void main(String[] args) throws Exception { Class clazz = Class.forName("com.example.MyClass"); Method method = clazz.getMethod("myMethod"); if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); System.out.println("Annotation found: " + annotation.value() + ", count: " + annotation.count()); } } } ====================================================================================================================== 一个自定义注解处理器的例子,自定义注解处理器 并打包成processor.jar使用 开发环境Android Studio 1、建立一个Android项目Test 2、建立一个java library,File->New->New Module->Java Or Kotlin Library 3、 新建注解文件: @Retention(RetentionPolicy.CLASS) public @interface CustomAnnotation { } 新建注解处理器: @AutoService(Processor.class) public class CustomAnnotationProcessor extends AbstractProcessor { Elements elementUtils; Filer mFiler; Messager mMessager; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set getSupportedAnnotationTypes() { Set annotations = new LinkedHashSet<>(); annotations.add(CustomAnnotation.class.getCanonicalName()); return annotations; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils = processingEnv.getElementUtils(); mFiler = processingEnv.getFiler(); mMessager = processingEnv.getMessager(); } @Override public boolean process(Set set, RoundEnvironment roundEnvironment) { System.out.printf("abstractProcessor process\n "); Set elements = roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class); if(elements.isEmpty()){ return false; } // generateCode(elements); generateCodeByJavapoet(elements); return true; } //使用javapoet库,使用MethodSpec,TypeSpec等方法,可以不需要手写生成的文件代码 private void generateCodeByJavapoet(Set elements){ StringBuilder stringBuilder = new StringBuilder(); for(Element element : elements){ stringBuilder.append(element.getClass().getSimpleName().toString()); } MethodSpec.Builder builder = MethodSpec.methodBuilder("getMessage") .addModifiers(Modifier.PUBLIC) .addParameter(TypeName.INT,"id"); TypeSpec injectClass = TypeSpec.classBuilder("GenerateClass") .addModifiers(Modifier.PUBLIC) .addMethod(builder.build()) .build(); String packageName = "com.newpos.processors"; try { JavaFile.builder(packageName,injectClass).build().writeTo(mFiler); } catch (IOException e) { throw new RuntimeException(e); } } private void generateCode(Set elements){ StringBuilder builder = new StringBuilder(); builder.append("package com.newpos.processors.generated;\n\n") .append("public class GeneratedClass{\n\n") .append("\tpublic String getMessage(){\n") .append("\t\treturn \""); for(Element element : elements){ String objectType= element.getSimpleName().toString(); builder.append(objectType) .append(" say hello !"); } builder.append("\";\n") .append("\t}\n") .append("}\n"); try { JavaFileObject source = processingEnv.getFiler().createSourceFile("com.newpos.processors.generated.GeneratedClass"); Writer writer = source.openWriter(); writer.write(builder.toString()); writer.flush(); writer.close(); } catch (IOException e) { throw new RuntimeException(e); } } } 上例中在类的名字上使用了注解@AutoService(Processor.class),这样会自动为我们的注解处理器 在META-INF/services目录下创建一个名为javax.annotation.processing.Processor的文件,内容为注解处理器的全限定名。 使用此注解需要引入google库 java library的 build.gradle代码 plugins { id 'java-library' id 'com.github.johnrengelman.shadow' version '7.0.0' } java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } dependencies { annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' compileOnly 'com.google.auto.service:auto-service:1.0-rc4' implementation 'com.squareup:javapoet:1.7.0' } //控制台执行 ./gradlew shadowJar会自动打包 需要修改app依赖本library shadowJar { //方式一,打包jar并自动包含依赖库,这样会导致依赖库体积过大 } 其中使用了shadow 使用此插件是为了把javapoet依赖jar一起打包到processor.jar中 app 中build.gradle代码 dependencies { implementation files('libs\\processors-all.jar') annotationProcessor files('libs\\processors-all.jar') } 把打包好的jar放到app的libs下面,在代码中直接引入注解,然后rebuild项目会自动生成java文件 app中的代码 @CustomAnnotation public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(activityMainBinding.getRoot()); activityMainBinding.tvText.setText("abc"); test4(); } @CustomAnnotation private void test4(){ GenerateClass generateClass = new GenerateClass(); generateClass.getMessage(1); } } 自动生成的代码文件绝对路径 Test\app\build\generated\ap_generated_sources\debug\out\com\newpos\processors\GenerateClass.java package com.newpos.processors; public class GenerateClass { public void getMessage(int id) { } }