Reflection and Annotation and a Case of Combination of the Two

1. Reflection

1. Reflection overview

This is described in the java.lang.reflect package:

Provides classes and interfaces to obtain reflective information about classes and objects.
Within security constraints, reflection allows programmatic access to fields about loaded classes,
method and constructor information, and allows the use of reflection fields,
Methods and constructors operate on their underlying counterparts. 

Reflection refers to allowing programmatic access to components of a loaded class (member variables, methods, constructors, etc.).

After knowing what reflection is, let's learn what reflection is specifically about.

Because reflection obtains class information, the first step of reflection first obtains the class. Since the design principle of Java is that everything is an object, the obtained class is actually embodied in the form of an object, called a bytecode object, and represented by the Class class. After the bytecode object is obtained, the components of the class can be obtained through the bytecode object. These components are actually objects. Each member variable is represented by an object of the Field class, and each member method is represented by a Method Each constructor is represented by an object of the Constructor class. Right now:

  1. Load the class to get the bytecode of the class: Class object
  2. Get the constructor of the class: Constructor object
  3. Get the member variables of the class: Field object
  4. Get the member method of the class: Method object

Let's start learning one by one! ! !

2. Obtain the bytecode of the class (important)

The first step of reflection is to load the bytecode into memory, and we need to obtain the bytecode object.

For example, if there is a Student class, there are three ways to write the bytecode code of the Student class. No matter which method is used, the obtained bytecode object is actually the same.

public class Test1Class{
    public static void main(String[] args){
        //(Method 1) Obtain the parameter through the static method forName of the Class class as the fully qualified class name
        Class c2 = Class.forName("com.itheima.d2_reflect.Student");
        System.out.println(c1 == c2); //true
        
        //(Method 2) Obtain the Class object through the class name.class
        Class c1 = Student.class;
        System.out.println(c1.getName()); //Get the full class name
        System.out.println(c1.getSimpleName()); //get simple class name
        
        // (Method 3) Get it through the getClass method in Object
        Student s = new Student();
        Class c3 = s.getClass();
        System.out.println(c2 == c3); //true
    }
}

By comparing the addresses of the Class objects obtained by the three methods, we can see that the three of them have the same address, so obtaining the Class objects through these three methods is equivalent. In fact, this is the object obtained in three different stages of the class, which are the compilation, loading and running stages.

After knowing how to get the bytecode object, it is very clear when we look at the equals method that we always used before;

The first step; first judge whether the two objects are the same address. If it is the same address, there is no need to compare and return directly. (compare addresses)

The second step; first judge whether it is null, if it is null, there is no need to compare and return false directly. Then judge whether the bytecode objects of the two objects are the same. Because there is only one copy of the bytecode file of the same class in the memory, if it is different, return it directly (compare the bytecode), if it is the same, it means that it must be the implementation class of the current class. Just cast

Finally: comparing the contents of it. Primitive data types are compared directly, and reference data types are compared by methods. return the result of the comparison

    @Override
    public boolean equals(Object o) {
        // this  o
        if (this == o) return true;
        if (o == null || this.getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

3. Get the constructor of the class

The first step of reflection is to get the class object first, and then get the component objects of the class from the class object.

The method used to obtain the constructor in the Class class

methodillustrate
Constructor<?>[] getConstructors()Returns an array of all constructor objects (only public ones)
Constructor<?>[] getDeclaredConstructors()Returns an array of all constructor objects, you can get them if they exist
Constructor getConstructor(Class<?>... parameterTypes)Return a single constructor object (only public ones)
Constructor getDeclaredConstructor(Class<?>... parameterTypes)Return a single constructor object, you can get it if it exists

There is a little trick when remembering methods. First of all, we must know the word of the Constructor. If you want to get a multi-band Constructor, then use the long name. That is, the longer the more the more methods. The short one is to get a constructor that is only modified by public

Sample code:

public class Cat{
    private String name;
    private int age;
    // public parameterless constructor
    public Cat(){   
    }
    // private full parameter constructor
    private Cat(String name, int age){       
    }
}

get all constructors

public class Test2Constructor(){
    @Test
    public void testGetConstructors(){
        //1. The first step of reflection: you must first get the Class object of this class
        Class c = Cat.class;
        
        //2. Get all the constructors of the class
        Constructor[] constructors = c.getDeclaredConstructors();
        //3. Traverse each constructor object in the array.
        for(Constructor constructor: constructors){
            System.out.println(constructor.getName()+"---> Number of parameters:"+constructor.getParameterCount());
        }
    }
}

Console result:

com.yfs1024.test.Cat---> Number of parameters: 0
com.yfs1024.test.Cat---> Number of parameters: 2

Get a single constructor

The parameter needs to pass in the class of the corresponding parameter. If it is a packaging type, use packaging class.TYPE

public class Test2Constructor(){
    @Test
    public void testGetConstructor(){
        //1. The first step of reflection: you must first get the Class object of this class
        Class c = Cat.class;
        
        //2. Obtain the empty parameter constructor modified by the public class
        Constructor constructor1 = c.getConstructor();
        System.out.println(constructor1.getName()+"---> Number of parameters:"+constructor1.getParameterCount());
        
        //3. Obtain a private modified constructor with two parameters, the first parameter is of type String, and the second parameter is of type int
        Constructor constructor2 = 
            c.getDeclaredConstructor(String.class,int.class);
        
        System.out.println(constructor2.getName()+"---> Number of parameters:"+constructor1.getParameterCount());

    }
}

Console result:

com.yfs1024.test.Cat---> Number of parameters: 0
com.yfs1024.test.Cat---> Number of parameters: 2

4. The role of reflection acquisition constructor

After getting the constructor, what is the function? It's a good idea, in fact, the role of the constructor is to initialize the object and return it.

Here we need to use the following two methods. Note: these two methods belong to the Constructor and need to be called by the Constructor object.

symbolillustrate
T newInstance(Object... initargs)Create an object according to the specified constructor
public void setAccessible(boolean flag)Set to true to cancel the access check and perform violent reflection

As shown in the figure below, constructor1 and constructor2 respectively represent two constructors in the Cat class. Now I'm going to implement these two constructors

Since the constructor is private ly modified, it is necessary to call setAccessible(true) to indicate that checking access control is prohibited, and then call newInstance (actual parameter list) to execute the constructor and complete the initialization of the object.

    @Test
    public void getConstructor() throws Exception {
        Class<Cat> catClass = Cat.class;
        Constructor<Cat> constructor = catClass.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);  // Close Check Access ---> Violent Reflection
        Cat cat = constructor.newInstance("zs",12);
        System.out.println(cat);
    }

After learning the above method, in fact, we generally only use one constructor to obtain objects in development. We never said, come on, traverse a constructor and create a bunch of objects, so we only need to master how to obtain an object. And we generally use no-argument construction, and our common method of obtaining no-argument construction is usually to use the current bytecode Class object to create. As follows: The premise is that the constructor is not private, if so, then the above method can be used

    @Test
    public void getConstructor() throws Exception {
        Class<Cat> catClass = Cat.class;
        Cat cat = catClass.newInstance();
        System.out.println(cat);
    }

5. Member variables obtained by reflection and their use

In fact, the routine is the same. The method of obtaining member variables is provided in the Class class, as shown in the figure below.

methodillustrate
Field[] getFields()Returns an array of all member variable objects (only public ones)
Field[] getDeclaredFields()Returns an array of all member variable objects, which can be obtained if they exist
Field getField(String name)Return a single member variable object (only public ones)
Field getDeclaredField(String name)Return a single member variable object, you can get it if it exists

Suppose there is a Cat class which has several member variables, use the methods provided by the Class class to get the objects of the member variables.

After executing the above code, we can see the output on the console, the name and type of each member variable.

How to use it after getting the object of the member variable?

The methods for assigning and obtaining values ​​to member variables are provided in the Filed class, as shown in the figure below.

symbolillustrate
void set(Object obj, Object value): assignment
Object get(Object obj)Get the value.
    @Test
    public void getFiles() throws Exception {   // Member variables are private, constructors are public
        Class<Cat> catClass = Cat.class;
        Cat cat = new Cat("little flower",23);
        Field name = catClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(cat,"Xiaohua's father");
        System.out.println(name.get(cat)); // Xiaohua's father

    }

Emphasize that the method of setting and obtaining the value of the Filed class needs to be called by the object of the Filed class, and whether it is setting the value or obtaining the value, it needs to depend on the object to which the variable belongs.

6. Reflection to obtain member methods (important)

In the reflection package in Java, each member method is represented by a Method object, and the member method object in the class can be obtained through the methods provided by the Class class. as follows:

methodillustrate
Method[] getMethods()Returns an array of all member method objects (only public ones can be obtained), which can also be obtained from the public method of the parent class
Method[] getDeclaredMethods()Returns an array of all member method objects, you can get it if it exists
Method getMethod(String name, Class<?>... parameterTypes)Return a single member method object (only public ones)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)Return a single member method object, you can get it if it exists

Next, let's demonstrate it with code: Suppose there is a Cat class, and there are several member methods in the Cat class

public class Cat{
    private String name;
    private int age;
    
    public Cat(){
        System.out.println("The empty parameter constructor executes");
    }
    
    private Cat(String name, int age){
        System.out.println("There is a parameter construction method executed");
        this.name=name;
        this.age=age;
    }
    
    private void run(){
        System.out.println("(>^ω^<)Meow runs so fast~~");
    }
    
    public void eat(){
        System.out.println("(>^ω^<)Meow loves cat food~");
    }
    
    private String eat(String name){
        return "(>^ω^<)Meow loves to eat:"+name;
    }
    
    public void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void setAge(int age){
        this.age=age;
    }
     public int getAge(){
        return age;
    }
}

Next, get all the member methods in the Cat class through reflection, each member method is a Method object

public class Test3Method{
    public static void main(String[] args){
        //1. The first step of reflection: first obtain the Class object
        Class c = Cat.class;
        
        //2. Get all member methods in the class
        Method[] methods = c.getDecalaredMethods();
        
        //3. Traverse each method object in this array
        for(Method method : methods){
            System.out.println(method.getName()+"-->"+method.getParameterCount()+"-->"+method.getReturnType());
        }
    }
}

Method name Number of parameters Return value type

getName-->0-->class java.lang.String
run-->0-->void
setName-->1-->void
eat-->1-->class java.lang.String
eat-->0-->void
setAge-->1-->void
getAge-->0-->int

Get a single specified member method:

    public static void main(String[] args) throws Exception {
        //1. The first step of reflection: first obtain the Class object
        Class c = Cat.class;

        //2. Get the eat method in the class
        Method eat = c.getDeclaredMethod("eat");
        System.out.println("Number of parameters:"+eat.getParameterCount());

//        get eat(String name)
        Method eat1 = c.getDeclaredMethod("eat", String.class);
        System.out.println("Number of parameters:"+eat1.getParameterCount());

    }

Console:

Number of parameters: 0
 Number of parameters: 1

After obtaining the member method, what is the function?

The method is provided in the Method class, and the method can be executed by itself.

symbolillustrate
Object invoke(Object obj, Object... args)Run the method Parameter 1: call the method with the obj object Parameter 2: the passed parameters of the calling method (if not written) Return value: the return value of the method (if not written)

Execute the run() method and the eat(String name) method. Look at the code below the dividing line

        //1. The first step of reflection: first obtain the Class object
        Class c = Cat.class;
        //4. Get the private modified run method and get the Method object
        Method run = c.getDeclaredMethod("run");
        //To execute the run method, the permission check needs to be canceled before execution
        Cat cat = new Cat();
        run.setAccessible(true);
        Object rs1 = run.invoke(cat); // No return value returns null
        System.out.println(rs1);

        //5. Obtain the private modified eat(String name) method and get the Method object
        Method eat = c.getDeclaredMethod("eat",String.class);
        eat.setAccessible(true);
        Object rs2 = eat.invoke(cat,"the fish");
        System.out.println(rs2);
The empty parameter constructor executes
(>^ω^<)Meow runs so fast~~
null
(>^ω^<)Meow loves to eat:the fish

7. Application cases of reflection

Case 1

Below we will make a comparison between using and not using reflection to appreciate the beauty of reflection.

Requirements: There is a fruit interface, there are some implementation classes such as apples and oranges, and the parent class has a method for obtaining juice. There is also a filtering class for processing the extracted fruit juice.

Instead of using reflection, we need to implement the following method: Fruit interface

// Fruit interface, there is a method to get juice
public interface Fruit {
    public void getJuice();
}

Apples

public class Apple implements Fruit{
    @Override
    public void getJuice() {
        System.out.println("squeeze apple juice");
    }
}

Orange Juice:

public class Orange implements Fruit{
    @Override
    public void getJuice() {
        System.out.println("get a glass of orange juice");
    }
}

Filter class:

public class MainGuoLv {
    public static void main(String[] args) throws Exception {
       // guoLv(new Apple());
        guoLv(new Orange());
    }
    // filter method
    public static void guoLv(Fruit fruit){
        // get juice
        fruit.getJuice();
        System.out.println("filter complete");
    }
}

When we learn the advantage of polymorphism, we only need to use the parent class to receive the subclass object, and we can use one method to process different water methods.

But there is a problem, if I filter the orange juice now, I need to create an orange juice object. Then if I want to filter the apple juice, I need to pass in an apple juice object. Seems simple enough, right? But there is a problem, that is, if this function is launched, the code after the launch is a compiled class file, that is, a bytecode file. Then if we want to change the requirements again, filter apple juice for a while and orange juice for a while, then it's over, we need to modify the java file, and then compile it into a class file. This is a local modification. Then if you go to a configuration place, you have to recompile every time, isn't it too much trouble.

Then let's see how to solve it with reflection? We can manipulate bytecode files

We don’t move the Fruit interface, apple class, and orange class. We only need to modify the filter class by reflection: the usual way is this, we configure a file, because txt files will not be compiled (properties files also Yes), we have learned to get the bytecode object of the class through the class name. With the bytecode object, we can operate the methods in this class. The implementation is as follows:

Prepare a Fruit.txt, the content is as follows

fruitName=com.yfs1024.reflectTest02.Apple

The code after modifying the filter class to reflection is as follows:

public class MainGuoLv {
    public static void main(String[] args) throws Exception {
//        (1) Get the fully qualified class name of the file
        Properties properties = new Properties();
//        (2) Load the file,
        properties.load(new FileInputStream("javaEE-day14\\fruit.txt"));
//        (3) Get the value by key
        String fruitName = properties.getProperty("fruitName");
//        (4) After getting the fully qualified class name, you can get the bytecode object through the static method in Class
        Class<?> aClass = Class.forName(fruitName);
//        (5) Create objects through bytecode files
        Fruit fruit = (Fruit)aClass.newInstance();   // This is commonly used to create objects. Of course, it can also be created by obtaining the constructor.
//        (6) Call method
        guoLv(fruit);
    }
//       filter method
    public static void guoLv(Fruit fruit) {
        fruit.getJuice();
        System.out.println("filter complete");
    }
}

It may look like a lot of code, but it has been unsealed by the java file. At this time, if we want to filter the orange juice, we can directly modify it in the Fruit.txt file. No need to change the source code

fruitName=com.yfs1024.reflectTest02.Orange

Case 2:

When we learned the method just now, we also saw that we can get all the methods through the bytecode object (Class object). So we can make some articles in the method? certainly.

There is such a tool class: I want to input specific data through the console, and implement the method to call specific functions, so let’s look at the benefits of reflection by not using reflection and using reflection

public class MyUtils {

    public static int getSum(int a,int b){
        return a + b;
    }

    public static int minus(int a,int b){
        return a - b;
    }

    public static int multiply(int a,int b){
        return a * b;
    }

    public static int by(int a,int b){
        return a / b;
    }
}

Code without reflection:

        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the first number");
        int a = scanner.nextInt();
        System.out.println("Please enter the second number");
        int b = scanner.nextInt();
        System.out.println("Please enter the method name to execute");
        String methodName = scanner.next();
// Judgment, pass in parameters and execute Here you will find that if one or two methods are needed, if ten or twenty, is it necessary to write
		if(methodName.equals("getSum")){
            int sum = Utils.getSum(a, b);
            System.out.println(sum);
        }else if(methodName.equals("minus")){
            int sum = Utils.minus(a, b);
            System.out.println(sum);
        }else if(methodName.equals("multiply")){
            int sum = Utils.multiply(a, b);
            System.out.println(sum);
        }else if(methodName.equals("by")){
            int sum = Utils.by(a, b);
            System.out.println(sum);
        }

Use reflection to accomplish the requirement:

        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the first number");
        int a = scanner.nextInt();
        System.out.println("Please enter the second number");
        int b = scanner.nextInt();
        System.out.println("Please enter the method name to execute");
        String methodName = scanner.next();
        Class<Utils> utilsClass = MyUtils.class;
        Method method = utilsClass.getMethod(methodName, int.class, int.class);//getSum
// I thought it was a static method, so there is no need to pass in an object, just pass in null
        Object result = method.invoke(null,a,b);
        System.out.println(result);

Moreover, no matter how many similar methods there are, of course, because this is an example, the parameters are all the same. If they are different, we can pass in an array when getMethod and invoke by obtaining some judgments. , because the second parameter is a variable parameter.

2. Notes

1. Understanding annotations & defining annotations

Annotations, like reflections, are used as frameworks. The purpose of learning annotations here is actually to pave the way for future learning of frameworks or frameworks.

How to learn the annotation? Like the reflection learning routine, we first fully understand annotations, master the definition and usage format of annotations, and then learn its application scenarios.

Let's first understand what is an annotation?

Java annotations are special tags in the code, such as @Override, @Test, etc., whose function is to let other programs decide how to execute the program based on the annotation information.

For example: the @Test annotation of the Junit framework can be used on a method to mark the method as a test method, and the method marked by @Test can be executed by the Junit framework.

Another example: the @Override annotation can be used on a method to mark the method as an overridden method, and the method marked by the @Override annotation can be recognized by IDEA for syntax checking.

  • Annotations can be used not only on methods, but also on classes, variables, constructors, etc.

The @Test annotations and @Overide annotations we mentioned above are defined by others for us to use. If we need to develop the framework ourselves in the future, we need to define the annotations ourselves. So if you can know how to use it, if you don’t know how to use it

Then we learn custom annotations

The format of the custom annotation is shown in the figure below

The following notes and considerations are important

in annotation:The return value type of the abstract method is limited to the following
       basic type
       string type
       bytecode type
       enumerated type
       Annotation type
       One-dimensional arrays of the above types
           
   Abstract methods cannot have parameters
           
Precautions:
       (1)When the method named value when, Use annotations, if only right value when assigning, value can be omitted
       (2)When the return value of the method is an array, But when your value is only one,{}can be omitted!!!!
       (3)Our abstract method can be accessed via default Given a default return value!!!,If the default return value is given, the user does not need to rewrite!!!!!!!!!!!!

Annotations can be defined in the following ways:

public @interface A {
    //define constant
    int age = 10;
    //define abstract method
    int value() default 18;
    String method1() default "jack";
    String[] method2() default {};
    Class method3();
    Week method4();    // enumerate
    Override method5();
    String[] method6();
}

For example: Now we customize a MyTest annotation

public @interface MyTest{
    String aaa();
    boolean bbb() default true;	//default true means the default value is true, and it is not necessary to assign a value when using it.
    String[] ccc();
}

After defining the MyTest annotation, we can use the MyTest annotation to mark on the class, method, etc. Note that the @ symbol needs to be added when using annotations, as follows

@MyTest1(aaa="bull devil",ccc={"HTML","Java"})
public class AnnotationTest1{
    @MyTest(aaa="iron fan princess",bbb=false, ccc={"Python","front end","Java"})
    public void test1(){
        
    }
}

Note: If the attribute name of the annotation is value, and only value has no default value, the value name can be omitted when using annotations. For example, now redefine a MyTest2 annotation

public @interface MyTest2{
    String value(); //special attribute
    int age() default 10;
}

After defining the MyTest2 annotation, mark @MyTest2 on the class. At this time, the value attribute name can be omitted. The code is as follows

@MyTest2("Sun Wukong") //Equivalent to @MyTest2(value="Monkey King")
@MyTest1(aaa="bull devil",ccc={"HTML","Java"})
public class AnnotationTest1{
    @MyTest(aaa="iron fan princess",bbb=false, ccc={"Python","front end","Java"})
    public void test1(){
        
    }
}

What is the essence of annotation?

To find out what the essence of the annotation is, we can decompile the bytecode of the annotation and use the XJad tool to decompile it. After decompiling the bytecode of MyTest1 annotation, we will find:

1.MyTest1 Annotations are essentially interfaces, and each annotation interface inherits children Annotation interface
2.MyTest1 Properties in annotations are essentially abstract methods
3.@MyTest1 actually as MyTest The implementation class object of the interface
4.@MyTest1(aaa="Sun Wukong",bbb=false,ccc={"Python","front end","Java"})Inside the attribute value, you can call aaa(),bbb(),ccc()method is obtained.  [Don't worry, keep reading, we will use it when parsing annotations]

2. Meta annotations

Next, we need to learn several special annotations, called meta-annotations.

What are meta annotations?

That is: the annotation of the modification annotation haha, it is a bit convoluted, but it is easy to understand

This is the case, we roughly know from the previous reflections that there are many stages in the middle from the java source code to the actual operation, so we need to use an annotation to tell the jvm when this class or method is what we need. Secondly, we can use some annotations to restrict the location of this annotation, whether it is in a class, a method, or a member variable.

These two annotations are @Target, @Retetion

@Target It is used to declare that annotations can only be used in those positions, such as: on classes, on methods, on member variables, etc.
@Retetion It is used to declare the annotation retention period, such as: source code period, bytecode period, runtime period

Note: Our usual cycle for keeping annotations is RUNTIME

Example:

@Target The use of meta annotations: such as defining a MyTest3 Annotate, and add@Target Annotations are used to declare MyTest3 where to use

Next, we use @MyTest3 on the class to observe whether there is an error, and then use @MyTest3 on the method and variable to observe whether there is an error

If we define the MyTest3 annotation, use the @Target annotation attribute value to write as follows

//Declare that the @MyTest3 annotation can only be used on classes and methods
@Target({ElementType.TYPE,ElementType.METHOD})	
public @interface MyTest3{
    
}

At this point, the use of @Target meta-annotations may be more than enough, let’s learn @Retetion meta-annotations

The use of @Retetion meta-annotation: When defining the MyTest3 annotation, add the @Retetion annotation to the MyTest3 annotation to declare the retention period of the MyTest3 annotation

@Retetion It is used to declare the annotation retention period, such as: source code period, bytecode period, runtime period
	@Retetion(RetetionPloicy.SOURCE): Annotations are retained until the source code period, and there will be no bytecode
	@Retetion(RetetionPloicy.CLASS): Annotations are retained in the bytecode, runtime annotations are gone
	@Retetion(RetetionPloicy.RUNTIME): Annotations persist until runtime

[When writing code by yourself, it is more common to keep it until the runtime]

//Declare that the @MyTest3 annotation can only be used on classes and methods
@Target({ElementType.TYPE,ElementType.METHOD})	
//In the code that controls the use of @MyTest3 annotations, @MyTest3 is reserved until runtime
@Retetion(RetetionPloicy.RUNTIME)
public @interface MyTest3{
    
}

3. Analyzing annotations (combined cases of annotations and reflections)

Through the previous study, we can define annotations by ourselves, and we can also mark the annotations we define on the class or method, but it always feels a bit awkward. After adding annotations to classes, methods, variables, etc., we have nothing to do. ! ! !

Next, we're going to do something. We can get the annotation objects on the class, method, and variable through reflection technology, and then get the attribute value on the annotation by calling the method. We call the process of obtaining position annotations on classes, methods, variables, etc. and annotation attribute values ​​​​as parsing annotations.

The analysis annotation routine is as follows

1.If the annotation is on the class, first get the bytecode object of the class, and then get the annotation on the class
2.If the annotation is on the method, first get the method object, and then get the annotation on the method
3.If the annotation is on the member variable, first get the member variable object, and then get the annotation on the variable
 In short: whoever the annotation is on, get the first person, and then use whoever gets the annotation

The operation of annotations often needs to be parsed. The parsing of annotations is to judge whether there are annotations, and if there are annotations, the content is parsed out.

Interfaces related to annotation parsing

Annotation: the top-level interface of annotations, all annotations are objects of type Annotation
AnnotatedElement: This interface defines the parsing methods related to annotation parsing

methodillustrate
Annotation[] getDeclaredAnnotations()Get all annotations used on the current object, returning an array of annotations.
T getDeclaredAnnotation(Class annotationClass)Obtain the corresponding annotation object according to the annotation type
boolean isAnnotationPresent(Class annotationClass)Determine whether the current object uses the specified annotation, if used, return true, otherwise false

All class components Class, Method, Field, and Constructor implement the AnnotatedElement interface and they all have the ability to parse annotations:

Let’s take a look at a case to demonstrate the code writing of parsing annotations

(1) Define a comment for MyTest4

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

    double aaa() default 100;

    String[] bbb();
}

(2) Define a class Demo

@MyTest4(value = "class value Schrödinger's cat",aaa = 100,bbb = {"on the class bbb Laplacemon","on the class bbb Laplacemon 2"})
public class Demo {
    public static void main(String[] args) {

    }
    @MyTest4(value = "method on zeno's tortoise",aaa = 800,bbb = {"Method on Maxwell's Demon 1","Method on Maxwell's Demon 2"})
    private void test1() {

    }
}

③ Write a test class AnnotationTest3 to parse the MyTest4 annotation on the Demo class

public class AnnotationTest3 {
    public static void main(String[] args) throws NoSuchMethodException {
//        Get the bytecode object of the annotated class
        Class<Demo> demoClass = Demo.class;
//        Get the annotations in the class
        MyTest4 annotation = demoClass.getAnnotation(MyTest4.class);
//        Get the value attribute of the MyTest4 annotation in the class
        String value = annotation.value();
        System.out.println("in class value the value of:" + value);
//        Get the value attribute of the MyTest4 annotation in the class
        double aaa = annotation.aaa();
        System.out.println("in class aaa the value of:" + aaa);
//        Get the bbb attribute in the MyTest4 annotation in the class
        String[] bbb = annotation.bbb();
//        Traverse the output
        for (String s : bbb) {
            System.out.println("in class bbb[]the value of:" + s);
        }

        //(2) in the acquisition method
        Method test1 = demoClass.getDeclaredMethod("test1");
        MyTest4 annotation1 = test1.getAnnotation(MyTest4.class);
//        Get the value attribute of the MyTest4 annotation in the method
        String value1 = annotation1.value();
        System.out.println("method value the value of:" + value1);
//        Get the value attribute of the MyTest4 annotation in the method
        double aaa1 = annotation1.aaa();
        System.out.println("method in aaa the value of:" + aaa1);
//        Get the bbb attribute in the MyTest4 annotation in the method
        String[] bbb1 = annotation1.bbb();
//        Traverse the output
        for (String s : bbb1) {
            System.out.println("method bbb[]the value of:" + s);
        }

    }
}
in class value the value of:class value Schrödinger's cat
 in class aaa the value of:100.0
 in class bbb[]the value of:on the class bbb Laplacemon
 in class bbb[]the value of:on the class bbb Laplacemon 2
 method value the value of:method on zeno's tortoise
 method in aaa the value of:800.0
 method bbb[]the value of:Method on Maxwell's Demon 1
 method bbb[]the value of:Method on Maxwell's Demon 2

4. Application scenarios of annotations

Next, let’s learn about the application scenarios of annotations. Annotations are used to write frameworks. For example, now we want to simulate Junit to write a test framework, which requires methods with @MyTest annotations to be executed by the framework, but methods without @MyTest annotations cannot implemented by the framework.

The first step: first define a MyTest annotation

@Target(ElementType.METHOD)	
@Retetion(RetetionPloicy.RUNTIME)
public @interface MyTest{
    
}

Step 2: Write a test class AnnotationTest4, and define several methods marked by @MyTest annotation in the class

public class AnnotationTest4{
    @MyTest
    public void test1(){
        System.out.println("=====test1====");
    }
    
    @MyTest
    public void test2(){
        System.out.println("=====test2====");
    }
    

    public void test3(){
        System.out.println("=====test2====");
    }
    
    public static void main(String[] args){
        AnnotationTest4 a = new AnnotationTest4();
        
        //1. Get the Class object first
        Class c = AnnotationTest4.class;
        
        //2. Parse all method objects in the AnnotationTest4 class
        Method[] methods = c.getDeclaredMethods();
        for(Method m: methods){
            //3. Determine whether there is a MyTest annotation on the method, and execute the method if there is one
            if(m.isAnnotationPresent(MyTest.class)){
            	m.invoke(a);
        	}
        }
    }
}

Tags: Java jvm

Posted by vomitbomb on Thu, 16 Mar 2023 03:22:51 +1030