Table Of Contents

1. Introduction
2. When to Use Annotation Processors
3. Annotation Processing Under the Hood
4. Writing Your Own Annotation Processor
5. Running Annotation Processors
6. What’s next
7. Download the source code

1. Introduction

In this part of the tutorial we are going to demystify the magic of annotation processing, which is often used to inspect, modify or generate source code, driven only by annotations. Essentially, annotation processors are some kind of plugins of the Java compiler. Annotation processors used wisely could significantly simplify the life of Java developers so that is why they are often bundled with many popular libraries and frameworks.

Being compiler plugins also means that annotation processors are a bit low-level and highly depend on the version of Java. However, the knowledge about annotations from the part 5 of the tutorial How and when to use Enums and Annotations and Java Compiler API from the part 13 of the tutorial, Java Compiler API, is going to be very handy in the understanding of intrinsic details of how the annotation processors work.

2. When to Use Annotation Processors

As we briefly mentioned, annotations processors are typically used to inspect the codebase against the presence of particular annotations and, depending on use case, to:

  • generate a set of source or resource files
  • mutate (modify) the existing source code
  • analyze the exiting source code and generate diagnostic messages

The usefulness of annotation processors is hard to overestimate. They can significantly reduce the amount of code which developers have to write (by generating or modifying one), or, by doing static analysis, hint the developers if the assumptions expressed by a particular annotation are not being hold.

Being somewhat invisible to the developers, annotation processors are supported in full by all modern Java IDEs and popular building tools and generally do not require any particular intrusion. In the next section of the tutorial we are going to build own somewhat naïve annotation processors which nonetheless will show off the full power of this Java compiler feature.

3. Annotation Processing Under the Hood

Before diving into implementation of our own annotation processors, it is good to know the mechanics of that. Annotation processing happens in a sequence of rounds. On each round, an annotation processor might be asked to process a subset of the annotations which were found on the source and class files produced by a prior round.

Please notice that, if an annotation processor was asked to process on a given round, it will be asked to process on subsequent rounds, including the last round, even if there are no annotations for it to process.

In essence, any Java class could become a full-blow annotation processor just by implementing a single interface: javax.annotation.processing.Processor. However, to become really usable, each implementation of the javax.annotation.processing.Processor must provide a public no-argument constructor (for more details, please refer to the part 1 of the tutorial, How to create and destroy objects) which could be used to instantiate the processor. The processing infrastructure will follow a set of rules to interact with an annotation processor and the processor must respect this protocol:

  • the instance of the annotation processor is created using the no-argument constructor of the processor class
  • the init method is being called with an appropriatejavax.annotation.processing.ProcessingEnvironment instance being passed
  • the getSupportedAnnotationTypesgetSupportedOptions, and getSupportedSourceVersion methods are being called (these methods are only called once per run, not on each round)
  • and lastly, as appropriate, the process method on the javax.annotation.processing.Processor is being called (please take into account that a new annotation processor instance is not going to be created for each round)

The Java documentation emphasizes that if annotation processor instance is created and used without the above protocol being followed then the processor’s behavior is not defined by this interface specification.

4. Writing Your Own Annotation Processor

We are going to develop several kinds of annotation processors, starting from the simplest one, immutability checker. Let us define a simple annotation Immutable which we are going to use in order to annotate the class to ensure it does not allow to modify its state.

1 @Target( ElementType.TYPE )
2 @Retention( RetentionPolicy.CLASS )
3 public @interface Immutable {
4 }

Following the retention policy, the annotation is going to be retained by Java compiler in the class file during the compilation phase however it will not be (and should not be) available at runtime.

As we already know from part 3 of the tutorial, How to design Classes and Interfaces, immutability is really hard in Java. To keep things simple, our annotation processor is going to verify that all fields of the class are declared as final. Luckily, the Java standard library provides an abstract annotation processor, javax.annotation.processing.AbstractProcessor, which is designed to be a convenient superclass for most concrete annotation processors. Let us take a look on SimpleAnnotationProcessor annotation processor implementation.

01 @SupportedAnnotationTypes("com.javacodegeeks.advanced.processor.Immutable" )
02 @SupportedSourceVersion( SourceVersion.RELEASE_7 )
03 public class SimpleAnnotationProcessor extendsAbstractProcessor {
04   @Override
05   public boolean process(final Set< ? extends TypeElement > annotations,
06       final RoundEnvironment roundEnv) {
07          
08     forfinal Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {
09       if( element instanceof TypeElement ) {
10         final TypeElement typeElement = ( TypeElement )element;
11                  
12         forfinal Element eclosedElement: typeElement.getEnclosedElements() ) {
13        if( eclosedElement instanceof VariableElement ) {
14            final VariableElement variableElement = ( VariableElement )eclosedElement;
15          
16            if( !variableElement.getModifiers().contains( Modifier.FINAL ) ) {
17              processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR,
18                String.format( "Class '%s' is annotated as@Immutable,
19                  but field '%s' is not declared as final",
20                  typeElement.getSimpleName(), variableElement.getSimpleName()           
21                )
22              );                    
23            }
24          }
25        }
26     }
27          
28     // Claiming that annotations have been processed by this processor
29     return true;
30   }
31 }

The SupportedAnnotationTypes annotation is probably the most important detail which defines what kind of annotations this annotation processor is interested in. It is possible to use * here to handle all available annotations.

Because of the provided scaffolding, our SimpleAnnotationProcessor has to implement only a single method, process. The implementation itself is pretty straightforward and basically just verifies if class being processed has any field declared without final modifier. Let us take a look on an example of the class which violates this naïve immutability contract.

01 @Immutable
02 public class MutableClass {
03     private String name;
04      
05     public MutableClass( final String name ) {
06         this.name = name;
07     }
08      
09     public String getName() {
10         return name;
11     }
12 }

Running the SimpleAnnotationProcessor against this class is going to output the following error on the console:

1 Class 'MutableClass' is annotated as @Immutable, but field'name' is not declared as final

Thus confirming that the annotation processor successfully detected the misuse of Immutableannotation on a mutable class.

By and large, performing some introspection (and code generation) is the area where annotation processors are being used most of the time. Let us complicate the task a little bit and apply some knowledge of Java Compiler API from the part 13 of the tutorial, Java Compiler API. The annotation processor we are going to write this time is going to mutate (or modify) the generated bytecode by adding the final modifier directly to the class field declaration to make sure this field will not be reassigned anywhere else.

01 @SupportedAnnotationTypes("com.javacodegeeks.advanced.processor.Immutable" )
02 @SupportedSourceVersion( SourceVersion.RELEASE_7 )
03 public class MutatingAnnotationProcessor extendsAbstractProcessor {
04   private Trees trees;
05      
06   @Override
07   public void init (ProcessingEnvironment processingEnv) {
08     super.init( processingEnv );
09     trees = Trees.instance( processingEnv );       
10   }
11      
12   @Override
13   public boolean process( final Set< ? extends TypeElement > annotations,
14       final RoundEnvironment roundEnv) {
15          
16     final TreePathScanner< Object, CompilationUnitTree > scanner =
17       new TreePathScanner< Object, CompilationUnitTree >() {
18         @Override
19          public Trees visitClass(final ClassTree classTree,
20            final CompilationUnitTree unitTree) {
21  
22          if (unitTree instanceof JCCompilationUnit) {
23            final JCCompilationUnit compilationUnit = ( JCCompilationUnit )unitTree;
24                          
25            // Only process on files which have been compiled from source
26            if (compilationUnit.sourcefile.getKind() == JavaFileObject.Kind.SOURCE) {
27              compilationUnit.accept(new TreeTranslator() {
28                public void visitVarDef( final JCVariableDecl tree ) {
29                  super.visitVarDef( tree );
30                                      
31                  if ( ( tree.mods.flags & Flags.FINAL ) == 0 ) {
32                    tree.mods.flags |= Flags.FINAL;
33                  }
34                }
35              });
36            }
37          }
38      
39         return trees;
40       }
41     };
42          
43     forfinal Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {   
44       final TreePath path = trees.getPath( element );
45       scanner.scan( path, path.getCompilationUnit() );
46     }
47          
48     // Claiming that annotations have been processed by this processor
49     return true;
50   }
51 }

The implementation became more complex, however many classes (like TreePathScanner,TreePath) should be already familiar. Running the annotation processor against the sameMutableClass class will generate following byte code (which could be verified by executing javap -p MutableClass.class command):

1 public classcom.javacodegeeks.advanced.processor.examples.MutableClass {
2   private final java.lang.String name;
3   publiccom.javacodegeeks.advanced.processor.examples.MutableClass(java.lang.String);
4   public java.lang.String getName();
5 }

Indeed, the name field has final modifier present nonetheless it was omitted in the original Java source file. Our last example is going to show off the code generation capabilities of annotation processors (and conclude the discussion). Continuing in the same vein, let us implement an annotation processor which will generate new source file (and new class respectively) by appending Immutable suffix to class name annotated with Immutableannotation.

01 @SupportedAnnotationTypes("com.javacodegeeks.advanced.processor.Immutable" )
02 @SupportedSourceVersion( SourceVersion.RELEASE_7 )
03 public class GeneratingAnnotationProcessor extendsAbstractProcessor {
04   @Override
05   public boolean process(final Set< ? extends TypeElement > annotations,
06       final RoundEnvironment roundEnv) {
07          
08     forfinal Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {
09       if( element instanceof TypeElement ) {
10         final TypeElement typeElement = ( TypeElement )element;
11         final PackageElement packageElement =
12           ( PackageElement )typeElement.getEnclosingElement();
13  
14         try {
15           final String className = typeElement.getSimpleName() + "Immutable";
16           final JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(
17             packageElement.getQualifiedName() + "." + className);
18                      
19           try( Writer writter = fileObject.openWriter() ) {
20             writter.append( "package " + packageElement.getQualifiedName() + ";" );
21             writter.append( "\\n\\n");
22             writter.append( "public class " + className + " {");
23             writter.append( "\\n");
24             writter.append( "}");
25           }
26         catchfinal IOException ex ) {
27           processingEnv.getMessager().printMessage(Kind.ERROR, ex.getMessage());
28         }
29       }
30     }
31          
32     // Claiming that annotations have been processed by this processor
33     return true;
34   }
35 }

As the result of injecting this annotation processor into compilation process of the MutableClassclass, the following file will be generated:

1 package com.javacodegeeks.advanced.processor.examples;
2  
3 public class MutableClassImmutable {
4 }

Nevertheless the source file and its class have been generated using primitive string concatenations (and it fact, this class is really very useless) the goal was to demonstrate how the code generation performed by annotation processors works so more sophisticated generation techniques may be applied.

5. Running Annotation Processors

The Java compiler makes it easy to plug any number of annotation processors into the compilation process by supporting –processor command line argument. For example, here is one way of running MutatingAnnotationProcessor by passing it as an argument of javac tool during the compilation of MutableClass.java source file:

1 javac -cp processors/target/advanced-java-part-14-java7.processors-0.0.1-SNAPSHOT.jar
2   -processor com.javacodegeeks.advanced.processor.MutatingAnnotationProcessor   
3   -d examples/target/classes
4   examples/src/main/java/com/javacodegeeks/advanced/processor/examples/MutableClass.java

Compiling just one file does not look very complicated but real-life projects contain thousands of Java source files and using javac tool from command line to compile those is just overkill. Likely, the community has developed a lot of great build tools (like Apache MavenGradle,sbtApache Ant, …), which take care of invoking Java compiler and doing a lot of other things, so nowadays most of Java project out there use at least one of them. Here, for example, is the way to invoke MutatingAnnotationProcessor from Apache Maven build file (pom.xml):

01 <plugin>
02   <groupId>org.apache.maven.plugins</groupId>
03   <artifactId>maven-compiler-plugin</artifactId>
04   <version>3.1</version>
05   <configuration>
06     <source>1.7</source>
07     <target>1.7</target>
08     <annotationProcessors>
09 <proc>com.javacodegeeks.advanced.processor.MutatingAnnotationProcessor</proc>
10     </annotationProcessors>
11   </configuration>
12 </plugin>

6. What’s next

In this part of the tutorial we have taken a deep look on annotation processors and the ways they help to inspect the source code, mutate (modify) resulting bytecode or generate new Java source files or resources. Annotation processors are very often used to free up Java developers from writing tons of boilerplate code by deriving it from annotations spread across codebase. In the next section of the tutorial we are going to take a look on Java agents and the way to manipulate how JVM interprets bytecode at runtime.

7. Download the source code

You can download the source code of this lesson here: advanced-java-part-14

Java Annotation Processors的更多相关文章

  1. Java Annotation概述

    @(Java)[Annotation|Java] Java Annotation概述 用途 编译器的相关信息,如用于检测错误和一些警告 编译时和部署时的处理,如一些软件用于自动生成代码之类的 运行时处 ...

  2. Java Annotation 注解

    java_notation.html div.oembedall-githubrepos { border: 1px solid #DDD; list-style-type: none; margin ...

  3. paip.Java Annotation注解的作用and 使用

    paip.Java Annotation注解的作用and 使用 作者Attilax 艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog. ...

  4. Java Annotation认知(包括框架图、详细介绍、示例说明)

    摘要 Java Annotation是JDK5.0引入的一种注释机制. 网上很多关于Java Annotation的文章,看得人眼花缭乱.Java Annotation本来很简单的,结果说的人没说清楚 ...

  5. Java Annotation原理分析(一)

    转自:http://blog.csdn.net/blueheart20/article/details/18725801 小引: 在当下的Java语言层面上,Annotation已经被应用到了语言的各 ...

  6. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

  7. Java Annotation 机制源码分析与使用

    1 Annotation 1.1 Annotation 概念及作用      1.  概念 An annotation is a form of metadata, that can be added ...

  8. Java Annotation手册

    Java Annotation手册 作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig) 原文:http://www.matri ...

  9. Java Annotation 必须掌握的特性

    什么是Annotation? Annotation翻译为中文即为注解,意思就是提供除了程序本身逻辑外的额外的数据信息.Annotation对于标注的代码没有直接的影响,它不可以直接与标注的代码产生交互 ...

随机推荐

  1. Spring AOP 实现读写分离

    原文地址:Spring AOP 实现读写分离 博客地址:http://www.extlight.com 一.前言 上一篇<MySQL 实现主从复制> 文章中介绍了 MySQL 主从复制的搭 ...

  2. Windows Redis安装,Java操作Redis

    一.Redis 的安装 1.Redis 下载 Windows 版本下载:https://github.com/dmajkic/redis/downloads 2.解压到 C:\redis-2.4.5- ...

  3. xhprof使用

    一.下载安装 wget http://pecl.php.net/get/xhprof-0.9.3.tgz tar zxvf xhprof-0.9.3.tgz cd xhprof-0.9.3/exten ...

  4. C# 与 Oracle 中 BINARY_DOUBLE数据类型查询

    Oracle 10g新增 BINARY_DOUBLE 数据类型,而.NET暂不支持这个类型,查询时需要转换为 NUMBER. eg: "SELECT RAWTOHEX(OID) AS OID ...

  5. C++ 构造函数_内存分区_对象初始化

    内存分区 栈区:int  x = 0:int  *p = NULL; 定义一个变量,定义一个指针时,会在栈区进行分配内存.分配的内存系统分配收回的,我们不用管. 堆区:int  *p = new  i ...

  6. 安卓控件获取器uiautomatorviewer初体验:"unable to connect to the adb. check if adb is installed correctly"

    解决方法:转自:https://plus.google.com/108487870030743970488/posts/2TrMqs1ZGQv Challenge Accepted:1. Screen ...

  7. eclipse+maven springMVC搭建

    1.新建项目: 选择Maven Project 选择项目位置,这里我选择的是C:\Users\admin\workspace\practice 选择maven项目类型,这里选择webapp: 填写Gr ...

  8. Selenium2+python自动化64-100(大结局)[已出书]

    前言 小编曾经说过要写100篇关于selenium的博客文章,前面的64篇已经免费放到博客园供小伙伴们学习,后面的内容就不放出来了,高阶内容直接更新到百度阅读了. 一.百度阅读地址: 1.本书是在线阅 ...

  9. Hyberledger-Fabric 1.00 RPC学习(1)

    参考:http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html 本文的目的就是基于Hyperledger Fabr ...

  10. 2个版本并存的python使用新的版本安装django的方法

    2个版本并存的python使用新的版本安装django的方法 默认是使用 pip install django 最新版的django会提示  要求python版本3.4以上,系统默认的版本是2.7.5 ...