加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

ANNOTATION PROCESSING 101 by Hannes Dorfmann — 10 Jan 2015

发布时间:2020-12-14 06:21:31 所属栏目:Java 来源:网络整理
导读:?原文地址:http://hannesdorfmann.com/annotation-processing/annotationprocessing101 In?this blog entry I would like to explain how to write an annotation processor. So here is my tutorial. First,I am going to explain to you what annotation

?原文地址:http://hannesdorfmann.com/annotation-processing/annotationprocessing101

In?this blog entry I would like to explain how to write an annotation processor. So here is my tutorial. First,I am going to explain to you what annotation processing is,what you can do with that powerful tool and finally what you cannot do with it. In a second step we will implement a simple annotation processor step by step.

To clarify a very important thing from the very beginning: we are not talking about evaluating annotations by using reflections at runtime (run time = the time when the application runs). Annotation processing takes place at compile time (compile time = the time when the java compiler compiles your java source code).

Annotation processing is a tool build in javac for scanning and processing annotations?at compile time. You can register your own annotation processor for certain annotations. At this point I assume that you already know what an annotation is and how to declare an annotation type. If you are not familar with annotations you can find more information in the. Annotation processing is already available since Java 5 but a useable API is available since Java 6 (released in December 2006). It took some time until the java world realized the power of annotation processing. So it has become popular in the last few years.

An annotation processor for a certain annotation takes java code (or compiled byte code) as input and generate files (usually .java files) as output. What does that exactly means? You can generate java code! The generated java code is in a generated??file. So you?can notmanipulate an existing java class for instance adding a method. The generated java file will be compiled by javac as any other hand written java source file.

Let’s have a look at the Processor API. Every Processor extends from??as follows:

<span class="kd">public <span class="kd">class <span class="nc">MyProcessor <span class="kd">extends <span class="n">AbstractProcessor <span class="o">{

<span class="nd"&gt;@Override
<span class="kd"&gt;public <span class="kd"&gt;synchronized <span class="kt"&gt;void <span class="n"&gt;init<span class="o"&gt;(<span class="n"&gt;ProcessingEnvironment <span class="n"&gt;env<span class="o"&gt;){ <span class="o"&gt;}

<span class="nd"&gt;@Override
<span class="kd"&gt;public <span class="kt"&gt;boolean <span class="n"&gt;process<span class="o"&gt;(<span class="n"&gt;Set<span class="o"&gt;<? <span class="kd"&gt;extends <span class="n"&gt;TypeElement<span class="o"&gt;> <span class="n"&gt;annoations<span class="o"&gt;,<span class="n"&gt;RoundEnvironment <span class="n"&gt;env<span class="o"&gt;) <span class="o"&gt;{ <span class="o"&gt;}

<span class="nd"&gt;@Override
<span class="kd"&gt;public <span class="n"&gt;Set<span class="o"&gt;<<span class="n"&gt;String<span class="o"&gt;> <span class="n"&gt;getSupportedAnnotationTypes<span class="o"&gt;() <span class="o"&gt;{ <span class="o"&gt;}

<span class="nd"&gt;@Override
<span class="kd"&gt;public <span class="n"&gt;SourceVersion <span class="n"&gt;getSupportedSourceVersion<span class="o"&gt;() <span class="o"&gt;{ <span class="o"&gt;}

<span class="o">}

  • : Every annotation processor class?must have an empty constructor. However,there is a special init() method which is invoked by the annotation processing tool with the??as parameter. The ProcessingEnviroment provides some useful util classes?,??and?. We will use them later.
  • annotations,RoundEnvironment env): This is kind of??method of each processor. Here you write your code for scanning,evaluating and processing annotations and generating java files. With??passed as parameter you can query for elements annotated with a certain annotation as we will see later.
  • : Here you have to specify for which annotations this annotation processor should be registered for. Note that the return type is a set of strings containing full qualified names for your annotation types you want to process with this annotation processor. In other words,you define here for which annotations you register your annotation processor.
  • : Used to specify which java version you use. Usually you will return?. However,you could also return??if you have good reasons for stick with Java 6. I recommend to use

With Java 7 you could also use annotations instead of overriding?and??like that:

<span class="nd"&gt;@Override
<span class="kd"&gt;public <span class="kd"&gt;synchronized <span class="kt"&gt;void <span class="n"&gt;init<span class="o"&gt;(<span class="n"&gt;ProcessingEnvironment <span class="n"&gt;env<span class="o"&gt;){ <span class="o"&gt;}

<span class="nd"&gt;@Override
<span class="kd"&gt;public <span class="kt"&gt;boolean <span class="n"&gt;process<span class="o"&gt;(<span class="n"&gt;Set<span class="o"&gt;<? <span class="kd"&gt;extends <span class="n"&gt;TypeElement<span class="o"&gt;> <span class="n"&gt;annoations<span class="o"&gt;,<span class="n"&gt;RoundEnvironment <span class="n"&gt;env<span class="o"&gt;) <span class="o"&gt;{ <span class="o"&gt;}

<span class="o">}

For compatibility reasons,especially for android,I recommend to override?and??instead of using?and?

The next thing you have to know is that the annotation processor runs in it’s own jvm. Yes you read correctly. javac starts a complete java virtual machine for running annotation processors. So what that means for you? You can use anything you would use in any other java application. Use guava! If you want to you can use dependency injection tools like dagger or any other library you want to. But don’t forget. Even if it’s just a small processor you should take care about efficient algorithms and design patterns like you would do for any other java application.

You may ask yourself?“How do I register MyProcessor to javac?”. You have to provide a?file. Like any other .jar file you pack your (compiled) annotation processor in that file. Furthermore you also have to pack a special file called?located in??in your .jar file. So the content of your .jar file looks like this:

<span class="o"&gt;- <span class="n"&gt;META<span class="o"&gt;-<span class="n"&gt;INF
    <span class="o"&gt;- <span class="n"&gt;services
        <span class="o"&gt;- <span class="n"&gt;javax<span class="o"&gt;.<span class="na"&gt;annotation<span class="o"&gt;.<span class="na"&gt;processing<span class="o"&gt;.<span class="na"&gt;Processor</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>

The content of the file??(packed in MyProcessor.jar) is a list with full qualified class names to the processors with new line as delimiter:

With??in your buildpath javac automatically detects and reads the?file and registers??as annotation processor.

It’s time to for a concrete example. We will use maven as our build system and dependency management tool. If you are not familiar with maven,don’t worry maven is not necessary. The whole code can be found?.

First of all I have to say,that it’s not so easy to find a simple problem for a tutorial that we can solve with an annotation processor. Here we gonna implement a very simple factory pattern (not abstract factory pattern). It should give you just a brief introduction on the annotation processing API. So the problem statement may be a little bit dump and not a real world one. Once again,you will learn about annotation processing and not about design patterns.

So here is the problem: We want to implement a pizza store. The pizza store offers to it’s customers 2 Pizzas (“Margherita” and “Calzone”) and Tiramisu for dessert.

Have a look at this code snippets,which should not need any further explanation:

<span class="kd">public <span class="kd">class <span class="nc">MargheritaPizza <span class="kd">implements <span class="n">Meal <span class="o">{

<span class="nd">@Override <span class="kd">public <span class="kt">float <span class="n">getPrice<span class="o">() <span class="o">{
<span class="k">return <span class="mf">6.0f<span class="o">;
<span class="o">}
<span class="o">}

<span class="kd">public <span class="kd">class <span class="nc">CalzonePizza <span class="kd">implements <span class="n">Meal <span class="o">{

<span class="nd">@Override <span class="kd">public <span class="kt">float <span class="n">getPrice<span class="o">() <span class="o">{
<span class="k">return <span class="mf">8.5f<span class="o">;
<span class="o">}
<span class="o">}

<span class="kd">public <span class="kd">class <span class="nc">Tiramisu <span class="kd">implements <span class="n">Meal <span class="o">{

<span class="nd">@Override <span class="kd">public <span class="kt">float <span class="n">getPrice<span class="o">() <span class="o">{
<span class="k">return <span class="mf">4.5f<span class="o">;
<span class="o">}
<span class="o">}

To order in our??the customer has to enter the name of the meal:

<span class="kd">public <span class="n">Meal <span class="n">order<span class="o">(<span class="n">String <span class="n">mealName<span class="o">) <span class="o">{

<span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;mealName <span class="o"&gt;== <span class="kc"&gt;null<span class="o"&gt;) <span class="o"&gt;{
  <span class="k"&gt;throw <span class="k"&gt;new <span class="n"&gt;IllegalArgumentException<span class="o"&gt;(<span class="s"&gt;"Name of the meal is null!"<span class="o"&gt;);
<span class="o"&gt;}

<span class="k"&gt;if <span class="o"&gt;(<span class="s"&gt;"Margherita"<span class="o"&gt;.<span class="na"&gt;equals<span class="o"&gt;(<span class="n"&gt;mealName<span class="o"&gt;)) <span class="o"&gt;{
  <span class="k"&gt;return <span class="k"&gt;new <span class="n"&gt;MargheritaPizza<span class="o"&gt;();
<span class="o"&gt;}

<span class="k"&gt;if <span class="o"&gt;(<span class="s"&gt;"Calzone"<span class="o"&gt;.<span class="na"&gt;equals<span class="o"&gt;(<span class="n"&gt;mealName<span class="o"&gt;)) <span class="o"&gt;{
  <span class="k"&gt;return <span class="k"&gt;new <span class="n"&gt;CalzonePizza<span class="o"&gt;();
<span class="o"&gt;}

<span class="k"&gt;if <span class="o"&gt;(<span class="s"&gt;"Tiramisu"<span class="o"&gt;.<span class="na"&gt;equals<span class="o"&gt;(<span class="n"&gt;mealName<span class="o"&gt;)) <span class="o"&gt;{
  <span class="k"&gt;return <span class="k"&gt;new <span class="n"&gt;Tiramisu<span class="o"&gt;();
<span class="o"&gt;}

<span class="k"&gt;throw <span class="k"&gt;new <span class="n"&gt;IllegalArgumentException<span class="o"&gt;(<span class="s"&gt;"Unknown meal '" <span class="o"&gt;+ <span class="n"&gt;mealName <span class="o"&gt;+ <span class="s"&gt;"'"<span class="o"&gt;);

<span class="o">}

<span class="kd">public <span class="kd">static <span class="kt">void <span class="n">main<span class="o">(<span class="n">String<span class="o">[] <span class="n">args<span class="o">) <span class="kd">throws <span class="n">IOException <span class="o">{
<span class="n">PizzaStore <span class="n">pizzaStore <span class="o">= <span class="k">new <span class="n">PizzaStore<span class="o">();
<span class="n">Meal <span class="n">meal <span class="o">= <span class="n">pizzaStore<span class="o">.<span class="na">order<span class="o">(<span class="n">readConsole<span class="o">());
<span class="n">System<span class="o">.<span class="na">out<span class="o">.<span class="na">println<span class="o">(<span class="s">"Bill: $" <span class="o">+ <span class="n">meal<span class="o">.<span class="na">getPrice<span class="o">());
<span class="o">}
<span class="o">}

As you see,we have a lot of?if statements?in the??method and whenever we add a new type of pizza we have to add a new if statement. But wait,with annotation processing and the factory pattern we can let an annotation processor generate this?if statements. So what we want to have is something like that:

<span class="kd">private <span class="n">MealFactory <span class="n">factory <span class="o">= <span class="k">new <span class="n">MealFactory<span class="o">();

<span class="kd">public <span class="n">Meal <span class="n">order<span class="o">(<span class="n">String <span class="n">mealName<span class="o">) <span class="o">{
<span class="k">return <span class="n">factory<span class="o">.<span class="na">create<span class="o">(<span class="n">mealName<span class="o">);
<span class="o">}

<span class="kd">public <span class="kd">static <span class="kt">void <span class="n">main<span class="o">(<span class="n">String<span class="o">[] <span class="n">args<span class="o">) <span class="kd">throws <span class="n">IOException <span class="o">{
<span class="n">PizzaStore <span class="n">pizzaStore <span class="o">= <span class="k">new <span class="n">PizzaStore<span class="o">();
<span class="n">Meal <span class="n">meal <span class="o">= <span class="n">pizzaStore<span class="o">.<span class="na">order<span class="o">(<span class="n">readConsole<span class="o">());
<span class="n">System<span class="o">.<span class="na">out<span class="o">.<span class="na">println<span class="o">(<span class="s">"Bill: $" <span class="o">+ <span class="n">meal<span class="o">.<span class="na">getPrice<span class="o">());
<span class="o">}
<span class="o">}

The??should look as follows:

<span class="kd">public <span class="n">Meal <span class="n">create<span class="o">(<span class="n">String <span class="n">id<span class="o">) <span class="o">{
<span class="k">if <span class="o">(<span class="n">id <span class="o">== <span class="kc">null<span class="o">) <span class="o">{
<span class="k">throw <span class="k">new <span class="n">IllegalArgumentException<span class="o">(<span class="s">"id is null!"<span class="o">);
<span class="o">}
<span class="k">if <span class="o">(<span class="s">"Calzone"<span class="o">.<span class="na">equals<span class="o">(<span class="n">id<span class="o">)) <span class="o">{
<span class="k">return <span class="k">new <span class="n">CalzonePizza<span class="o">();
<span class="o">}

<span class="k"&gt;if <span class="o"&gt;(<span class="s"&gt;"Tiramisu"<span class="o"&gt;.<span class="na"&gt;equals<span class="o"&gt;(<span class="n"&gt;id<span class="o"&gt;)) <span class="o"&gt;{
  <span class="k"&gt;return <span class="k"&gt;new <span class="n"&gt;Tiramisu<span class="o"&gt;();
<span class="o"&gt;}

<span class="k"&gt;if <span class="o"&gt;(<span class="s"&gt;"Margherita"<span class="o"&gt;.<span class="na"&gt;equals<span class="o"&gt;(<span class="n"&gt;id<span class="o"&gt;)) <span class="o"&gt;{
  <span class="k"&gt;return <span class="k"&gt;new <span class="n"&gt;MargheritaPizza<span class="o"&gt;();
<span class="o"&gt;}

<span class="k"&gt;throw <span class="k"&gt;new <span class="n"&gt;IllegalArgumentException<span class="o"&gt;(<span class="s"&gt;"Unknown id = " <span class="o"&gt;+ <span class="n"&gt;id<span class="o"&gt;);

<span class="o">}
<span class="o">}


<h2 id="factory-annotation">@Factory Annotation

Guess what: We want to generate the??by using annotation processing. To be more general,we want to provide an annotation and a processor for generating factory classes.

Let’s have a look at the??annotation:

<span class="cm">/**

  • The name of the factory
    */
    <span class="n">Class <span class="n">type<span class="o">();

<span class="cm">/**

  • The identifier for determining which item should be instantiated
    */
    <span class="n">String <span class="n">id<span class="o">();
    <span class="o">}

The idea is that we annotate classes which should belong to the same factory with the same?and with??we do the mapping from??to??class. Let’s apply?to our classes:



<span class="nd">@Override <span class="kd">public <span class="kt">float <span class="n">getPrice<span class="o">() <span class="o">{
<span class="k">return <span class="mi">6<span class="n">f<span class="o">;
<span class="o">}
<span class="o">}

<span class="nd">@Override <span class="kd">public <span class="kt">float <span class="n">getPrice<span class="o">() <span class="o">{
<span class="k">return <span class="mf">8.5f<span class="o">;
<span class="o">}
<span class="o">}

<span class="nd">@Override <span class="kd">public <span class="kt">float <span class="n">getPrice<span class="o">() <span class="o">{
<span class="k">return <span class="mf">4.5f<span class="o">;
<span class="o">}
<span class="o">}

You may ask yourself if we could just apply??on the??interface. Annotations are not inherited. Annotating??with an annotation does not mean that??is automatically annotated. Before we start writing the processor code we have to specify some rules:

  1. Only classes can be annotated with??since interfaces or abstract classes can not be instantiated with the??operator.
  2. Classes annotated with??must provide at least one public empty default constructor (parameterless). Otherwise we could not instantiate a new instance.
  3. Classes annotated with??must inherit directly or indirectly from the specified?(or implement it if it’s an interface).
  4. ?annotations with the same??are grouped together and one Factory class will be generated. The name of the generated class has?“Factory”?as suffix,for example??will generate?
  5. ?are limited to Strings and must be unique in it’s type group.

I will guide you step by step by adding line of code followed by an explanation paragraph. Three dots () means that code is omitted either was discussed in the paragraph before or will be added later as next step. Goal is to make the snipped more readable. As already mentioned above the complete code can be found?. Ok lets start with the skeleton of our?:

<span class="kd">private <span class="n">Types <span class="n">typeUtils<span class="o">;
<span class="kd">private <span class="n">Elements <span class="n">elementUtils<span class="o">;
<span class="kd">private <span class="n">Filer <span class="n">filer<span class="o">;
<span class="kd">private <span class="n">Messager <span class="n">messager<span class="o">;
<span class="kd">private <span class="n">Map<span class="o"><<span class="n">String<span class="o">,<span class="n">FactoryGroupedClasses<span class="o">> <span class="n">factoryClasses <span class="o">= <span class="k">new <span class="n">LinkedHashMap<span class="o"><<span class="n">String<span class="o">,<span class="n">FactoryGroupedClasses<span class="o">>();

<span class="nd">@Override
<span class="kd">public <span class="kd">synchronized <span class="kt">void <span class="n">init<span class="o">(<span class="n">ProcessingEnvironment <span class="n">processingEnv<span class="o">) <span class="o">{
<span class="kd">super<span class="o">.<span class="na">init<span class="o">(<span class="n">processingEnv<span class="o">);
<span class="n">typeUtils <span class="o">= <span class="n">processingEnv<span class="o">.<span class="na">getTypeUtils<span class="o">();
<span class="n">elementUtils <span class="o">= <span class="n">processingEnv<span class="o">.<span class="na">getElementUtils<span class="o">();
<span class="n">filer <span class="o">= <span class="n">processingEnv<span class="o">.<span class="na">getFiler<span class="o">();
<span class="n">messager <span class="o">= <span class="n">processingEnv<span class="o">.<span class="na">getMessager<span class="o">();
<span class="o">}

<span class="nd">@Override
<span class="kd">public <span class="n">Set<span class="o"><<span class="n">String<span class="o">> <span class="n">getSupportedAnnotationTypes<span class="o">() <span class="o">{
<span class="n">Set<span class="o"><<span class="n">String<span class="o">> <span class="n">annotataions <span class="o">= <span class="k">new <span class="n">LinkedHashSet<span class="o"><<span class="n">String<span class="o">>();
<span class="n">annotataions<span class="o">.<span class="na">add<span class="o">(<span class="n">Factory<span class="o">.<span class="na">class<span class="o">.<span class="na">getCanonicalName<span class="o">());
<span class="k">return <span class="n">annotataions<span class="o">;
<span class="o">}

<span class="nd">@Override
<span class="kd">public <span class="n">SourceVersion <span class="n">getSupportedSourceVersion<span class="o">() <span class="o">{
<span class="k">return <span class="n">SourceVersion<span class="o">.<span class="na">latestSupported<span class="o">();
<span class="o">}

<span class="nd">@Override
<span class="kd">public <span class="kt">boolean <span class="n">process<span class="o">(<span class="n">Set<span class="o"><? <span class="kd">extends <span class="n">TypeElement<span class="o">> <span class="n">annotations<span class="o">,<span class="n">RoundEnvironment <span class="n">roundEnv<span class="o">) <span class="o">{
<span class="o">...
<span class="o">}
<span class="o">}

In the first line you see?. What’s that? It’s an annotation from another annotation processor. This??annotation processor has been developed by Google and generates the??file. Yes,you read correctly. We can use annotation processors in our annotation processor. Handy,isn’t it? In??we specify that??is processed by this processor.

In??we retrieve a reference to

  • Elements: A utils class to work with??classes (more information later).
  • Types: A utils class to work with??(more information later)
  • Filer: Like the name suggests with Filer you can create files.

In annotation processing we are scanning java source code files. Every part of the source code is a certain type of?. In other words:??represents a program element such as a package,class,or method. Each element represents a static,language-level construct. In the following example I have added comments to clarify that:

<span class="kd">public <span class="kd">class <span class="nc">Foo <span class="o">{       <span class="c1">// TypeElement

<span class="kd"&gt;private <span class="kt"&gt;int <span class="n"&gt;a<span class="o"&gt;;        <span class="c1"&gt;// VariableElement
<span class="kd"&gt;private <span class="n"&gt;Foo <span class="n"&gt;other<span class="o"&gt;;     <span class="c1"&gt;// VariableElement

<span class="kd"&gt;public <span class="n"&gt;Foo <span class="o"&gt;() <span class="o"&gt;{}   <span class="c1"&gt;// ExecuteableElement

<span class="kd"&gt;public <span class="kt"&gt;void <span class="n"&gt;setA <span class="o"&gt;(    <span class="c1"&gt;// ExecuteableElement
                 <span class="kt"&gt;int <span class="n"&gt;newA    <span class="c1"&gt;// TypeElement
                 <span class="o"&gt;) <span class="o"&gt;{}

<span class="o">}

You have to change the way you see source code. It’s just structured text. It’s not executable. You can think of it like a XML file you try to parse (or an abstract syntax tree in compiler construction). Like in XML parsers there is some kind of DOM with elements. You can navigate from Element to it’s parent or child Element.

For instance if you have a??representing??you could iterate over its children like that:

As you see Elements are representing source code. TypeElement represent type elements in the source code like classes. However,TypeElement does not contain information about the class itself. From TypeElement you will get the name of the class,but you will not get information about the class like the superclass. This is kind of information are accessible through a?. You can access the TypeMirror of an Element by calling?.

So lets implement the??method step by step. First we start with searching for classes annotated with?:

<span class="kd">private <span class="n">Types <span class="n">typeUtils<span class="o">;
<span class="kd">private <span class="n">Elements <span class="n">elementUtils<span class="o">;
<span class="kd">private <span class="n">Filer <span class="n">filer<span class="o">;
<span class="kd">private <span class="n">Messager <span class="n">messager<span class="o">;
<span class="kd">private <span class="n">Map<span class="o"><<span class="n">String<span class="o">,<span class="n">FactoryGroupedClasses<span class="o">>();
<span class="o">...

<span class="nd">@Override
<span class="kd">public <span class="kt">boolean <span class="n">process<span class="o">(<span class="n">Set<span class="o"><? <span class="kd">extends <span class="n">TypeElement<span class="o">> <span class="n">annotations<span class="o">,<span class="n">RoundEnvironment <span class="n">roundEnv<span class="o">) <span class="o">{

<span class="c1"&gt;// Itearate over all @Factory annotated elements
<span class="k"&gt;for <span class="o"&gt;(<span class="n"&gt;Element <span class="n"&gt;annotatedElement <span class="o"&gt;: <span class="n"&gt;roundEnv<span class="o"&gt;.<span class="na"&gt;getElementsAnnotatedWith<span class="o"&gt;(<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;)) <span class="o"&gt;{
    <span class="o"&gt;...
<span class="o"&gt;}

<span class="o">}

<span class="o">...

<span class="o">}

No rocket science here.??returnes a list of Elements annotated with?. You may have noted that I have avoited saying?“returns list of classes annotated with @Factory”,because it really returns list of?. Remember:?can be a class,method,variable etc. So what we have to do next is to check if the Element is a class:

 <span class="k"&gt;for <span class="o"&gt;(<span class="n"&gt;Element <span class="n"&gt;annotatedElement <span class="o"&gt;: <span class="n"&gt;roundEnv<span class="o"&gt;.<span class="na"&gt;getElementsAnnotatedWith<span class="o"&gt;(<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;)) <span class="o"&gt;{

  <span class="c1"&gt;// Check if a class has been annotated with @Factory
  <span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;annotatedElement<span class="o"&gt;.<span class="na"&gt;getKind<span class="o"&gt;() <span class="o"&gt;!= <span class="n"&gt;ElementKind<span class="o"&gt;.<span class="na"&gt;CLASS<span class="o"&gt;) <span class="o"&gt;{
        <span class="o"&gt;...
  <span class="o"&gt;}

<span class="o">}

<span class="o">...
<span class="o">}

What’s going on here? We want to ensure that only elements of type class are processed by our processor. Previously we have learned that classes are?. So why don’t we check?. That’s a wrong assumption because interfaces are TypeElement as well. So in annoation processing you should avoid?instanceofbut rather us??or??with TypeMirror.

In??we also retrieve a reference to?. A Messager provides the way for an annotation processor to report error messages,warnings and other notices. It’s not a logger for you,the developer of the annotation processor (even thought it can be used for that during development of the processor). Messager is used to write messages to the third party developer who uses your annotation processor in their projects. There are different levels of messages described in the?. Very important is??because this kind of message is used to indicate that our annotation processor has failed processing. Probably the third party developer is misusing our??annotation (i.e. annotated an interface with @Factory). The concept is a little bit different from traditional java application where you would throw an?. If you throw an exception in??then the jvm which runs annotation processing crashs (like any other java application) and the third party developer who is using our FactoryProcessor will get an error from javac with a hardly understandable Exception,because it contains the stacktrace of FactoryProcessor. Therefore Annotation Processor has this??class. It prints a pretty error message. Additionaly,you can link to the element who has raised this error. In modern IDEs like IntelliJ the third party developer can click on this error message and the IDE will jump to the source file and line of the third party developers project where the error source is.

Back to implementing the??method. We raise a error message if the user has annotated a non class with @Factory:

 <span class="k"&gt;for <span class="o"&gt;(<span class="n"&gt;Element <span class="n"&gt;annotatedElement <span class="o"&gt;: <span class="n"&gt;roundEnv<span class="o"&gt;.<span class="na"&gt;getElementsAnnotatedWith<span class="o"&gt;(<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;)) <span class="o"&gt;{

  <span class="c1"&gt;// Check if a class has been annotated with @Factory
  <span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;annotatedElement<span class="o"&gt;.<span class="na"&gt;getKind<span class="o"&gt;() <span class="o"&gt;!= <span class="n"&gt;ElementKind<span class="o"&gt;.<span class="na"&gt;CLASS<span class="o"&gt;) <span class="o"&gt;{
    <span class="n"&gt;error<span class="o"&gt;(<span class="n"&gt;annotatedElement<span class="o"&gt;,<span class="s"&gt;"Only classes can be annotated with @%s"<span class="o"&gt;,<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;.<span class="na"&gt;getSimpleName<span class="o"&gt;());
    <span class="k"&gt;return <span class="kc"&gt;true<span class="o"&gt;; <span class="c1"&gt;// Exit processing
  <span class="o"&gt;}

  <span class="o"&gt;...
<span class="o"&gt;}

<span class="kd">private <span class="kt">void <span class="nf">error<span class="p">(<span class="n">Element <span class="n">e<span class="o">,<span class="n">String <span class="n">msg<span class="o">,<span class="n">Object<span class="o">... <span class="n">args<span class="o">) <span class="o">{
<span class="n">messager<span class="o">.<span class="na">printMessage<span class="o">(
<span class="n">Diagnostic<span class="o">.<span class="na">Kind<span class="o">.<span class="na">ERROR<span class="o">,<span class="n">String<span class="o">.<span class="na">format<span class="o">(<span class="n">msg<span class="o">,<span class="n">args<span class="o">),<span class="n">e<span class="o">);
<span class="o">}

<span class="o">}

To get the message of the Messager displayed it’s?important that the annotation processor has to complete without crashing. That’s why we??after having called. If we don’t return here??will continue running since??does not stop the process. So it’s very likely that if we don’t return after printing the error we will run in an internal?NullPointerException?etc. if we continue in. As said before,the problem is that if an unhandled exception is thrown in?javac will print the stacktrace of the internal?NullPointerException?and NOT your error message of?.

Before we continue with checking if classes annotated with @Factory observe our five rules (see above) we are going to introduce data structures which makes it easier for us to continue. Sometimes the problem or processor seems to be so simple that programmers tend to write the whole processor in a procedural manner.?But you know what? An Annotation Processor is still a java application. So use object oriented programming,interfaces,design patterns and anything else you would use in any other java application!

Our FactoryProcessor is quite simple but there are some information we want to store as objects. With??we store the annotated class data like qualified class name along with the data of the @Factory annotation itself. So we store the TypeElement and evaluate the @Factory annotation:

<span class="kd">private <span class="n">TypeElement <span class="n">annotatedClassElement<span class="o">;
<span class="kd">private <span class="n">String <span class="n">qualifiedSuperClassName<span class="o">;
<span class="kd">private <span class="n">String <span class="n">simpleTypeName<span class="o">;
<span class="kd">private <span class="n">String <span class="n">id<span class="o">;

<span class="kd">public <span class="n">FactoryAnnotatedClass<span class="o">(<span class="n">TypeElement <span class="n">classElement<span class="o">) <span class="kd">throws <span class="n">IllegalArgumentException <span class="o">{
<span class="k">this<span class="o">.<span class="na">annotatedClassElement <span class="o">= <span class="n">classElement<span class="o">;
<span class="n">Factory <span class="n">annotation <span class="o">= <span class="n">classElement<span class="o">.<span class="na">getAnnotation<span class="o">(<span class="n">Factory<span class="o">.<span class="na">class<span class="o">);
<span class="n">id <span class="o">= <span class="n">annotation<span class="o">.<span class="na">id<span class="o">();

<span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;StringUtils<span class="o"&gt;.<span class="na"&gt;isEmpty<span class="o"&gt;(<span class="n"&gt;id<span class="o"&gt;)) <span class="o"&gt;{
  <span class="k"&gt;throw <span class="k"&gt;new <span class="n"&gt;IllegalArgumentException<span class="o"&gt;(
      <span class="n"&gt;String<span class="o"&gt;.<span class="na"&gt;format<span class="o"&gt;(<span class="s"&gt;"id() in @%s for class %s is null or empty! that's not allowed"<span class="o"&gt;,<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;.<span class="na"&gt;getSimpleName<span class="o"&gt;(),<span class="n"&gt;classElement<span class="o"&gt;.<span class="na"&gt;getQualifiedName<span class="o"&gt;().<span class="na"&gt;toString<span class="o"&gt;()));
<span class="o"&gt;}

<span class="c1"&gt;// Get the full QualifiedTypeName
<span class="k"&gt;try <span class="o"&gt;{
  <span class="n"&gt;Class<span class="o"&gt;<?> <span class="n"&gt;clazz <span class="o"&gt;= <span class="n"&gt;annotation<span class="o"&gt;.<span class="na"&gt;type<span class="o"&gt;();
  <span class="n"&gt;qualifiedSuperClassName <span class="o"&gt;= <span class="n"&gt;clazz<span class="o"&gt;.<span class="na"&gt;getCanonicalName<span class="o"&gt;();
  <span class="n"&gt;simpleTypeName <span class="o"&gt;= <span class="n"&gt;clazz<span class="o"&gt;.<span class="na"&gt;getSimpleName<span class="o"&gt;();
<span class="o"&gt;} <span class="k"&gt;catch <span class="o"&gt;(<span class="n"&gt;MirroredTypeException <span class="n"&gt;mte<span class="o"&gt;) <span class="o"&gt;{
  <span class="n"&gt;DeclaredType <span class="n"&gt;classTypeMirror <span class="o"&gt;= <span class="o"&gt;(<span class="n"&gt;DeclaredType<span class="o"&gt;) <span class="n"&gt;mte<span class="o"&gt;.<span class="na"&gt;getTypeMirror<span class="o"&gt;();
  <span class="n"&gt;TypeElement <span class="n"&gt;classTypeElement <span class="o"&gt;= <span class="o"&gt;(<span class="n"&gt;TypeElement<span class="o"&gt;) <span class="n"&gt;classTypeMirror<span class="o"&gt;.<span class="na"&gt;asElement<span class="o"&gt;();
  <span class="n"&gt;qualifiedSuperClassName <span class="o"&gt;= <span class="n"&gt;classTypeElement<span class="o"&gt;.<span class="na"&gt;getQualifiedName<span class="o"&gt;().<span class="na"&gt;toString<span class="o"&gt;();
  <span class="n"&gt;simpleTypeName <span class="o"&gt;= <span class="n"&gt;classTypeElement<span class="o"&gt;.<span class="na"&gt;getSimpleName<span class="o"&gt;().<span class="na"&gt;toString<span class="o"&gt;();
<span class="o"&gt;}

<span class="o">}

<span class="cm">/**

  • Get the id as specified in {@link Factory#id()}.
  • return the id
    */
    <span class="kd">public <span class="n">String <span class="n">getId<span class="o">() <span class="o">{
    <span class="k">return <span class="n">id<span class="o">;
    <span class="o">}

<span class="cm">/**

  • Get the full qualified name of the type specified in {@link Factory#type()}.
  • @return qualified name
    */
    <span class="kd">public <span class="n">String <span class="n">getQualifiedFactoryGroupName<span class="o">() <span class="o">{
    <span class="k">return <span class="n">qualifiedSuperClassName<span class="o">;
    <span class="o">}

<span class="cm">/**

  • Get the simple name of the type specified in {@link Factory#type()}.
  • @return qualified name
    */
    <span class="kd">public <span class="n">String <span class="n">getSimpleFactoryGroupName<span class="o">() <span class="o">{
    <span class="k">return <span class="n">simpleTypeName<span class="o">;
    <span class="o">}

<span class="cm">/**

  • The original element that was annotated with @Factory
    */
    <span class="kd">public <span class="n">TypeElement <span class="n">getTypeElement<span class="o">() <span class="o">{
    <span class="k">return <span class="n">annotatedClassElement<span class="o">;
    <span class="o">}
    <span class="o">}

Lot of code,but the most important thing happens ins the constructor where you find the following lines of code:



<span class="k">if <span class="o">(<span class="n">StringUtils<span class="o">.<span class="na">isEmpty<span class="o">(<span class="n">id<span class="o">)) <span class="o">{
<span class="k">throw <span class="k">new <span class="n">IllegalArgumentException<span class="o">(
<span class="n">String<span class="o">.<span class="na">format<span class="o">(<span class="s">"id() in @%s for class %s is null or empty! that's not allowed"<span class="o">,<span class="n">classElement<span class="o">.<span class="na">getQualifiedName<span class="o">().<span class="na">toString<span class="o">()));
<span class="o">}

Here we access the @Factory annotation and check if the id is not empty. We will throw an IllegalArgumentException if id is empty. You may be confused now because previously we said that we are not throwing exceptions but rather use?. That’s still correct. We throw an exception here internally and we will catch that one in??as you will see later. We do that for two reasons:

  1. I want to demonstrate that you should still code like in any other java application. Throwing and catching exceptions is considered as good practice in java.
  2. If we want to print a message right from??we also have to pass the?and as already mentioned in “Error Handling” (scroll up) the processor has to terminate successfully to make??print the error message. So if we would write an error message by using??how do we “notify”??that an error has occurred? The easiest and from my point of view most intuitive way is to throw an Exception and let?chatch this one.

Next we want to get the??field of the??annotation. We are interessted in the full qualified name.

 

That’s a little bit tricky,because the type is?. That means,that this is a real Class object. Since annotation processing runs before compiling java source code we have to consider two cases:

  1. The class is already compiled: This is the case if a third party .jar contains compiled .class files with??annotations. In that case we can directly access the Class like we do in the?.
  2. The class is not compiled yet: This will be the case if we try to compile our source code which has @Factory annotations. Trying to access the Class directly throws a. Fortunately MirroredTypeException contains a?representation of our not yet compiled class. Since we know that it must be type of class (we have already checked that before) we can cast it to??and access?to read the qualified name.

Alright,now we need one more datastructure named??which basically groups all??together.

<span class="kd">private <span class="n">String <span class="n">qualifiedClassName<span class="o">;

<span class="kd">private <span class="n">Map<span class="o"><<span class="n">String<span class="o">,<span class="n">FactoryAnnotatedClass<span class="o">> <span class="n">itemsMap <span class="o">=
<span class="k">new <span class="n">LinkedHashMap<span class="o"><<span class="n">String<span class="o">,<span class="n">FactoryAnnotatedClass<span class="o">>();

<span class="kd">public <span class="n">FactoryGroupedClasses<span class="o">(<span class="n">String <span class="n">qualifiedClassName<span class="o">) <span class="o">{
<span class="k">this<span class="o">.<span class="na">qualifiedClassName <span class="o">= <span class="n">qualifiedClassName<span class="o">;
<span class="o">}

<span class="kd">public <span class="kt">void <span class="n">add<span class="o">(<span class="n">FactoryAnnotatedClass <span class="n">toInsert<span class="o">) <span class="kd">throws <span class="n">IdAlreadyUsedException <span class="o">{

<span class="n"&gt;FactoryAnnotatedClass <span class="n"&gt;existing <span class="o"&gt;= <span class="n"&gt;itemsMap<span class="o"&gt;.<span class="na"&gt;get<span class="o"&gt;(<span class="n"&gt;toInsert<span class="o"&gt;.<span class="na"&gt;getId<span class="o"&gt;());
<span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;existing <span class="o"&gt;!= <span class="kc"&gt;null<span class="o"&gt;) <span class="o"&gt;{
  <span class="k"&gt;throw <span class="k"&gt;new <span class="n"&gt;IdAlreadyUsedException<span class="o"&gt;(<span class="n"&gt;existing<span class="o"&gt;);
<span class="o"&gt;}

<span class="n"&gt;itemsMap<span class="o"&gt;.<span class="na"&gt;put<span class="o"&gt;(<span class="n"&gt;toInsert<span class="o"&gt;.<span class="na"&gt;getId<span class="o"&gt;(),<span class="n"&gt;toInsert<span class="o"&gt;);

<span class="o">}

<span class="kd">public <span class="kt">void <span class="n">generateCode<span class="o">(<span class="n">Elements <span class="n">elementUtils<span class="o">,<span class="n">Filer <span class="n">filer<span class="o">) <span class="kd">throws <span class="n">IOException <span class="o">{
<span class="o">...
<span class="o">}
<span class="o">}

As you see it’s basically just a?. This map is used to map an @Factory.id() to FactoryAnnotatedClass. We have chosen??because we want to ensure that each id is unique. That can be easily done with a map lookup.??will be called to generate the Factory code (discussed later).

Let’s proceed with the implementation of?. Next we want to check if the annotated class has at least one public constructor,is not an abstract class,inherits the specified type and is a public class (visibility):

<span class="nd">@Override
<span class="kd">public <span class="kt">boolean <span class="n">process<span class="o">(<span class="n">Set<span class="o"><? <span class="kd">extends <span class="n">TypeElement<span class="o">> <span class="n">annotations<span class="o">,<span class="n">RoundEnvironment <span class="n">roundEnv<span class="o">) <span class="o">{

<span class="k"&gt;for <span class="o"&gt;(<span class="n"&gt;Element <span class="n"&gt;annotatedElement <span class="o"&gt;: <span class="n"&gt;roundEnv<span class="o"&gt;.<span class="na"&gt;getElementsAnnotatedWith<span class="o"&gt;(<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;)) <span class="o"&gt;{

  <span class="o"&gt;...

  <span class="c1"&gt;// We can cast it,because we know that it of ElementKind.CLASS
  <span class="n"&gt;TypeElement <span class="n"&gt;typeElement <span class="o"&gt;= <span class="o"&gt;(<span class="n"&gt;TypeElement<span class="o"&gt;) <span class="n"&gt;annotatedElement<span class="o"&gt;;

  <span class="k"&gt;try <span class="o"&gt;{
    <span class="n"&gt;FactoryAnnotatedClass <span class="n"&gt;annotatedClass <span class="o"&gt;=
        <span class="k"&gt;new <span class="n"&gt;FactoryAnnotatedClass<span class="o"&gt;(<span class="n"&gt;typeElement<span class="o"&gt;); <span class="c1"&gt;// throws IllegalArgumentException

    <span class="k"&gt;if <span class="o"&gt;(!<span class="n"&gt;isValidClass<span class="o"&gt;(<span class="n"&gt;annotatedClass<span class="o"&gt;)) <span class="o"&gt;{
      <span class="k"&gt;return <span class="kc"&gt;true<span class="o"&gt;; <span class="c1"&gt;// Error message printed,exit processing
     <span class="o"&gt;}
   <span class="o"&gt;} <span class="k"&gt;catch <span class="o"&gt;(<span class="n"&gt;IllegalArgumentException <span class="n"&gt;e<span class="o"&gt;) <span class="o"&gt;{
    <span class="c1"&gt;// @Factory.id() is empty
    <span class="n"&gt;error<span class="o"&gt;(<span class="n"&gt;typeElement<span class="o"&gt;,<span class="n"&gt;e<span class="o"&gt;.<span class="na"&gt;getMessage<span class="o"&gt;());
    <span class="k"&gt;return <span class="kc"&gt;true<span class="o"&gt;;
   <span class="o"&gt;}

   <span class="o"&gt;...

<span class="o">}

<span class="kd">private <span class="kt">boolean <span class="n">isValidClass<span class="o">(<span class="n">FactoryAnnotatedClass <span class="n">item<span class="o">) <span class="o">{

<span class="c1"&gt;// Cast to TypeElement,has more type specific methods
<span class="n"&gt;TypeElement <span class="n"&gt;classElement <span class="o"&gt;= <span class="n"&gt;item<span class="o"&gt;.<span class="na"&gt;getTypeElement<span class="o"&gt;();

<span class="k"&gt;if <span class="o"&gt;(!<span class="n"&gt;classElement<span class="o"&gt;.<span class="na"&gt;getModifiers<span class="o"&gt;().<span class="na"&gt;contains<span class="o"&gt;(<span class="n"&gt;Modifier<span class="o"&gt;.<span class="na"&gt;PUBLIC<span class="o"&gt;)) <span class="o"&gt;{
  <span class="n"&gt;error<span class="o"&gt;(<span class="n"&gt;classElement<span class="o"&gt;,<span class="s"&gt;"The class %s is not public."<span class="o"&gt;,<span class="n"&gt;classElement<span class="o"&gt;.<span class="na"&gt;getQualifiedName<span class="o"&gt;().<span class="na"&gt;toString<span class="o"&gt;());
  <span class="k"&gt;return <span class="kc"&gt;false<span class="o"&gt;;
<span class="o"&gt;}

<span class="c1"&gt;// Check if it's an abstract class
<span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;classElement<span class="o"&gt;.<span class="na"&gt;getModifiers<span class="o"&gt;().<span class="na"&gt;contains<span class="o"&gt;(<span class="n"&gt;Modifier<span class="o"&gt;.<span class="na"&gt;ABSTRACT<span class="o"&gt;)) <span class="o"&gt;{
  <span class="n"&gt;error<span class="o"&gt;(<span class="n"&gt;classElement<span class="o"&gt;,<span class="s"&gt;"The class %s is abstract. You can't annotate abstract classes with @%"<span class="o"&gt;,<span class="n"&gt;classElement<span class="o"&gt;.<span class="na"&gt;getQualifiedName<span class="o"&gt;().<span class="na"&gt;toString<span class="o"&gt;(),<span class="n"&gt;Factory<span class="o"&gt;.<span class="na"&gt;class<span class="o"&gt;.<span class="na"&gt;getSimpleName<span class="o"&gt;());
  <span class="k"&gt;return <span class="kc"&gt;false<span class="o"&gt;;
<span class="o"&gt;}

<span class="c1"&gt;// Check inheritance: Class must be childclass as specified in @Factory.type();
<span class="n"&gt;TypeElement <span class="n"&gt;superClassElement <span class="o"&gt;=
    <span class="n"&gt;elementUtils<span class="o"&gt;.<span class="na"&gt;getTypeElement<span class="o"&gt;(<span class="n"&gt;item<span class="o"&gt;.<span class="na"&gt;getQualifiedFactoryGroupName<span class="o"&gt;());
<span class="k"&gt;if <span class="o"&gt;(<span class="n"&gt;superClassElement<span class="o"&gt;.<span class="na"&gt;getKind<span class="o"&gt;() <span class="o"&gt;== <span class="n"&gt;ElementKind<span class="o"&gt;.<span class="na"&gt;INTERFACE<span class="o"&gt;) <span class="o"&gt;{
  <span class="c1"&gt;// Check interface implemented
  <span class="k"&gt;if <span class="o"&gt;(!<span class="n"&gt;classElement<span class="o"&gt;.<span class="na"&gt;getInterfaces<span class="o"&gt;().<span class="na"&gt;contains<span class="o"&gt;(<span class="n"&gt;superClassElement<span class="o"&gt;.<span class="na"&gt;asType<span class="o"&gt;())) <span class="o"&gt;{
    <span class="n"&gt;error<span class="o"&gt;(<span class="n"&gt;classElement<span class="o"&gt;,<span class="s"&gt;"The class %s annotated with @%s must implement the interface %s"<span class="o"&gt;,<span class="n"&gt;item<span class="o"&gt;.<span class="na"&gt;getQualifiedFactoryGroupName<span class="o"&gt;());
    <span class="k"&gt;return <span class="kc"&gt;false<span class="o"&gt;;
  <span class="o"&gt;}
<span class="o"&gt;} <span class="k"&gt;else <span class="o"&gt;{
  <span class="c1"&gt;// Check subclassing
  <span class="n"&gt;TypeElement <span class="n"&gt;currentClass <span class="o"&gt;= <span class="n"&gt;classElement<span class="o"&gt;;
  <span class="k"&gt;while <span class="o"&gt;(<span class="kc"&gt;true<span class="o" class="o"&gt;) <span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读