annotations

Annotations classification

Annotations are divided into standard annotations and meta-annotations

  1. Standard annotations
  • Override: Annotates a method in an Override superclass. If the annotated method does not actually Override a method in the superclass, the compiler issues a warning
  • Deprecated: Annotates Deprecated or obsolete methods that will display error messages at compile time when they are used by programmers
  • @SuppressWarning: Selectively cancel warnings in specific code segments
  • SafeVarargs: new method in JDK7 for declaring variable length parameters without type safety issues when used with generic classes
  1. Yuan notes
  • @target: The range of objects that the annotation modifies
  • @Inherited: Indicates that annotations can be Inherited
  • Documented: Indicates that this annotation should be Documented by the JavaDoc tool
  • @Retention: The Retention policy used to declare annotations
  • Repeatable: new in JDK8 to allow the same annotation to be used multiple times within the same declaration type

Where @target is an array of type ElementType and has the following values, corresponding to different object scopes

  • Elementtype. TYPE: Modifies classes, interfaces, and enumerated types
  • Elementtype. FIELD: Decorates a member variable
  • ElementType.METHOD: Decorates methods
  • Elementtype. PARAMETER: Modifies parameters
  • Elementtype. CONSTRUCTOR: Decorates the CONSTRUCTOR
  • Elementtype. LOCAL_VARIABLE: Modifies local variables
  • Elementtype. ANNOTATION_TYPE: Annotates annotations
  • Elementtype. PACKAGE: Decorates the PACKAGE
  • Elementtype. TYPE_PARAMETER: type parameter declaration
  • Elementtype. TYPE_USE: Use type

There are three types of @Retention annotations, representing different levels of Retention policies

  • Retentionpolicy. SOURCE: source-level annotations. The annotation information is only retained in the Java source code; the annotation information is discarded after the source code is compiled and is not retained in the.class file
  • Retentionpolicy. CLASS: compile-time annotations. The annotation information remains in the Java source code and.class files, and is discarded by the JVM when the Java program is run
  • Retentionpolicy. RUNTIME: RUNTIME annotations. When running a Java program, the JVM retains this annotation information, which can be retrieved through reflection

Custom annotation

1. Basic definitions

Define a new annotation type using the @interface keyword, much like defining an interface, as follows

public @interface Swordsman {
}
Copy the code

Once you have defined an annotation, you can use it in your class

@Swordsman
public class test{
}
Copy the code

2. Define member variables

Annotations have only member variables and no methods. An annotation’s member variable is declared in the annotation definition as a method of invisible parameters, whose method name defines the name of the member variable, and whose return value defines the type of the member variable

public @interface Swordsman {
    String name();
    int age();
}
Copy the code

Here we define two member variables that are defined in the form of methods. Once we define the member variables, we should specify values for them when we use the annotation

public class test{
    @Swordsman(name = "zwj", age = 23) public void fighting(){
        
    }
}
Copy the code

You can also specify a default value for annotation member variables using the default keyword, as follows

public @interface Swordsman {
    String name() default "zwj";
    int age() default 23;
}
Copy the code

So use can not display the specified

public class test{
    @Swordsman public void fighting(){

    }
}
Copy the code

3. Define runtime annotations

You can use @retention to specify annotation Retention policies. The lifetime length of the three strategies mentioned above is SOURCE<CLASS<RUNTIME. For those that have a short lifetime, a long lifetime can also have an effect. Then you can only use retentionPolicy.runtime. If you want to do some pre-processing at compile time, such as generating some auxiliary code, use retentionPolicy.class. If you want to do some checking, such as @Override and @SuppressWarning, use retentionPolicy. SOURCE. When set to retentionPolicy. RUNTIME, this annotation is a RUNTIME annotation, as shown below

@Retention(RetentionPolicy.RUNTIME)
public @interface Swordsman {
    String name() default "zwj";
    int age() default 23;
}
Copy the code

Define compile-time annotations

Similarly, if the @retention Retention policy is set to retentionPolicy.class, this annotation is a compile-time annotation, as follows

@Retention(RetentionPolicy.CLASS)
public @interface Swordsman {
    String name() default "zwj";
    int age() default 23;
}
Copy the code

Annotation processor

Annotations don’t matter much if you don’t have a tool for handling them. There are different annotation handlers for different annotations, and although annotation handlers are written in a variety of ways, there are also standards for handling them. For example, reflection is used for runtime annotations and AbstractProcessor is used for compile-time annotations

1. Runtime annotation processor

To handle runtime annotations, you need to use reflection. First, define runtime annotations as follows

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {
    String value() default "";
}
Copy the code

Define the GET annotation, apply it to the method, and then apply the annotation

Public class AnnotationTest {@ the GET (value = "http://baidu.com/59.102.53.24") public String getIpMsg () {return ""; } @GET(value = "http://baidu.com") public String getIp(){ return ""; }}Copy the code

This code assigns a value to the @get member variable, and then writes the annotation handler

public class AnnotationProcessor { public static void main(String[] args) { Method[] methods = AnnotationTest.class.getDeclaredMethods(); for(Method method : methods){ GET get = method.getAnnotation(GET.class); System.out.println(get.value()); }}}Copy the code

The code above uses two reflection methods, both of which belong to the AnnotatedElement interface, which classes like Class, Method, and Field implement. Call the getAnnotation method to return the annotation object of the specified type, namely GET, and finally call the value method of GET to return the value of the element extracted from the GET object


2. Compile time annotation processor

There are a few more steps to handle compile-time annotations, which still need to be defined first. Create a New Java Library in your project to store annotations, called Annotations. Next, define the annotations as follows

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

The annotations defined in this code are similar to ButterKnife’s @BindView annotation. Next, write the annotation processor, and create a New Java Library to store the annotation processor. This Library is called Processor, and configure gradle for it

plugins {
    id 'java-library'
}

apply plugin:'java'

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

dependencies {
    implementation fileTree(includes: ['*.jar'], dir: 'libs')
    implementation project(':annotations')
}
Copy the code

Next, write the annotation processor ClassProcessor, derived from AbstractProcessor, as follows

public class ClassProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnvironment){ super.init(processingEnvironment); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return true; } @Override public Set<String> getSupportedAnnotationTypes(){ Set<String> annotations = new LinkedHashSet<>(); annotations.add(BindView.class.getCanonicalName()); return annotations; } @Override public SourceVersion getSupportedSourceVersion(){ return SourceVersion.latestSupported(); }}Copy the code

The four methods of rewriting are introduced

  • Init: called by the annotation processing tool, enter the ProcessingEnvironment parameter. This parameter represents a class that provides many useful utility classes, such as Elements, Types, Filer, Messager, etc
  • Process: Equivalent to each processor’s main function, this is where you write code to scan, evaluate, and process annotations, as well as generate Java files with the parameter RoundEnvironment to query for annotated elements that contain specific annotations
  • GetSupportedAnnotationTypes: this is the method must be specified, which specify the annotation processor is registered to annotation. Notice that its return value is a collection of strings containing the legal full name of the annotation type that the handler wants to process
  • GetSupportedSourceVersion: used to specify the Java version you use, usually here return SourceVersion. LatestSupported ()

Next, write the process method, which is not yet implemented, as follows

@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    Messager messager = processingEnv.getMessager();
    for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
        if (element.getKind() == ElementKind.FIELD){
            messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + element.toString());
        }
    }
    return true;
}
Copy the code

The printMessage of Messager is used to print the name of the annotated member variable, which is printed in the Console window

Then, in order to use the annotation Processor, we need a service file to register it, using Google AutoService open source, can help developers to generate meta-inf. Services/javax.mail annotation. Processing. The Processor file. Click “File” -> “Project Structure” and search “auto-service” to find the library and add

Implementation 'com. Google. Auto. Services: auto - service: 1.0.1'Copy the code

You can then add annotations to the annotation processor ClassProcessor

@autoService (Processor. Class) public class class extends AbstractProcessor {···}Copy the code

We need to reference annotations in the main project. First, we need to reference annotations and Processor in the build.gradle of the main project app project, as follows

implementation project(':annotations')
implementation project(':processor')
Copy the code

Then apply the annotations to MainActivity as follows

public class MainActivity extends AppCompatActivity { @BindView(value = R.id.tv_text) TextView textView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}Copy the code

Finally, by making Project, you can see that the following screenshot is run in the Gradle Console window and the specific annotation information is printed

In addition, we can also use the Android-apt plug-in. The Processor library is referenced in our main project app, but the annotation processor is only needed during compilation, and it has no actual effect after compilation. However, adding this library to the main project will introduce a lot of unnecessary trouble. In order to solve this problem, we introduced the android-apt plugin, which has two main functions

  • The library in which the annotation processor resides is only relied upon and worked on at compile time, but is not packaged into APK
  • Set up a path for the code generated by the annotation processor so that AS can find it

How to use it? First, you need to add the following statement to the build.gradle for the entire project

BuildScript {... dependencies {... the classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'}}Copy the code

Then, introduce the annotation processor processor in apt mode in build.gradle of the main project app as follows

apply plugin: 'com.neenbedankt.android-apt'

dependencies{
    apt project(':processor')
}
Copy the code

Dependency injection principle

With annotations out of the way, dependency injection. To understand dependency injection, start with inversion of control

1. Inversion of control

Coupling between objects is inevitable, but as industrial-scale applications become larger and more complex, multiple dependencies between objects often occur. In order to solve the problem of high coupling degree, some people put forward IOC theory, namely inversion of control, to realize decoupling between objects. Before IOC was introduced into the software system, if object A depended on object B, then object A needed to create object B by itself at A fixed time when it was running, and the control was in its own hands whether it was created or used. After the introduction of IOC, object A and object B can lose direct contact. When A runs to the point where B is needed, IOC container will create an object B and inject it into the place where A needs it. Through the comparison before and after the introduction of IOC, it can be seen that the process of object A acquiring B has changed from active to passive, which is the origin of inversion of control. Thus, since the process of relying on objects is reversed, inversion of control has a more appropriate name, dependency injection, which refers to the dynamic injection of a dependency into an object by the IOC container at run time


2. Dependency injection

When we write code, we will find that some classes depend on other classes. Here is an example of a Car and its parts. The Car contains components such as Engine, as follows

public class Car{ private Engine engine; public Car(){ engine = new PetrolEngine(); }}Copy the code

There is nothing wrong with this code, but Car and Engine are highly coupled because Car needs to create Engine objects, know about PetrolEngine, and modify the Car constructor if the Engine is changed to DieselEngine. Dependency injection can be used to solve these problems. Dependency injection is implemented in three common ways, as follows

  1. Constructor injection. Pass the Engine object to the Car constructor
public class Car{ private Engine engine; public Car(Engine engine){ this.engine = engine; }}Copy the code
  1. Setter method injection. The Engine object is passed through the set method
public class Car{ private Engine engine; public void set(Engine engine){ this.engine = engine; }}Copy the code
  1. Interface injection

Define the information to be injected and complete the injection through the interface. The interface code is as follows

public interface ICar{
    public void setEngine(Engine engine);
}
Copy the code

The Car class then implements the interface

public class Car implements ICar{ private Engine engine; @Override public void setEngine(Engine engine){ this.engine = engine; }}Copy the code

In this way, Car and Engine are decoupled, Car doesn’t care about the implementation of Engine, and even if Engine needs to change, Car doesn’t need to change anything