java处理注释_Java注释处理器

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方法
  • 正在调用getSupportedAnnotationTypesgetSupportedOptionsgetSupportedSourceVersion方法(这些方法每次运行仅调用一次,而不是在每个回合中调用一次)
  • 最后,在适当时调用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;
  }
}

实现变得更加复杂,但是许多类(例如TreePathScannerTreePath )应该已经很熟悉了。 对同一个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 MavenGradlesbtApache 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处理注释

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值