preface
Little apes who have just come into contact with java may not know what annotation is for, or some old birds know only a little and can only use it without knowing why. Therefore, before understanding what annotations are, let me first talk about the use of God horses, the scenes, and whether there are templates at present. After knowing this, let's see if you need to learn at the current stage or use it later. First, let's talk about the usefulness. Annotations can mainly simplify the repetitive work and automation process of developers, but do not improve the efficiency of code execution. They are used for the construction of frameworks and the development of tools; Most application scenarios include automatic code generation in the compilation stage and runtime stage; At present, there are many frameworks developed with annotations, such as ButterKnife, EventBus, Retrofit,ARouter, etc. Well, I've been gossiping so much. Now let's talk about what you can know after reading this article. Let's go to the directory first
- Concept of annotation
- Meta annotation
- Custom annotations (use of annotations)
- Runtime annotation (engine -- > reflection)
- Compile time annotation (core -- > annotation parser)
- Automatic code generation help library javapool
What is annotation
Annotations are special tags in code. These tags can be read during compilation, class loading and runtime, and perform corresponding processing. By using Annotation, developers can embed some supplementary information in the source file without changing the original logic. Code analysis tools, development tools and deployment tools can be verified, processed or deployed through these supplementary information
Meta annotation
Meta annotation is the annotation of annotation. Cry and say what. In fact, it can be understood as an analogy to the keywords provided by java. At present, meta annotations are divided into four types as follows (analogy to multiple keywords provided by java). How each meta annotation (keyword) works is the matter of the compiler.
1.@Retention: defines the life cycle of the annotation (to what extent the described annotation is valid). The parameter is the RetentionPolicy enumeration object
@Retention retention policy type | explain |
---|---|
RetentionPolicy.SOURCE | Annotations are only kept in the source file. When Java files are compiled into class files, annotations are discarded |
RetentionPolicy.CLASS | Annotations are retained in the class file, but are discarded when the jvm loads the class file. This is the default life cycle for compile time annotations |
RetentionPolicy.RUNTIME | Annotations are not only saved to the class file, but also exist after the jvm loads the class file (for runtime annotations) |
2.@Target: indicates where our annotation can appear (not including all), and the parameter is an array of ElementType type
@Target ElementType type | explain |
---|---|
ElementType.TYPE | Interface, class, enumeration, annotation |
ElementType.FIELD | Field, enumerated constant |
ElementType.METHOD | method |
ElementType.PARAMETER | Method parameters |
ElementType.CONSTRUCTOR | Constructor |
ElementType.LOCAL_VARIABLE | local variable |
ElementType.ANNOTATION_TYPE | annotation |
ElementType.PACKAGE | package |
3. @ document (less used)
It indicates that the annotations we marked can be documented by tools such as javadoc
4. @ inherited (less used)
Indicates that the annotation we marked is Inherited, and the default is false. For example, if a parent class uses an annotation decorated with @ Inherited, the child class is allowed to inherit the annotation of the parent class.
Custom annotation
Customize the annotation process, so easy as follows:
- Declaration note
- Parsing annotations (two modes are as follows)
- Reflective annotation processor (runtime annotation)
- Implementation class of AbstractProcessor (compile time annotation)
- Configure annotation parser
- Use of annotations
Let's demonstrate these steps through AS. First, create Module1: Annotation (select Java library), which is used to declare annotations. For example: (ARouter is used to simulate Ali's routing framework, Bindview is used to simulate Butterknife Library)
/** * View view comments by ID */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface FindViewById { // If value is used for naming, it can be ignored during use, otherwise the parameter name must be added int value(); } /** * Set click event */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SetOnClickListener { int id(); String methodName(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface ARouter { String path(); String group() default ""; }
Step 2: create Module2: compiler (select Java Library or Android Library), which is used to interpret annotations
Note: generally, Android Library is selected for runtime annotation, and Java Library is used for compilation annotation (because AbstractProcessor class depends on rt.jar package)
Reflective annotation processor (runtime annotation)
import android.app.Activity; import android.view.View; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Annotation Parser */ public class ButterKnifeProcess{ /** * analysis * @param target Resolution target */ public static void parse(final Activity target) { try { Class<?> clazz = target.getClass(); // Get all fields Field[] fields = clazz.getDeclaredFields(); FindViewById byId; SetOnClickListener clkListener; View view; String name; String methodName; int id; for (Field field : fields){ //Returns all annotations that exist on the element Annotation[] annotations = field.getAnnotations(); for(Annotation annotation:annotations) { if (annotation instanceof FindViewById) { // Gets the FindViewById object (returns the annotation of the specified type existing on the element) byId = field.getAnnotation(FindViewById.class); // This operation is required for reflection access to private members field.setAccessible(true); //Field name name = field.getName(); id = byId.value(); // Find objects view = target.findViewById(id); if (view != null) field.set(target, view); else throw new Exception("Cannot find.View name is " + name + "."); } else if (annotation instanceof SetOnClickListener) { // Set click method //Returns an annotation of the specified type that exists on the element clkListener = field.getAnnotation(SetOnClickListener.class); field.setAccessible(true); // Get variable id = clkListener.id(); methodName = clkListener.methodName(); name = field.getName(); view = (View) field.get(target); if (view == null) { // If the object is empty, find the object again view = target.findViewById(id); if (view != null) field.set(target, view); else throw new Exception("Cannot find.View name is " + name + "."); } // Returns all methods of the current class, including public, private and protected, excluding the parent class Method[] methods = clazz.getDeclaredMethods(); boolean isFind = false; for (final Method method:methods) { if (method.getName().equals(methodName)) { isFind = true; view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { method.invoke(target); } catch (Exception e) { e.printStackTrace(); } } }); break; } } // Can't find if (!isFind) { throw new Exception("Cannot find.Method name is " + methodName + "."); } } } } } catch (Exception e) { e.printStackTrace(); } } }
Implementation class of AbstractProcessor (compile time annotation)
package com.jsonan.compile; import com.google.auto.service.AutoService; import com.jsonan.annotation.ARouter; import java.io.IOException; import java.io.Writer; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** * AbstractProcessor Is an abstract class that implements the Processor interface. * Processor The implementation class of the interface must provide a public parameterless constructor, which will be used by the virtual machine tool to instantiate the Processor. * The interaction process between the virtual machine tool framework and the class implementing this interface: * 1.Create Processor object * 2.Call the init method of the Processor object * 3.Call the getSupportedAnnotationTypes, getSupportedOptions and getSupportedSourceVersion methods of the Processor object (we will implement these methods using annotations below) * 4.Calling the process method of the Processor object */ @AutoService(Processor.class) // Sets the name of the annotation type supported by this annotation parser @SupportedAnnotationTypes("com.jsonan.annotation.ARouter") // Set the latest JDK version supported by this annotation parser @SupportedSourceVersion(SourceVersion.RELEASE_7) // Set the options recognized by this annotation parser @SupportedOptions("content") public class ARouterProcessor extends AbstractProcessor { //A tool class used to handle Element elements private Elements elementUtils; //Tool class used to handle TypeMirror private Types typeUtils; //Output warning, error log tool class private Messager messager; //File generator private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); elementUtils = processingEnvironment.getElementUtils(); typeUtils = processingEnvironment.getTypeUtils(); messager = processingEnvironment.getMessager(); filer = processingEnvironment.getFiler(); //The parameters of the content are from build. In the annotated Module Transfer in gradle, such as: //android { // defaultConfig { // javaCompileOptions{ // annotationProcessorOptions { // arguments = [content:'hello apt'] // } // } // } String content = processingEnvironment.getOptions().get("content"); messager.printMessage(Diagnostic.Kind.NOTE, content); } /** *The results scanned by the annotation Processor will be stored in the roundEnvironment, where you can get the annotation content and write your operation logic *Note: you cannot throw an exception directly in the process() function, otherwise the program will crash abnormally */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set.isEmpty()) { return false; } Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class); for (Element element : elements) { String packName = elementUtils.getPackageOf(element).getQualifiedName().toString(); String className = element.getSimpleName().toString(); messager.printMessage(Diagnostic.Kind.NOTE, "The annotated classes are:" + className); String finalClassName = className + "$$ARouter"; try { //This will be displayed in app \ build \ generated \ AP_ generated_ Generate mainactivity $$arouter under sources \ debug \ XXX \ package Java file JavaFileObject sourceFile = filer.createSourceFile(packName + "." + finalClassName); Writer writer = sourceFile.openWriter(); writer.write("package " + packName + ";\n"); writer.write("public class " + finalClassName + "{\n"); writer.write("public static Class<?> findTargetClass(String path){\n"); // Returns an annotation of the specified type that exists on this element ARouter aRouter = element.getAnnotation(ARouter.class); writer.write("if (path.equalsIgnoreCase(\"" + aRouter.path() + "\")){\n"); writer.write("return " + className + ".class;\n}\n"); writer.write("return null;\n"); writer.write("}\n}\n"); writer.close(); } catch (IOException e) { e.printStackTrace(); } } return false; } }
The core of the annotation processor is the process() method, and the core of the process() method is the element element. Element represents the element of the program. During annotation processing, the compiler will scan all Java source files and treat each part of the source code as a specific type of element. It can represent packages, classes, interfaces, methods, fields and other element types. All elements must have several subclasses. As shown below.
Element subclass | explain |
---|---|
TypeElement | Class or interface element |
VariableElement | Field, enum constant, method or constructor parameter, local variable or exception parameter element |
ExecutableElement | A method, constructor, or annotation type element of a class or interface |
PackageElement | Package element |
TypeParameterElement | Generic parameters of a class, interface, method, or constructor element |
We also give a brief introduction to the methods in the Element class:
/** * Returns the types defined by this element, int,long */ TypeMirror asType(); /** * Return the type of this element: package, class, interface, method and field */ ElementKind getKind(); /** * Returns the modifiers of this element: public, private, protected */ Set<Modifier> getModifiers(); /** * Returns the simple name (class name) of this element */ Name getSimpleName(); /** * Returns the innermost element that encapsulates this element. * If the declaration of this element is lexically directly encapsulated in the declaration of another element, the encapsulated element is returned; * If this element is a top-level type, its package is returned; * If this element is a package, null is returned; * null if this element is a generic parameter */ Element getEnclosingElement(); /** * Returns the child element directly encapsulated by this element */ List<? extends Element> getEnclosedElements(); /** * Returns the annotation that exists directly on this element * To get inherited annotations, use getAllAnnotationMirrors */ List<? extends AnnotationMirror> getAnnotationMirrors(); /** * Returns an annotation of the specified type that exists on this element */ <A extends Annotation> A getAnnotation(Class<A> var1);
Use examples of subclasses of Element are as follows:
//VariableElement modifies the annotation of attributes and class members for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) { //ElementType.FIELD annotation can be directly forced to VariableElement VariableElement variableElement = (VariableElement) element; //The getEnclosingElement attribute, the direct encapsulation of class members, and the upper element is the class, so it is a TypeElement TypeElement classElement = (TypeElement) element.getEnclosingElement(); PackageElement packageElement = elementUtils.getPackageOf(classElement); //Class name String className = classElement.getSimpleName().toString(); //Package name String packageName = packageElement.getQualifiedName().toString(); //Class member name String variableName = variableElement.getSimpleName().toString(); //Class member type TypeMirror typeMirror = variableElement.asType(); String type = typeMirror.toString(); }
Note: in the process() function above, we use the JavaFileObject method to generate java files. If there are many java files and the content is complex, it is easy to make mistakes. There is a convenient tool library for us to use javapool. The last part of the following will be described in detail. Skip for the moment and finish the whole process first
Step 3:
Method 1: create javax annotation. processing. Processor file: under the main folder, create the resources folder in the same level directory as java, and then create meta-inf / services / javax annotation. processing. Processor file, in javax annotation. processing. The processor file indicates the class path of the annotation parser class processor (the full class name of the processor). If there are multiple annotation processors, it can be written on a new line.
Mode 2:
-
In the build. Of processor Module: compile Add google's auto generation service library to gradle, such as:
dependencies { implementation 'com.google.auto.service:auto-service:1.0-rc4' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' implementation project(path:':annotation') } sourceCompatibility = "7" targetCompatibility = "7"
-
Add @ AutoService(Processor.class) before the annotation processor class, such as:
@AutoService(Processor.class) public class ButterKnifeProcess{ ... }
Method 3: if the Module created in step 2 is Android Library, you can create it in build In the dependencies block of the gradle file, add the support annotations dependency
dependencies { compile 'com.android.support:support-annotations:24.2.0' }
If the module uses the appcompat library, there is no need to add a support annotations dependency. Because the appcompat library already depends on the annotation library
Step 4: to use the annotation, you need to use it in the Module build Add dependencies to the gradle file. For example, if you want to use the main Module: app in the project, you can use it in the build. App of the main Module Add dependencies to gradle:
dependencies { annotationProcessor project(path: ':compile(Parsing annotation Module Name of)') implementation project(path: ':annotation(Declaration note Module Name of)') }
If the annotation resolution Module written is only scanned at compile time, you can also modify the dependency to:
dependencies { annotationProcessor project(path: ':compile(Parsing annotation Module Name of)') complileOnly project(path: ':annotation(Declaration note Module Name of)') }
Compile time annotation and run-time annotation are used as follows:
//Runtime annotation @ARouter(path = "/app/MainActivity", group = "app") public class MainActivity extends AppCompatActivity { //Automatically bind view (compile time annotation) @FindViewById(R.id.tv_Toast) @SetOnClickListener(id = R.id.tv_Toast, methodName = "click") TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnifeProcess.parse(this); } /** * Click event */ public void click() { Toast.makeText(this, "Hello compile annotatioin", Toast.LENGTH_SHORT).show(); } }
A brief introduction and use of javapool
Javapool is a third-party library that automatically generates java files by programming. It is usually used to write templates, classes, frameworks, etc
Use it directly (benchmark the MainActivity$$ARouter.java file generated when compiling in Filer mode above). Refer to the following for the specific introduction of javapool Reference link
Files that need to be generated to use javapool
package com.jsonan.annotationlearning; import java.lang.Class; import java.lang.String; public class MainActivity$$ARouter { public static Class<?> findTargetClass(String path) { if (path.equalsIgnoreCase("/app/MainActivity")) { return MainActivity.class; } return null; } }
Javapool uses the following:
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { ClassName path = ClassName.get("java.lang", "String"); ParameterSpec savedInstanceState = ParameterSpec.builder(path, "path").build(); //Construct a method MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass") //name .addModifiers(Modifier.PUBLIC, Modifier.STATIC) //modification .returns(Class.class) //return .addParameter(savedInstanceState) //parameter .beginControlFlow("if($N.equalsIgnoreCase(\"/app/MainActivity\"))",savedInstanceState) .addStatement("return MainActivity.class") //sentence .endControlFlow() .addStatement("return null") //sentence .build(); //Construct a class TypeSpec MainActivity$$ARouter = TypeSpec.classBuilder("MainActivity$$ARouter") //name .addModifiers(Modifier.PUBLIC) //modification .addMethod(findTargetClass) //method .build(); JavaFile javaFile = JavaFile.builder("com.jsonan.annotationlearning",MainActivity$$ARouter).build(); try { javaFile.writeTo(System.out); } catch (IOException e) { e.printStackTrace(); } File file = new File("."+"\\src\\"); if (file.exists()) { file.delete(); } javaFile.writeTo(file); return false; }
Reference link
https://blog.csdn.net/wuyuxing24/article/details/81139846
https://blog.csdn.net/wangjiang_qianmo/article/details/99350360
Introduction to Element subclass