java处理注释
本文是我们名为“ 高级Java ”的学院课程的一部分。
本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程! 在这里查看 !
1.简介
在本部分的教程中,我们将揭开注释处理的魔力,它通常用于检查,修改或生成仅由注释驱动的源代码。 本质上,注释处理器是Java编译器的某种插件。 明智地使用注释处理器可以大大简化Java开发人员的工作,因此这就是为什么它们通常与许多流行的库和框架捆绑在一起的原因。
作为编译器插件还意味着注释处理器有点底层,并且高度依赖Java版本。 但是,本教程的第5部分中的有关注释的知识以及本教程的第13 部分中的如何以及何时使用Enums和Annotations和Java编译器API ,对理解Java编译器的内在细节将非常有用。注释处理器如何工作。
2.何时使用注释处理器
正如我们简要提到的那样,批注处理器通常用于检查代码库是否存在特定的批注,并根据用例来:
- 生成一组源文件或资源文件
- 更改(修改)现有源代码
- 分析现有的源代码并生成诊断消息
注释处理器的有用性很难高估。 它们可以显着减少开发人员必须编写的代码量(通过生成或修改一个),或者通过进行静态分析来提示开发人员是否不满足特定注释所表示的假设。
对于开发人员来说,注解处理器几乎是不可见的,它被所有现代Java IDE和流行的构建工具完全支持,并且通常不需要任何特定的入侵。 在本教程的下一部分中,我们将构建自己的有些天真的注释处理器,尽管如此,它们仍将展示此Java编译器功能的全部功能。
3.后台处理注释
在深入研究自己的注释处理器的实现之前,最好先了解一下它的机制。 批注处理按一系列回合进行。 在每一轮中,可能会要求注释处理器处理在上一轮产生的源文件和类文件中找到的注释子集。
请注意,如果要求注释处理器在给定回合中进行处理,则即使没有注释要处理,也将要求其在后续回合中进行处理,包括最后一轮。
本质上,任何Java类都可以通过实现单个接口javax.annotation.processing.Processor
成为全功能注释处理器。 但是,要真正变得可用, javax.annotation.processing.Processor
每个实现都必须提供一个公共的无参数构造函数(有关更多详细信息,请参阅教程的第1部分 , 如何创建和销毁对象 ),该方法可以用于实例化处理器。 处理基础结构将遵循一组规则以与注释处理器进行交互,并且处理器必须遵守以下协议:
- 使用处理器类的无参数构造函数创建注释处理器的实例
- 通过适当的
javax.annotation.processing.ProcessingEnvironment
实例调用init
方法 - 正在调用
getSupportedAnnotationTypes
,getSupportedOptions
和getSupportedSourceVersion
方法(这些方法每次运行仅调用一次,而不是在每个回合中调用一次) - 最后,在适当时调用
javax.annotation.processing.Processor
上的处理方法(请考虑到不会为每个回合创建新的注释处理器实例)
Java文档强调,如果在未遵循上述协议的情况下创建和使用注释处理器实例,则该接口规范不会定义处理器的行为。
4.编写自己的注释处理器
我们将从最简单的一种不变性检查器开始,开发几种注释处理器。 让我们定义一个简单的注释Immutable
,我们将使用它来注释该类,以确保它不允许修改其状态。
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.CLASS )
public @interface Immutable {
}
遵循保留策略,注释将在编译阶段由Java编译器保留在类文件中,但在运行时将不可用(也不应使用)。
从本教程的第3部分“ 如何设计类和接口”中我们已经知道,不可变性在Java中确实很难。 为简单起见,我们的注释处理器将验证该类的所有字段都声明为final。 幸运的是,Java标准库提供了一个抽象注释处理器javax.annotation.processing.AbstractProcessor
,它被设计为大多数具体注释处理器的便捷超类。 让我们看一下SimpleAnnotationProcessor注释处理器的实现。
@SupportedAnnotationTypes( "com.javacodegeeks.advanced.processor.Immutable" )
@SupportedSourceVersion( SourceVersion.RELEASE_7 )
public class SimpleAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(final Set< ? extends TypeElement > annotations,
final RoundEnvironment roundEnv) {
for( final Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {
if( element instanceof TypeElement ) {
final TypeElement typeElement = ( TypeElement )element;
for( final Element eclosedElement: typeElement.getEnclosedElements() ) {
if( eclosedElement instanceof VariableElement ) {
final VariableElement variableElement = ( VariableElement )eclosedElement;
if( !variableElement.getModifiers().contains( Modifier.FINAL ) ) {
processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR,
String.format( "Class '%s' is annotated as @Immutable,
but field '%s' is not declared as final",
typeElement.getSimpleName(), variableElement.getSimpleName()
)
);
}
}
}
}
// Claiming that annotations have been processed by this processor
return true;
}
}
SupportedAnnotationTypes
注释可能是最重要的细节,它定义了此注释处理器感兴趣的注释类型。可以在此处使用*来处理所有可用的注释。
由于提供了脚手架,因此我们的SimpleAnnotationProcessor
只需要实现一个方法process
。 实现本身非常简单,基本上只验证要处理的类是否声明了没有final
修饰符的任何字段。 让我们看一下违反该天真不变性契约的类的示例。
@Immutable
public class MutableClass {
private String name;
public MutableClass( final String name ) {
this.name = name;
}
public String getName() {
return name;
}
}
针对此类运行SimpleAnnotationProcessor
将在控制台上输出以下错误:
Class 'MutableClass' is annotated as @Immutable, but field 'name' is not declared as final
因此,确认注释处理器成功检测到可变类上Immutable
注释的滥用。
总的来说,执行自省(和代码生成)是大部分时间使用注释处理器的领域。 让我们复杂的任务一点点,从本教程中,该部13应用的Java编译器API的一些知识的Java编译器API 。 我们这次要编写的注释处理器将通过将final
修饰符直接添加到类字段声明中来更改(或修改)生成的字节码,以确保不会在其他任何地方重新分配该字段。
@SupportedAnnotationTypes( "com.javacodegeeks.advanced.processor.Immutable" )
@SupportedSourceVersion( SourceVersion.RELEASE_7 )
public class MutatingAnnotationProcessor extends AbstractProcessor {
private Trees trees;
@Override
public void init (ProcessingEnvironment processingEnv) {
super.init( processingEnv );
trees = Trees.instance( processingEnv );
}
@Override
public boolean process( final Set< ? extends TypeElement > annotations,
final RoundEnvironment roundEnv) {
final TreePathScanner< Object, CompilationUnitTree > scanner =
new TreePathScanner< Object, CompilationUnitTree >() {
@Override
public Trees visitClass(final ClassTree classTree,
final CompilationUnitTree unitTree) {
if (unitTree instanceof JCCompilationUnit) {
final JCCompilationUnit compilationUnit = ( JCCompilationUnit )unitTree;
// Only process on files which have been compiled from source
if (compilationUnit.sourcefile.getKind() == JavaFileObject.Kind.SOURCE) {
compilationUnit.accept(new TreeTranslator() {
public void visitVarDef( final JCVariableDecl tree ) {
super.visitVarDef( tree );
if ( ( tree.mods.flags & Flags.FINAL ) == 0 ) {
tree.mods.flags |= Flags.FINAL;
}
}
});
}
}
return trees;
}
};
for( final Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {
final TreePath path = trees.getPath( element );
scanner.scan( path, path.getCompilationUnit() );
}
// Claiming that annotations have been processed by this processor
return true;
}
}
实现变得更加复杂,但是许多类(例如TreePathScanner
, TreePath
)应该已经很熟悉了。 对同一个MutableClass
类运行注释处理器将生成以下字节码(可以通过执行javap -p MutableClass.class
命令来验证):
public class com.javacodegeeks.advanced.processor.examples.MutableClass {
private final java.lang.String name;
public com.javacodegeeks.advanced.processor.examples.MutableClass(java.lang.String);
public java.lang.String getName();
}
实际上, name
字段具有final
修饰符,但在原始Java源文件中已将其省略。 我们的最后一个示例将展示注释处理器的代码生成功能(并结束讨论)。 同样,让我们实现一个注释处理器,该处理器将通过将Immutable
后缀附加到使用Immutable
注释进行注释的类名来生成新的源文件(分别是新类)。
@SupportedAnnotationTypes( "com.javacodegeeks.advanced.processor.Immutable" )
@SupportedSourceVersion( SourceVersion.RELEASE_7 )
public class GeneratingAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(final Set< ? extends TypeElement > annotations,
final RoundEnvironment roundEnv) {
for( final Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {
if( element instanceof TypeElement ) {
final TypeElement typeElement = ( TypeElement )element;
final PackageElement packageElement =
( PackageElement )typeElement.getEnclosingElement();
try {
final String className = typeElement.getSimpleName() + "Immutable";
final JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(
packageElement.getQualifiedName() + "." + className);
try( Writer writter = fileObject.openWriter() ) {
writter.append( "package " + packageElement.getQualifiedName() + ";" );
writter.append( "\\n\\n");
writter.append( "public class " + className + " {" );
writter.append( "\\n");
writter.append( "}");
}
} catch( final IOException ex ) {
processingEnv.getMessager().printMessage(Kind.ERROR, ex.getMessage());
}
}
}
// Claiming that annotations have been processed by this processor
return true;
}
}
作为将此注释处理器注入MutableClass
类的编译过程的结果,将生成以下文件:
package com.javacodegeeks.advanced.processor.examples;
public class MutableClassImmutable {
}
不过,源文件及其类是使用原始字符串连接生成的(事实上,该类确实非常无用),目的是演示注释处理器执行的代码生成是如何工作的,因此可以应用更复杂的生成技术。
5.运行注释处理器
Java编译器通过支持–processor命令行参数,可以轻松地将任意数量的注释处理器插入到编译过程中。 例如,这是通过在MutableClass.java
源文件的编译期间将其作为javac
工具的参数传递而运行MutatingAnnotationProcessor
的一种方法:
javac -cp processors/target/advanced-java-part-14-java7.processors-0.0.1-SNAPSHOT.jar
-processor com.javacodegeeks.advanced.processor.MutatingAnnotationProcessor
-d examples/target/classes
examples/src/main/java/com/javacodegeeks/advanced/processor/examples/MutableClass.java
仅编译一个文件看起来并不复杂,但是现实生活中的项目包含成千上万个Java源文件,而从命令行使用javac
工具编译这些文件实在是太过分了。 社区很可能已经开发了很多很棒的构建工具(例如Apache Maven , Gradle , sbt , Apache Ant等等),这些工具负责调用Java编译器并做很多其他事情,因此,如今大多数Java项目在那里至少使用其中之一。 例如,以下是从Apache Maven构建文件( pom.xml )调用MutatingAnnotationProcessor
的方法:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<annotationProcessors>
<proc>com.javacodegeeks.advanced.processor.MutatingAnnotationProcessor</proc>
</annotationProcessors>
</configuration>
</plugin>
6.接下来
在本教程的这一部分中,我们对注解处理器及其帮助检查源代码,变异(修改)结果字节码或生成新的Java源文件或资源的方式进行了深入研究。 批注处理器通常用于使Java开发人员从遍布整个代码库的批注中派生出来,从而免于编写大量样板代码。 在本教程的下一部分中,我们将介绍Java代理以及操作JVM在运行时解释字节码的方式。
7.下载源代码
您可以在此处下载本课程的源代码: advanced-java-part-14
翻译自: https://www.javacodegeeks.com/2015/09/java-annotation-processors.html
java处理注释