【 Previous words 】
- To understand many open source libraries like Arouter, Dagger,Butter Knife, you have to understand annotations;
- To improve development efficiency and code quality, annotations can help a lot.
What are annotations
Java.lang. annotation, the interface annotation, was introduced in JDK5.0 and later. Annotations are special tags in your code that can be read at compile time, class load time, run time, and perform processing accordingly. Using annotations, developers can embed additional information in source files without changing the original logic. Code analysis tools, development tools, and deployment tools can use this supplementary information to validate, process, or deploy.
Reading at run time is handled using the Java reflection mechanism.
The Annotation doesn’t run, it just has member variables, it has no methods. Annotations are part of a program element in the same way that modifiers such as public and final are. Annotations cannot be used as a program element.
In fact, most people have used annotations, but they may not have a thorough understanding of their principles and functions, such as @Override, @deprecated, etc.
1. The role of annotations
Annotations make repetitive tasks automatic, simplifying and automating the process. For example, it is used to generate Java Doc, check the format during compilation, and automatically generate code to improve the quality of software and improve the production efficiency of software.
2. What are the annotations
We usually use annotations from JDK included, Android SDK included, can also be customized. 2.1 meta annotations defined by the JDK
Java provides four meta-annotations that are specifically responsible for creating new annotations, that is, annotating other annotations.
- Target
Defines the range of objects that the Annotation modifies. The value is:ElementType.CONSTRUCTOR
: describes the constructorElementType.FIELD
: Describes a domainElementType.LOCAL_VARIABLE
: Describes local variablesElementType.METHOD
: Describes a methodElementType.PACKAGE
: Describes packagesElementType.PARAMETER
: Describes parametersElementType.TYPE
: Describes classes, interfaces (including annotation types), or enum declarations
- Retention
Defines the length of time the Annotation is retained. The value can be:
–RetentionPoicy.SOURCE
Annotations are only retained in the source file. When a Java file is compiled into a class file, annotations are discarded. It is used to perform some inspection operations, such as@Override
和@SuppressWarnings
–RetentionPoicy.CLASS:
Annotations are kept in the class file, but are discarded when the JVM loads the class file, which is the default lifecycle; Used for pre-processing operations at compile time, such as generating auxiliary code (e.gButterKnife
)
–RetentionPoicy.RUNTIME
Annotations are not only saved to the class file, but still exist after the CLASS file is loaded by the JVM. Used to dynamically retrieve annotation information at run time.
-
Documented, used to describe other types of annotations that should be treated as a public API for the annotated program members and therefore can be Documented without assigning by tools such as Javadoc.
-
Inherited, which allows a subclass to inherit annotations from its parent. This is a bit confusing at first, but I need to break it down and allow subclasses to inherit comments from their superclass. Example:
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Sample {
public String name() default "";
}
@Sample
class Test{
}
class Test2 extents Test{
}
Copy the code
This class Test2 actually has an annotation @sample.
If the member name is value, it can be abbreviated during assignment. If the member type is array, but only one element is assigned, it can also be abbreviated. The following three forms are equivalent.
Normal writing
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
Copy the code
Omitting value (only if the member name is value)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
Copy the code
The member type is an array, which assigns only one element
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
Copy the code
* 2.2 Other built-in annotations in the JDK *
@Override, @Deprecated, @SuppressWarnings, @Safevarargs, @FunctionalInterface, @Resources, etc.
* 2.3 Android SDK built-in annotations *
Android SDK built-in annotations are in the package. Com Android. Support: support – in the annotations, below to ‘com. Android. Support: support – annotations: 25.2.0’, for example
- Resource reference restriction class: The restriction parameter must be the corresponding resource type
AnimRes @anyres @arrayres @attrres @BoolRes @colorRes etc
- Thread execution restriction class: Used to restrict methods or classes that must be executed in a specified thread
@AnyThread @BinderThread @MainThread @UiThread @WorkerThread
- Parameter null limit class: used to restrict whether a parameter can be null
@NonNull @Nullable
- Type range limiting class: Used to limit the range of values for a token value
@FloatRang @IntRange
- Type definition class: a collection of values used to restrict defined annotations
@IntDef @StringDef
- Additional functional annotations:
@CallSuper @CheckResult @ColorInt @Dimension @Keep @Px @RequiresApi @RequiresPermission @RestrictTo @Size @VisibleForTesting
2. Custom annotations
The most rewarding use of annotations is to customize them to your own needs. Here are three examples of annotation customizations in turn:
1, RetentionPolicy. SOURCE
General function parameter values are limited, such as view. setVisibility parameters are limited, you can see the view. class source code
In addition to IntDef, we have StringDef
@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
Copy the code
2, RetentionPolicy. The RUNTIME
Runtime annotations are defined as follows:
// Apply classes, interfaces (including annotation types), or enumerations
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.TYPE)
public @interface ClassInfo {
String value();
}
// Applies to field attributes, including enum constants
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldInfo {
int[] value();
}
// Method of application
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodInfo {
String name() default "long";
int age() default 27;
}
Copy the code
Define a test class to use these annotations:
/ * *
* Test runtime annotations
* /
@ClassInfo("Test Class")
public class TestRuntimeAnnotation {
@FieldInfo(value = {1, 2})
public String fieldInfo = "FiledInfo";
@MethodInfo(name = "BlueBird")
public static String getMethodInfo() {
return return fieldInfo;
}
}
Copy the code
Use notes:
/ * *
* Test runtime annotations
* /
private void _testRuntimeAnnotation() {
StringBuffer sb = new StringBuffer();
Class<? > cls = TestRuntimeAnnotation.class;
Constructor<? >[] constructors = cls.getConstructors();
// Get the annotation of the specified type
Sb.append ("Class annotation: ").append("\n");
ClassInfo classInfo = cls.getAnnotation(ClassInfo.class);
if (classInfo ! = null) {
sb.append(cls.getSimpleName()).append("\n");
Sb. Append (" comments: "), append (classInfo. Value ()), append (" \ n \ n ");
}
Sb.append ("Field note: ").append("\n");
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class);
if (fieldInfo ! = null) {
sb.append(field.getName()).append("\n");
Sb.append (" annotations: ").append(Fieldinfo.value ())).append("\n\n");
}
}
Sb.append ("Method ").append("\n");
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);
if (methodInfo ! = null) {
sb.append(Modifier.toString(method.getModifiers())).append(" ")
.append(method.getName()).append("\n");
Sb.append (" note value: ").append("\n");
sb.append("name: ").append(methodInfo.name()).append("\n");
sb.append("age: ").append(methodInfo.age()).append("\n");
}
}
System.out.print(sb.toString());
}
Copy the code
All you do is get the corresponding element by reflection, get the annotation on the element, and finally get the attribute value of the annotation. Run-time annotations are somewhat inefficient because of reflection, and many open source projects now use compile-time annotations.
3, RetentionPolicy. CLASS
* 3.1 Adding dependencies *
If the Gradle plugin is above 2.2, you do not need to add the following Android-apt dependencies.
The classpath 'com. Android. Tools. Build: gradle: 2.2.1'
Copy the code
Add android-apt dependencies to build.gradle for the entire project
buildscript {
repositories {
jcenter()
mavenCentral() // add
}
dependencies {
Classpath 'com. Android. Tools. Build: gradle: 2.1.2' / / above 2.2 without adding apt rely on
The classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8' / / add
}
}
Copy the code
Android-apt: Android-apt is a Gradle plugin that assists Android Studio with annotation processors. It has two purposes:
- Allows configurations to be used only as annotations processor dependencies at compile time and not added to the final APK or library
- Set the source path so that the code generated by the annotation handler is properly referenced by Android Studio
With the release of Android Gradle plugin version 2.2, the author of Android-apt recently confirmed on the official website that he will not continue to maintain Android-apt, and recommended that you use the same capabilities provided by the official Android plugin. Android-apt, which was released about three years ago, is about to be replaced by The Android Gradle plugin called annotationProcessor.
Annotations Create a Java library for storing annotations (annotations)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value();
}
Copy the code
Create a Java library named javax.processors (processors) and plugin. javax.processors (processors)
Build. gradle depends on the following:
The compile 'com. Google. Auto. Services: auto - service: 1.0 rc2'
The compile 'com. Squareup: javapoet: 1.7.0'
compile(project(':annotations'))
Copy the code
Among them, the auto – service is used to automatically in the meta-inf/services directory folder to create javax.mail. Annotation. Processing. The Processor files; Javapoet is a helper library for generating.java source files. It is very convenient to help us generate the.java source files we need
Example:
package com.example;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// Filer is an interface that supports the creation of new files through annotation handlers
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement element : annotations) {
// Create a new file
if (element.getQualifiedName().toString().equals(MyAnnotation.class.getCanonicalName())) {
Create the main method
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!" )
.build();
Create the HelloWorld class
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
try {
/ / generated com. Example. The HelloWorld. Java
JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
.addFileComment(" This codes are generated automatically. Do not modify!" )
.build();
// Generate the file
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Print logs on Gradle Console
for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
System.out.println("------------------------------");
// Determine the type of the element as Class
if (element.getKind() == ElementKind.CLASS) {
// Displays the conversion element type
TypeElement typeElement = (TypeElement) element;
// Prints the element name
System.out.println(typeElement.getSimpleName());
/ / output annotation attribute value System. Out. Println (typeElement. GetAnnotation (MyAnnotation. Class). The value ());
}
System.out.println("------------------------------");
}
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(MyAnnotation.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
Copy the code
3.4 Using defined annotations in code * : Rely on annotations and Processors, the two Java libraries above
import com.example.MyAnnotation;
@MyAnnotation("test")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Copy the code
When compiled, the specified file with the specified package name is generated, as shown:
In the initialization interface of the custom annotation processor, the following four auxiliary interfaces can be obtained:
public class MyProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
}
Copy the code
Types: Types is a tool for handling TypeMirror Elements: Elements is a tool for handling Element Filer: Normally we will use it in conjunction with JavaPoet to generate what we need. Messager provides the annotation handler with a way to report errors, warnings, and prompts
* 3.5 Annotated libraries available to third parties *
The following example uses gradle plugin 2.2 by default and no longer uses apt
A typical library that uses compile-time annotations has three modules:
- Annotations module, Java library, XXXX-Annotations
- Module to implement annotator, Java library, XXXX-compiler
- Provide external interface module, Android library, XXXX-API
Module xxxx-API dependencies {annotationProcessor ‘XXXX-compiler :1.0.0′ compile’ xxxx-Annotations :1.0.0′ //…. others }
When used by a third party, it can be relied on as follows:
dependencies {
.
The compile 'com. Google. Dagger: a dagger: 2.9'
AnnotationProcessor 'com. Google. Dagger: a dagger - compiler: 2.9'
}
Copy the code
See AnnotationSample sample project
Reference link: www.jb51.net/article/802… Blog.csdn.net/github_3518… Blog.csdn.net/github_3518… www.zhihu.com/question/36… Github.com/alibaba/ARo… Blog.csdn.net/asce1885/ar…