One sentence understanding

Annotations are an alternative type of annotation that doesn’t work unless combined with other techniques; It is often used to change the execution logic, such as modifying the source code according to annotations (APT/ASM), or modifying the execution logic at runtime in conjunction with reflection.

The generation and definition of annotations

Annotations are a type of annotation introduced after JDK 1.5. Unlike normal comments ignored by the virtual machine, annotations have different effects depending on their lifetime after being processed by the annotation processor. It can be annotated before the specified class, method, field, or parameter.

Without an annotation handler, it will be just as useless as regular annotations.

It was created to influence the flow of bytecode execution at different stages in a simple and intuitive way.

1. Definition of annotations

With the @interface tag, we can define an annotation that contains two core elements:

  • @rentention is the life cycle of annotation, which is meant to inform the compiler when the annotation is tention, and consists of source time (.java file processing stage), compiler (.class file processing stage), and runtime (bytecode interpretation run stage)
  • The annotation object @target marks the object for which the annotation is applied, usually including classes, properties, methods, parameters, and annotations.Annotations that mark annotations are often referred to as meta-annotations
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}
Copy the code

2. Relationship between annotations and annotations

Test the relationship between defined annotations and annotations

System.out.println("an=" + an);
System.out.println("an.annotationType=" + an.annotationType());
System.out.println("an.getClass:()=" + an.getClass());
System.out.println("an instanceof Annotation=" + (an instanceof Annotation));
System.out.println("BindView.class=" + OnClick.class);
Copy the code

The output is as follows:

System.out: [email protected](value=[2131231092])
System.out: an.annotationType=interface com.hch.ioc.BindView
System.out: an.getClass:()=class $Proxy1
System.out: an instanceof Annotation=true
System.out: BindView.class=interface com.hch.ioc.BindView
Copy the code

It can be concluded that:

  • An Annotation defined by an @interface is itself an Annotation
  • AnnotationType is the same as the annotationType obtained via.class, which is a specific annotationType interface com.hch.ioc.bindview
  • Instead of getting an Annotation with getClass, you get a proxy class object

Ii. The life cycle of annotations

The annotation life cycle determines which stage the compiler is ready to process the annotation. With @rentention the meta-annotation, it is possible to define the annotation’s full term, with each annotation being only applicable to one term, which is defined as follows:

package java.lang.annotation; Public enum RetentionPolicy {SOURCE, /* Annotation information exists only during compiler processing, After the compiler processes the Annotation, there is no Annotation information */ CLASS, /* The compiler stores the Annotation in the corresponding.class file of the CLASS. The default behavior */ RUNTIME /* The compiler stores the annotations in a class file that can be read in by the JVM */}Copy the code

The object of the annotation

The annotation handler needs to tell the object on which the annotation is applied. It is annotated by the @target meta-annotation. Common objects include:

package java.lang.annotation; Public enum ElementType {TYPE, /* class, interface, or enumeration declaration */ FIELD, /* FIELD declaration (including enumeration constants) */ METHOD, /* METHOD declaration */ PARAMETER, /* ANNOTATION_TYPE, /* annotation type declaration */ PACKAGE, /* package declaration */ TYPE_PARAMETER, /* generic parameter declaration */ TYPE_USE, /* generic variable use declaration */}Copy the code

TYPE_PARAMETER, TYPE_USE, was introduced after Java 1.8 to annotate generic types

public class Callback<@NoneNull T> {
    ...
}

@Target(ElementType.TYPE_PARAMETER)
public @interface NoneNull {}
Copy the code
@Test String text;

@Target(ElementType.TYPE_USE)
public @interface Test {}
Copy the code

Annotation processor

The Annotation Processing tool is responsible for scanning and Processing annotations. It can handle annotations in any lifecycle

The steps to use APT are summarized as follows:

  • Create a New Java library, called Annotations, to store the annotations you need to process
  • Create a New Java library, called annotationProcessor, that handles annotations and relies on the Annotation Library
  • The Android app relies on annotation and annotationProcessor

The BindView annotation is used as an example

1. New Java Library library (Annotation)

Creating a new process is simple

graph LR
file-->new_module
new_module --> javalibrary
Copy the code

Why Java library, because the annotationProcessor library has to be a Java library, and Java libraries can’t rely on android libraries.

Add BindView

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}
Copy the code

2. Create Java Library annotationProcessor

Add dependencies in build.gradle

  • Auto-service is used to automatically generate a description of the Annotation Processor and to process different annotations separately with steps, each step requiring only declared annotations.
  • Javapoet is used to generate new Java files
  • Dependencies on the Annotation Library
plugins { id 'java-library' } java { sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 } tasks.withType(JavaCompile){ options.encoding = "UTF-8" } dependencies { annotationProcessor 'com. Google. Auto. Services: auto - service: 1.0 -rc3' implementation "com. Google. Auto. Services: auto - service: 1.0 -rc3." " Implementation "com. Google. Auto: auto - common: 1.1.2" implementation 'com. Squareup: javapoet: 1.13.0 / / is dependent on the annotation implementation  project(':annotation') }Copy the code

Create a new annotation processor HCHProcessor,

  • The Steps method defines the annotations to be processed and the actual processing logic.
  • @AutoService helps automatically generate annotationsProcessor annotation claims in meta-info.
  • GetSupportedSourceVersion designated to support Java source code version
@AutoService(Processor.class) public class HCHProcessor extends BasicAnnotationProcessor { @Override protected Iterable<? extends Step> steps() { return ImmutableList.of(new BindViewStep(processingEnv) , new FastClickStep(processingEnv)); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }}Copy the code

The code in BindViewStep is as follows:

  • The Annotations method defines the type of annotations that the step needs to handle
  • The process method is the actual processing logic. It consists of two steps: generating a Java file and writing a Java file. Generating a Java file is done through Javapoet.
public class BindViewStep extends BasicStep { public BindViewStep(ProcessingEnvironment processingEnv) { super(processingEnv); } @Override protected void process(List<Element> elementsByAnnotation) { // 1. build class file TypeSpec typeSpec = buildClass(elementsByAnnotation); JavaFile JavaFile = javafile.builder ("com.hch",typeSpec).build(); mProcessionEnv.getMessager().printMessage(Diagnostic.Kind.NOTE , javaFile.toString()); try { javaFile.writeTo(mProcessionEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } private TypeSpec buildClass(List<Element> elementsByAnnotation) { Element enclosingElement = elementsByAnnotation.get(0).getEnclosingElement(); TypeMirror enclosingType = enclosingElement.asType(); TypeSpec typeSpec = TypeSpec.classBuilder(enclosingElement.getSimpleName().toString()+"_viewBinding") .addModifiers(Modifier.PUBLIC) .addMethod(buildMethod(elementsByAnnotation)) .addSuperinterface(IBinder.class) .build();  return typeSpec; } private MethodSpec buildMethod(List<Element> elementsByAnnotation) { Element enclosingElement = elementsByAnnotation.get(0).getEnclosingElement(); TypeMirror enclosingType = enclosingElement.asType(); MethodSpec.Builder build = null; build = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) VOID) // Get a Type from an element Type. AddParameter (TypeName. Get (enclosingType), "target"); .addParameter(TypeName.get(Object.class) , "target"); for (Element variable : ElementsByAnnotation) {build. AddStatement ("(($L)target).$L = (android.view.View)(($L)target).findViewById($L)" , enclosingType.toString() , variable.getSimpleName().toString() , enclosingType.toString() , variable.getAnnotation(BindView.class).value()); } MethodSpec methodSpec = build.build(); return methodSpec; } @Override public Set<String> annotations() { return new HashSet<String>(Arrays.asList(BindView.class.getCanonicalName())); }}Copy the code

V. Dependencies in app

All you need to do in the app is simply rely on the annotation and declare the Annotation Processor

implementation project(':annotation')
annotationProcessor project(':annotationProcessor')
Copy the code

If you select build-rebuild Project, the process annotation will be printed under the javac task. In this example, the output is as follows:

Task :app:compileHuaweiApi21DebugJavaWithJavac The following annotation processors are not incremental: Jetified annotationProcessor - 1.1.0. Jar (project: annotationProcessor), Jetified - auto - service - 1.0 -rc3. Jar (com. Google. Auto. Services: auto - service: 1.0 -rc3). Make sure all the annotation processors are incremental to improve your build speed. Note: BindViewStep xxxxxxxx key=MainActivity element=testAnnotation elementKind=FIELD asType=android.view.View element.getEnclosingElement=com.hch.MainActivity element.getEnclosingElement.asType=com.hch.MainActivity Element. GetEnclosingElement = class com. Sun. View javac. Code. The Symbol $ClassSymbol note: note: BindViewStep xxxxxxxx key=MainActivity element=testDagger elementKind=FIELD asType=android.view.View element.getEnclosingElement=com.hch.MainActivity element.getEnclosingElement.asType=com.hch.MainActivity element.getEnclosingElement=class com.sun.tools.javac.code.Symbol$ClassSymbolCopy the code

The generated auxiliary class content is as follows:

package com.hch; import com.hch.annotation.IBinder; import java.lang.Object; import java.lang.Override; public class MainActivity_viewBinding implements IBinder { @Override public void bind(Object target) { ((com.hch.MainActivity)target).testAnnotation = (android.view.View)((com.hch.MainActivity)target).findViewById(2131231093); ((com.hch.MainActivity)target).testDagger = (android.view.View)((com.hch.MainActivity)target).findViewById(2131231094); }}Copy the code

5. Some basic concepts in annotations

1.Element

public class Foo { // TypeElement private int a; // VariableElement private Foo other; // VariableElement public Foo() {} // ExecuteableElement public void setA( // ExecuteableElement int newA // TypeElement ) {}}Copy the code

The official instructions

2.MirrorType

Represents the class type of the annotation being processed in the Java language

The official instructions

3.Elements

Utility methods that operate on program elements

The official instructions

4.Types

Utility methods used to operate on types (MirrorType).

The official instructions

5.ProcessingEnvironment

Write new files, report error messages, and find aggregation contexts for other utilities

The official instructions

The resources

Cloud.tencent.com/developer/a…

github.com/google/auto

Kahnsen. Making. IO/kahnblog / 20…