Understanding of android annotations and reflection

Understanding of android annotations and reflection

When I first started using butterknife, I thought it was so cool. Why don’t you need to write annoying findViewById by adding @bindview? Then there are EventBus, Retrofit, and now many libraries use annotations, so let’s sort them out now. Let's also write the reflection that handles runtime annotations together.

1. Annotation

1. Basic concepts

Annotations can be simply understood as special tags in the code, which can be read and processed accordingly during compilation, class loading, and runtime. For example the most common override.

  • Annotations are a type of metadata (data describing data).
  • Annotation is a way and method that Java provides an element in the source program to associate any information or any metadata.
  • Annotations are passive metadata, and annotations have no active behavior.

Classification of annotations:

  • Standard Annotations
    The standard annotations mainly include:
    @Override: Mark overrides the methods in the superclass.
    @Deprecated: Mark and annotate obsolete or deprecated methods.
    @SuppressWarnings: Selectively suppress warnings in specific code.
    @SafeVarargs: Used to declare methods that use variable-length parameters, and there will be no type safety issues when used with generic classes.

  • Meta-annotations
    Meta-annotations are annotations used to annotate other annotations, thus creating new annotations. The main ones are:
    @Retention: The life cycle of annotation retention.
    @Target: The scope of the annotation object.
    @Inherited: Indicates that annotations can be inherited.
    @Documented: Indicates that this annotation should be recorded by the JavaDoc tool.
    The values ​​and descriptions of @Target and @Retention are shown in the following table:

@Retention value type illustrate
RetentionPolicy.SOURCE Source code comments. Annotations are only kept in the source code, and the annotation information will be discarded after compilation
RetentionPolicy.CLASS Annotations at compile time. Annotation information will be retained in the source code and compile-time .class. Discarded at runtime, not kept in jvm
RetentionPolicy.RUNTIME Runtime annotations. Annotation information can be retained until runtime, and annotation information can be obtained through reflection
@Target value type illustrate
ElementType.TYPE Can modify interfaces, classes, enumerations
ElementType.FIELD Can modify member variables
ElementType.METHOD can modify the method
ElementType.PARAMETER Can modify parameters
ElementType.CONSTRUCTOR can modify the constructor
ElementType.LOCAL_VARIABLE can modify local variables
ElementType.ANNOTATION_TYPE Can modify annotations
ElementType.PACKAGE can modify bag
ElementType.TYPE_PARAMETER type parameter declaration
ElementType.TYPE_USE use type

2. Custom annotations

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
    
    
    String name();
}

The above code: the custom annotation is declared through the @interface keyword, and it is decorated with two meta-annotations @Retention and @Target. According to the above table, we can know that the life cycle of this annotation is reserved until runtime (RUNTIME), and the scope is the member variable (FIELD).
Note: "String name()" is not a method, and the member variable is declared in the annotation as "method without formal parameters". That is to say, the annotation declares a member variable of type String named "name".
In addition, when @Targer has multiple values, you can also write @Target({type1, type2, type3,...}).

The name variable is declared in MyAnnotation, which should be assigned a value when used:

 @MyAnnotation(name = "李磊")
 private String userName;

Or you can use the default keyword to specify the default value when defining the annotation, as follows:

public @interface MyAnnotation {
    
    
    String name() default "李磊";
}

This way you don't have to specify a value for the name variable:

@MyAnnotation
private String userName;

So far, we have customized annotations, added default values ​​and added them to userName. But at this time, the printed value of userName is still null, because as mentioned above, annotations are passive metadata and there will be no active behavior. So we have to process the annotation to get the value of the annotation.

 针对运行时注解和编译时注解分别会使用反射机制来处理和AbstractProcessor(apt)来处理。

2. Reflection

1. Basic concepts

The JAVA reflection mechanism is in the running state, for any class, you can know all the properties and methods of this class; for any object, you can call any of its methods and properties; this kind of dynamically obtained information and dynamic call The function of the method of the object is called the reflection mechanism of the java language.

2. Common methods

There are three ways to get the class, here we take the String class as an example:

Class a=String.class; //1
//2
String s= "";
Class b=s.getClass();
//3
Class c=Class.forName("java.lang.String");//这里要填写完整的包名

After obtaining the class, you can use the following commonly used methods to obtain data such as member variables, methods, variable types, and method return values ​​of the class:

  • Annotation[] getAnnotations () //Get all annotations in this class

  • getClassLoader() //Get the class loader that loads this class

  • getDeclaredMethods() //Get the declared methods in this class

  • getDeclaredFields() //Get the declared variables in this class

  • getReturnType() //Get the return type of the method

  • getParameterTypes() // Get the incoming parameter type of the method

  • isAnnotation() //Test whether this class is an annotation class

  • getDeclaredConstructors() //Get all constructors

  • getDeclaredMethod(String name, Class... parameterTypes) // Get the specified constructor (parameter: parameter type.class)

  • getSuperclass() //Get the parent class of this class

  • getInterfaces() // Get all interfaces implemented by this class

  • getFields() //Get all public modified member variables in this class

  • getField(String name) //Get the public modified member variable with the specified name

  • isAnnotationPresent(Class<? extends Annotation> annotationType) //Determine whether a variable or method is modified by an annotation

  • getModifiers() //Get attribute modifiers, such as public, static, etc.

  • newInstance() //Returns the class represented by this Class, a new instance created by calling the default (ie no parameter) constructor

3. Examples

Define a User class:

public class User {
    
    
    private String userName;
    private int age;


    public String getUserName() {
    
    
        return userName;
    }

    public void setUserName(String userName) {
    
    
        this.userName = userName;
    }

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    @Override
    public String toString() {
    
    
        return "User{" +
                "userName='" + userName + '\'' +
                ", age=" + age +
                '}';
    }
}

Reflection obtains member variables and methods in the User class:

 private void getUserClass() {
    
    
        Class c = User.class;
        Field[] fields = c.getDeclaredFields();
        Method[] methods = c.getDeclaredMethods();

        StringBuilder sb = new StringBuilder();
        sb.append(Modifier.toString(c.getModifiers()) + " class "); // 获取属性的修饰符,例如public,static等等
        sb.append(c.getSimpleName() + " { \n"); //获取类名
        for (Field field : fields) {
    
     //遍历类中的变量
            sb.append("\t");// 空格
            sb.append(Modifier.toString(field.getModifiers()) + " ");
            sb.append(field.getType().getSimpleName() + " ");// 属性的类型的名字 如String 、 int
            sb.append(field.getName() + ";\n");
        }
        for (Method method : methods) {
    
     //遍历类中的方法
            sb.append("\t");
            sb.append(Modifier.toString(method.getModifiers()) + " ");// 获取属性的修饰符
            sb.append(method.getReturnType().getSimpleName() + " "); // 获取返回类型
            sb.append(method.getName() + " ");  //获取方法名
            Parameter[] parameters = method.getParameters(); //获取方法所有参数
            String params = "";
            if (parameters.length > 0) {
    
     //遍历参数
                StringBuffer pSb = new StringBuffer();
                for (Parameter parameter : parameters) {
    
    
                    //获取参数类型以及参数名称
                    pSb.append(parameter.getType().getSimpleName() + " " + parameter.getName() + ",");
                }
                //去掉最后一个逗号
                params = pSb.substring(0, pSb.length() - 1);
            }
            sb.append("(" + params + ");\n");
        }
        sb.append("}");
        System.out.println(sb);
    }

The method logic is relatively simple. First, get the User class and all the methods and member variables declared in the class. Then traverse the methods and member variables separately and print them out. There are detailed comments above, so I won’t introduce them one by one. Call getUserClass() to see the method and member variable information of the User class:

Reflection obtains the information of the User class

3. Reflection to obtain runtime annotation information

After understanding the basic principles of reflection and annotation, it is relatively simple to obtain annotation information.
Add annotations to userName in User:

public class User {
    
    
    @MyAnnotation
    private String userName;
    ....

Obtain annotation information and assign it to userName:

 private void getAnnotation() {
    
    
        Class c = User.class;
        User user = null;
        try {
    
    
            user = (User) c.newInstance();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        }

        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
    
    
            //判断该变量是否被MyAnnotation注解修饰
            if (field.isAnnotationPresent(MyAnnotation.class)) {
    
    
                //获取注解
                MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
                try {
    
    
                    //如果变量声明为private时,为了确保可以访问可将Accessible置为true
                    field.setAccessible(true);
                    //将当前属性的值修改为注解中成员变量的值
                    field.set(user, annotation.name());
                    System.out.println(user.getUserName());
                } catch (IllegalAccessException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

After the above code obtains the variable, it first judges whether the variable is modified by the custom MyAnnotation, and if so, obtains the annotation. Then assign the value of the annotation to the variable. It is worth noting that:

  • field.setAccessible(true): This method is to ensure that variables modified by private can be accessed and modified.
  • field.set(user, annotation.name()); modification operation, parameter 1 is the class of the modified variable, which is the User class.

In this way, you can see that userName is already the default value in the annotation:
insert image description here
in principle, butterknife is also annotated through @bindview, and finally calls findviewByid to initialize the control. But in fact butterknife uses apt compile-time annotation processing , and how to automatically generate code will not be discussed here.

Guess you like

Origin blog.csdn.net/weixin_40652755/article/details/109001487