1. Some basic concepts
Before we start, we need to declare an important thing: we are not discussing the annotations that are processed through reflection mechanisms at runtime, but discussing the annotations that are processed at compile time.
What is the difference between compile-time annotations and run-time annotations? In fact, it is not big, mainly because of performance issues. Runtime annotations mainly rely entirely on reflection, and the efficiency of reflection is slower than that of the native ones, so there will be some lag on machines with less memory and poor CPU. However, the annotation will not have this problem at all, because in our compilation process (java->class), some annotation marks are used to dynamically generate some classes or files, so it has nothing to do with our APK operation, so there is naturally no performance problem. Therefore, if a more famous open source project uses annotation function, it usually uses compile-time annotation.
Annotation processor is a tool that comes with javac, which is used to scan and process annotation information during the compilation period. You can register your own annotation processor for certain annotations. Here, I assume you already understand what annotations are and how to customize them. If you haven't understood the annotations yet, you can check the official documentation. Annotation processors already existed in Java 5, but there was no API available until Java 6 (released in December 2006). It took a while before Java users realized the power of the annotation processor. So it has only become popular in recent years.
A processor with a specific annotation takes the java source code (or compiled bytecode) as input and then generates some files (usually .java files) as output. What does that mean? You can generate java code! These java codes are in the generated .java file. Therefore you cannot change the existing java class, such as adding a method. These generated java files will be compiled by javac like other manually written java source code.
Annotation processing is executed in the compilation stage. Its principle is to read the Java source code, parse the annotations, and then generate new Java code. The newly generated Java code is finally compiled into Java bytecode. The annotation processor cannot change the read Java class, such as adding or deleting Java methods.
2. AbstractProcessor
Let's take a look at the processor's API. All processors inherit AbstractProcessor, as shown below:
package com.example;import java.util.LinkedHashSet;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.ProcessingEnvironment;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.TypeElement;public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> announcements, RoundEnvironment env) { return false; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotataions = new LinkedHashSet<String>(); annotataions.add("com.example.MyAnnotation"); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); }}init(ProcessingEnvironment processingEnv): All annotation processor classes must have a parameterless constructor. However, there is a special method init(), which is called by the annotation processing tool, taking ProcessingEnvironment as a parameter. ProcessingEnvironment provides some practical tool classes Elements, Types and Filers. We will use them later.
process(Set<? extends TypeElement> annoations, RoundEnvironment env) : This is similar to the main() method of each processor. You can encode and implement scanning, processing annotations, and generating java files in this method. Using the RoundEnvironment parameter, you can query elements annotated with a certain annotation (original text: you can query for elements annotated with a certain annotation). We will see the details later.
getSupportedAnnotationTypes(): In this method you must specify which annotations should be registered by the annotation processor. Note that its return value is a String collection that contains the full name of the annotation type that your annotation processor wants to process. In other words, you define here which annotations your annotation processor is going to handle.
getSupportedSourceVersion() : Used to specify the java version you use. Usually you should return SourceVersion.latestSupported() . However, if you have enough reason to stick to Java 6, you can also return SourceVersion.RELEASE_6 . I recommend using SourceVersion.latestSupported() . In Java 7, you can also use annotations to replace rewriting getSupportedAnnotationTypes() and getSupportedSourceVersion() , as shown below:
@SupportedSourceVersion(value=SourceVersion.RELEASE_7)@SupportedAnnotationTypes({ // Set of full qulified annotation type names "com.example.MyAnnotation", "com.example.AnotherAnnotation" })public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> announcements, RoundEnvironment env) { return false; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); }} Due to compatibility issues, especially for Android, I recommend rewriting getSupportedAnnotationTypes() and getSupportedSourceVersion() instead of using @SupportedAnnotationTypes and @SupportedSourceVersion .
The next thing you have to know is: the annotation processor runs in its own JVM. Yes, you read that right. javac starts a complete java virtual machine to run the annotation processor. what does that mean? You can use anything you use in a normal java program. Using guava! You can use dependency injection tools such as dagger or any other class library you want to use. But don't forget that even if it's just a tiny processor, you should pay attention to using efficient algorithms and design patterns, just like you do in developing other java programs.
3. Register your processor
You might ask "How to register my annotation processor to javac?". You must provide a .jar file. Just like other .jar files, you package your already compiled annotation processor into this file. And, in your .jar file, you must package a special file javax.annotation.processing.Processor into the META-INF/services directory. So your .jar file directory structure looks like this:
MyProcess.jar -com -example -MyProcess.class -META-INF -services -javax.annotation.processing.Processor
The contents of the javax.annotation.processing.Processor file are a list, and each line is the full name of an annotation processor. For example:
com.example.MyProcess
com.example.AnotherProcess
4. Example: Factory model
The problem we want to solve is: we want to implement a pizza store, which provides customers with two pizza (Margherita and Calzone), as well as the dessert Tiramisu (tiramisu).
public interface Meal { public float getPrice();}public class MargheritaPizza implements Meal{ @Override public float getPrice() { return 6.0f; }}public class CalzonePizza implements Meal{ @Override public float getPrice() { return 8.5f; }}public class Tiramisu implements Meal{ @Override public float getPrice() { return 4.5f; }}public class PizzaStore { public Meal order(String mealName) { if (null == mealName) { throw new IllegalArgumentException("name of meal is null!"); } if ("Margherita".equals(mealName)) { return new MargheritaPizza(); } if ("Calzone".equals(mealName)) { return new CalzonePizza(); } if ("Tiramisu".equals(mealName)) { return new Tiramisu(); } throw new IllegalArgumentException("Unknown meal '" + mealName + "'"); } private static String readConsole() { Scanner scanner = new Scanner(System.in); String meal = scanner.nextLine(); scanner.close(); return meal; } public static void main(String[] args) { System.out.println("welcome to pizza store"); PizzaStore pizzaStore = new PizzaStore(); Meal meal = pizzaStore.order(readConsole()); System.out.println("Bill:$" + meal.getPrice()); }}As you can see, in the order() method, we have many if condition judgment statements. And, if we add a new pizza, we have to add a new if conditional judgment. But wait, using the annotation processor and factory mode, we can have an annotation processor generate these if statements. In this way, the code we want looks like this:
public class PizzaStore { private MealFactory factory = new MealFactory(); public Meal order(String mealName) { return factory.create(mealName); } private static String readConsole() { Scanner scanner = new Scanner(System.in); String meal = scanner.nextLine(); scanner.close(); return meal; } public static void main(String[] args) { System.out.println("welcome to pizza store"); PizzaStore pizzaStore = new PizzaStore(); Meal meal = pizzaStore.order(readConsole()); System.out.println("Bill:$" + meal.getPrice()); }}public class MealFactory { public Meal create(String id) { if (id == null) { throw new IllegalArgumentException("id is null!"); } if ("Calzone".equals(id)) { return new CalzonePizza(); } if ("Tiramisu".equals(id)) { return new Tiramisu(); } if ("Margherita".equals(id)) { return new MargheritaPizza(); } throw new IllegalArgumentException("Unknown id = " + id); }}5. @Factory Annotation
Can you guess that we plan to use the annotation processor to generate the MealFactory class. More generally, we want to provide an annotation and a processor to generate factory classes.
Let's take a look at the @Factory annotation:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.CLASS)public @interface Factory { /** * The name of the factory */ Class<?> type(); /** * The identifier for determining which item should be instantiated */ String id();}The idea is as follows: we annotate those food classes, use type() to indicate which factory this class belongs to, and use id() to indicate the specific type of this class. Let's apply the @Factory annotation to these classes:
@Factory(type=MargheritaPizza.class, id="Margherita")public class MargheritaPizza implements Meal{ @Override public float getPrice() { return 6.0f; }}@Factory(type=CalzonePizza.class, id="Calzone")public class CalzonePizza implements Meal{ @Override public float getPrice() { return 8.5f; }}@Factory(type=Tiramisu.class, id="Tiramisu")public class Tiramisu implements Meal{ @Override public float getPrice() { return 4.5f; }}You might ask, can we just apply the @Factory annotation to the Meal interface? The answer is no, because the annotation cannot be inherited. That is, if there are annotations on class X, class Y extends X, then class Y will not inherit the annotations on class X. Before we write a processor, we need to clarify a few rules:
Annotation processor:
public class FactoryProcessor extends AbstractProcessor { private Types typeUtils; private Elements elementUtils; private Filer filer; private Messager messager; private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); typeUtils = processingEnv.getTypeUtils(); elementUtils = processingEnv.getElementUtils(); filer = processingEnv.getFiler(); messager = processingEnv.getMessager(); } @Override public boolean process(Set<? extends TypeElement> arg0, RoundEnvironment arg1) { ... return false; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotataions = new LinkedHashSet<String>(); annotataions.add(Factory.class.getCanonicalName()); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }} In the getSupportedAnnotationTypes() method, we specify that the @Factory annotation will be processed by this processor.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.