Java annotations and reflection in detail

annotation

What is an annotation?

  • Annotation is a new technology introduced since JDK 1.5
  • The role of annotation
    • It is not the program itself, it can be explained (this point is no different from comments)
    • Can be read by other programs (such as compilers, etc.)
  • Annotation format
    • Annotations exist in the code as "@ annotation name", and some parameter values ​​can also be added, for example: @SuppressWarnings(value="unchecked")
  • Where is Annotation used?
    • It can be attached to package, class, method, field, etc., which is equivalent to adding additional auxiliary information to them. We can achieve access to these metadata through reflection mechanism programming

Such as: (There will be an @Override annotation when overriding the parent class method)
Insert picture description here

Built-in annotations

  • @Override: Defined in java.lang.Override, this annotation only applies to rhetorical methods, indicating that a method declaration intends to override another method declaration in the super class
@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override
  • @Deprecated: Defined in java.lang.Deprecated, this annotation can be used for rhetorical methods, attributes, classes, to indicate that programmers are not encouraged to use such elements, usually because it is dangerous or there are better options
@Documented
@Retention(value=RUNTIME)
@Target(value={CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
public @interface Deprecated

@SuppressWarnings: Defined in java.lang.SuppressWarings, used to suppress compile-time warning messages. It is different from the previous two comments. You need to add a parameter to use it correctly. These parameters are already defined. We Just use it selectively

  • @SuppressWarnings(“all”)
  • @SuppressWarnings(“unchecked”)
  • @SuppressWarnings(value={“unchecked”,“deprecation”})
  • and so on…
@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@Retention(value=SOURCE)
public @interface SuppressWarnings

Meta annotation

  • The role of meta-annotation is to annotate other annotations. Java defines 4 standard meta-annotation types, which are used to provide explanations for other annotation types
  • These types and the classes they support can be found in the java.lang.annotation package (@Target, @Retention, @Documented, @Inherited)

@Target

Used to describe the scope of use of annotations (ie: where can the described annotations be used)

For example: we customize an annotation
Insert picture description here
ElementType is an enumeration class, which provides some simple classifications (the following is a partial screenshot of the class)
Insert picture description here
and then we use the annotation

public class Test01 {
    @MyAnnotation
    public void test() {

    }
}

It is also defined in @MyAnnotation that it can be used in the class, so this annotation can also be used on the class, such as:
Insert picture description here

@Retention

Indicates at what level the annotation information needs to be saved, used to describe the life cycle of the annotation (SOURCE <CLASS <  RUNTIME )
Insert picture description here

@Documented

Explain that the annotation will be included in the javadoc
Insert picture description here

@Inherited

Explain that the subclass can inherit the annotation in the parent class

Insert picture description here

Custom annotation

  • When using @interface custom annotations, it automatically inherits the java.lang.annotation.Annotation interface
  • analysis:
    • @interface is used to declare an annotation, format: public @interface annotation name {define content}
    • Each of these methods actually declares a configuration parameter
    • The name of the method is the name of the parameter
    • The return value type is the type of the parameter (the return value can only be the basic type, Class, String, enum)
    • The default value of the parameter can be declared by default
    • If there is only one parameter member, the general parameter name is value
    • The annotation element must have a value. When we define the annotation element, we often use an empty string, with 0 as the default value
import java.lang.annotation.*;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {

    // 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
    @MyAnnotation(age = 18, name = "lisa")
    public void test() {

    }
    
    @DemoAnnotation("注解测试")
    public void test1() {

    }
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    // 注解的参数:参数类型 + 参数名()
    String name() default "";

    int age();

    // 如果默认值为 -1,代表不存在
    int id() default -1;

    String[] schools() default {"大学"};
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface DemoAnnotation {
    String value();
}

reflection

Static language VS dynamic language

Dynamic language

A dynamic language is a kind of language whose structure can be changed at runtime: for example, new functions, objects, and even codes can be introduced, existing functions can be deleted or other structural changes. In layman's terms, the code can change its structure according to certain conditions at runtime.

Static language

Corresponding to dynamic languages, languages ​​with immutable runtime structures are static languages, such as Java, C, and C++

Java is not a static language, but Java can be called a "quasi-dynamic language". That is, Java has a certain degree of dynamics, and we can use the reflection mechanism to obtain characteristics similar to dynamic languages. The dynamic nature of Java makes programming more flexible

Overview of Java reflection mechanism

    Reflection is the key to Java being regarded as a dynamic language. The reflection mechanism allows the program to obtain the internal information of any class with the help of Reflection API during execution, and can directly manipulate the internal properties and methods of any object

Class c = Class.forName("java.lang.String");

After the class is loaded, an object of type Class is generated in the method area of ​​the heap memory (a class has only one Class object), and this object contains the structural information of the complete class. We can see the structure of the class through this object. This object is like a mirror, through which the structure of the class can be seen, so we call it vividly: reflection

  • Normal way: Introduce the required "package class" name -> instantiate with new -> get the instantiated object
  • Reflection method: instantiate the object -> getClass() method -> get the complete "package class" name

Functions provided by the Java reflection mechanism

  • Determine the class to which any object belongs at runtime
  • Construct an object of any class at runtime
  • Judge the member variables and methods of any class at runtime
  • Get generic information at runtime
  • Call member variables and methods of any object at runtime
  • Processing annotations at runtime
  • Generate dynamic proxy

Advantages and disadvantages of Java reflection

Advantages:  dynamic creation and compilation of objects can be achieved, showing great flexibility

Disadvantages:  have an impact on performance. Using reflection is basically an interpretation operation. We can tell the JVM what we want to do and what requirements it meets us. This type of operation is always slower than performing the same operation directly.

Main APIs related to reflection

  • java.lang.Class represents a class
  • java.lang.reflect.Method represents the method of the class
  • java.lang.reflect.Field represents the member variables of the class
  • java.lang.reflect.Constructor represents the constructor of the class

Understand the Class class and get the Class instance 

First define an entity class

// 定义一个实体类
public class User {

    private int id;

    private String name;

    private int age;

    public User() {
    }

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

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

Main program

package com.java.demo.reflect;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {
    public static void main(String[] args) throws Exception {
        // 通过反射获取类的 Class 对象
        Class<?> aClass = Class.forName("com.java.demo.reflect.User");
        System.out.println(aClass);
    }
}

Output:

Insert picture description here
A class has only one Class object in memory. After a class is loaded, the entire structure of the class will be encapsulated in the Class object

Class class

The following methods are defined in the Object class, this method will be inherited by all subclasses

public final native Class<?> getClass();

The type of the return value of the above method is a Class class, which is the source of Java reflection. In fact, the so-called reflection is also well understood from the running results of the program, that is: the name of the class can be obtained through object reflection

For each class, JRE reserves an unchanging Class type object for it. A Class object contains a specific structure (class / interface / enum / annotation / primitive type/void / [])

  • Class itself is also a class
  • Class objects can only be created by the system
  • A loaded class will only have one Class instance in the JVM
  • A Class object corresponds to a .class file loaded into the JVM
  • Each instance of the class will remember which instance of the class it was generated by
  • Through Class, all loaded structures in a class can be completely obtained
  • The Class class is the root of Reflection, for any class you want to dynamically load and run, and only the Class object that gets the response first

Common methods of Class

Method name Function Description
static Class forName(String name) Returns the Class object of the specified class name name
Object newInstance() Call the default constructor and return an instance of the Class object
getName() Returns the name of the entity (class, interface, array class, or void) represented by this Class object
Class getSuperClass() Returns the Class object of the parent class of the current Class object
Class[] getInterfaces() Get the interface of the current Class object
ClassLoader getClassLoader() Returns the class loader of this class
Constructor[] getConstructors() Returns an array containing some Constructor objects
Method getMethod(String name, Class… T) Returns a Method object whose formal parameter type is paramType
Field[] getDeclaredFields() Returns an array of Field objects

Get an instance of the Class class

public class Test01 {
    public static void main(String[] args) throws Exception {
        // 方式一:forName 获得
        Class aClass = Class.forName("com.java.demo.reflect.User");
        System.out.println(aClass);

        // 方式二:通过对象获得
        Class aClass1 = new User().getClass();
        System.out.println(aClass1);

        // 方式三:通过类名.class 获得
        Class<User> aClass2 = User.class;
        System.out.println(aClass2);
    }
}

All types of Class objects

public class Test01 {
    public static void main(String[] args) {
        Class objectClass = Object.class;            // 类
        Class comparableClass = Comparable.class;    // 接口
        Class aClass = String[].class;               // 一维数组
        Class aClass1 = int[][].class;               // 二维数组
        Class overrideClass = Override.class;        // 注解
        Class elementTypeClass = ElementType.class;  // 枚举
        Class integerClass = Integer.class;          // 基本数据类型
        Class voidClass = void.class;                // void
        Class classClass = Class.class;              // Class

        System.out.println(objectClass);
        System.out.println(comparableClass);
        System.out.println(aClass);
        System.out.println(aClass1);
        System.out.println(overrideClass);
        System.out.println(elementTypeClass);
        System.out.println(integerClass);
        System.out.println(voidClass);
        System.out.println(classClass);
    }
}

Output:
Insert picture description here

Java memory analysis

Insert picture description here

Class loading and the understanding of ClassLoader

  • Loading: Load the bytecode content of the class file into the memory, and convert these static data into the runtime data structure of the method area, and then generate a java.lang.Class object representing this class

  • Link: The process of merging the binary code of the Java class into the running state of the JVM

    • Verification: Ensure that the loaded class information complies with the JVM specification, and there are no security issues
    • Preparation: The stage of formally allocating memory for class variables (static) and setting the default initial value of class variables. These memories will be allocated in the method area
    • Resolution: The process of replacing symbol references (constant names) in the virtual machine constant pool with direct references (addresses)
  • initialization:

    • The process of executing the class constructor <clint>() method. The class constructor <clint>() method is generated by automatically collecting the assignment actions of all class variables in the class during compile time and combining the statements in the static code block (class constructor It is for constructing class information, not the constructor for constructing objects of this class)
    • When initializing a class, if you find that its parent class has not been initialized, you need to trigger the initialization of its parent class first
    • The virtual machine guarantees that the <clint>() method of a class is correctly locked and synchronized in a multithreaded environment
package com.java.demo.reflect;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {
    public static void main(String[] args) {
        /**
         * 1、加载到内存,会产生一个类对应的 Class 对象
         * 2、链接,链接结束后 m = 0
         * 3、初始化
              <clint>() {
                    System.out.println("A 类静态代码块初始化");
                    m = 300;
                    m = 100;
                }
                m = 100;
         */
        A a = new A();
        System.out.println(A.m);
    }
}

class A {
    static {
        System.out.println("A 类静态代码块初始化");
        m = 300;
    }

    static int m = 100;

    public A() {
        System.out.println("A 类的无参构造函数初始化");
    }
}

Output:
Insert picture description here

Get the complete structure of the runtime class & dynamically create object execution method

Field、Method、Constructor、SuperClass、Interface、Annotation

  • All interfaces implemented
  • Inherited parent class
  • Full constructor
  • All methods
  • All Field
  • annotation

For details, you can read this article written before, which is very detailed and  the simple use of Java reflection mechanism

Performance comparative analysis

setAccessible

  • Method and Field, Constructor objects have setAccessible() method
  • setAccessible is a switch to enable and disable access security checks
  • If the parameter value is true, it prompts that the reflected object should cancel the Java language access check when in use
    • Improve the efficiency of reflection. If reflection must be used in the code and the statement needs to be called frequently, then please set it to true
    • Make private members that are otherwise inaccessible can also be accessed
  • The parameter value is false, indicating that the reflected object should implement Java language access checks

 

package com.java.demo.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {

    // 普通方式调用
    public static void test01() {
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行 1 亿次:" + (endTime - startTime) + "ms");
    }

    // 反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行 1 亿次:" + (endTime - startTime) + "ms");
    }

    // 反射方式调用,关闭检测
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("关闭检测方式执行 1 亿次:" + (endTime - startTime) + "ms");
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
    }
}

Output:
Insert picture description here

Through the output results, it can be found that the normal method is the fastest to execute, and the second is to turn off the detection method

Manipulate generics through reflection

  • Java uses the mechanism of generic erasure to introduce generics. Generics in Java are only used by the compiler javac to ensure data security and avoid the problem of forced type conversion. However, once the compilation is completed, all generics are compatible with generics. All related types are erased
  • In order to manipulate these types through reflection, Java has added ParameterizedType, GenericArrayType, TypeVariable, and WildcardType to represent types that cannot be grouped into the Class class but have the same name as the original type.
    • ParameterizedType: Represents a parameterized type, such as Collection<String>
    • GenericArrayType: Parameterized type or type variable array type when representing an element type
    • TypeVariable: is the public parent interface of various types of variables
    • WildcardType: Represents a wildcard type expression

 

package com.java.demo.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {

    public void test01(Map<String, User> map, List<User> list) {
        System.out.println("test01");
    }

    public Map<String, User> test02() {
        System.out.println("test02");
        return null;
    }

    // 通过反射获取泛型
    public static void main(String[] args) throws NoSuchMethodException {
        Method test01 = Test01.class.getMethod("test01", Map.class, List.class);
        // 获取通用参数类型
        Type[] genericParameterTypes = test01.getGenericParameterTypes();
        // 迭代遍历
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("# " + genericParameterType);
            if (genericParameterType instanceof ParameterizedType) {
                // 获取实际参数类型
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        Method test02 = Test01.class.getMethod("test02", null);
        Type genericReturnType = test02.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

Output:
Insert picture description here

Reflection operation notes

  • getAnnotations
  • getAnnotation

First define two annotations, we will use them later

// 类名注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tables {
    String value();
}

// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fields {
    String columnName();
    String type();
    int length();
}

Then we use the annotations we defined in the entity class (isn’t it a bit like JPA?)

@Tables("db_student")
public class Student {

    @Fields(columnName = "db_id", type = "int", length = 10)
    private int id;

    @Fields(columnName = "db_age", type = "int", length = 10)
    private int age;

    @Fields(columnName = "db_name", type = "varchar", length = 3)
    private String name;

    public Student() {
    }

    public Student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

Then reflect to get the annotation information

public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.java.demo.reflect.Student");

        // 通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 获得注解的 value 值
        Tables tables = (Tables) c1.getAnnotation(Tables.class);
        String value = tables.value();
        System.out.println(value);

        // 获得类指定的注解
        Field field = c1.getDeclaredField("name");
        Fields annotation = field.getAnnotation(Fields.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

Output:
Insert picture description here

Expand

Scenarios and implementation of custom annotations

    Login, permission interception, log processing, and various Java frameworks, such as Spring, Hibernate, and Junit. When it comes to annotations, reflection cannot be ignored. Java custom annotations obtain annotations through reflection at runtime.

    In actual development, for example, if we want to obtain the call log of a certain method, we can add aspects to the method through AOP (dynamic proxy mechanism), and obtain the annotations contained in the method through reflection. If log annotations are included, log records are performed.

Guess you like

Origin blog.csdn.net/qq_32445015/article/details/108833149