Java advanced unit testing and reflection

1. Junit testing tool

@Test defines test method
1. Methods marked with @BeforeClass are executed before all methods
2. Methods marked with @AfterCalss are executed after all methods
3. Methods marked with @Before are executed before each @Test method
4. Methods marked with @After are executed after each @Test method

public class StringUtilTest{
    @Before
    public void test1(){
        System.out.println("--> test1 Before was executed");
    }
    @BeforeClass
    public static void test11(){
        System.out.println("--> test11 BeforeClass executed");
    }
    @After
    public void test2(){
        System.out.println("--> test2 After was executed");
    }
    @AfterCalss
    public static void test22(){
        System.out.println("--> test22 AfterCalss was executed");
    }
}

2. Reflection

Definition: Reflection technology refers to loading the bytecode of a class into memory, and using programming methods to analyze the various components of the class (member variables, methods, constructors, etc.)

2.1. Obtain class object (bytecode object)

Since the design principle of Java is that everything is an object, the obtained class is actually embodied in the form of an object, which is called a bytecode object and is represented by the Class class. After obtaining the bytecode object, you can obtain the components of the class through the bytecode object. These components are actually objects, in which each member variable is represented by an object of the Field class , Each member method is represented by an object of the Method class, and Each constructor is represented by an object of the Constructor class.

Three ways to obtain Class objects

  • Class c1=class name.class
  • Call the method provided by Class: public static Class forName(String package);
  • Methods provided by Object: public Class getClass();Class c3=object getClass();

2.2 Get the constructor of the class

Class provides methods to obtain constructors from classes
method
Constructor<?>[] getConstructors() gets all constructors (only public modified ones can be obtained)
Constructor<?>[] getDeclaredConstructors() Get all constructors (you can get them as long as they exist)
Constructor<T> getConstructor(Class<?>... parameterTypes) Get a certain constructor (only public modified ones can be obtained)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) Get a certain constructor (you can get it as long as it exists)

get: get
Declared: With this word, you can get any one. Without this word, you can only get one public modified one.
Constructor: The meaning of constructor method
Suffix s: means you can get multiple ones, without suffix you can only get one

2.3 Obtain the function of the constructor

Get the function of the class constructor: still initialize the object and return it
Methods provided by Constructor
T newInstance(Object... initargs) (can be any number of parameters) calls the constructor represented by this constructor object, passes in the parameters, completes the initialization of the object and returns

public void setAccessible(boolean flag) is set to true, which means that checking access control (violent reflection) is prohibited

2.4 Reflection to obtain member variables & use

Class provides methods to obtain member variables from the class
method
public Field[] getFields() Gets all member variables of the class (only public modified ones can be obtained)
public Field[] getDeclaredFields() Gets all member variables of the class (you can get them as long as they exist)
public Field getField(String name) Gets a member variable of the class (only public modified ones can be obtained)
public Field getDeclaredField(String name) Gets a member variable of the class (you can get it as long as it exists)
Get the function of member variables: they are still assignment and value acquisition.
method
void set(Object obj, object value): Assignment (need to pass in the object, otherwise you will not know which object to assign value to)
object get(Object obj) value
public void setAccessible(boolean flag) is set to true, which means that checking access control is prohibited (violent reflection)

2.5 Obtaining and values of member methods

Get the member methods of the class
Class provides an API to obtain member methods from the class.
method
Method[] getMethods() gets all member methods of the class (only public modified ones can be obtained)
Method[] getDeclaredMethods() Gets all member methods of the class (you can get them as long as they exist)
Method getMethod(String name, Class<?>... parameterTypes) Gets a member method of the class (only public modified ones can be obtained)
Method getDeclaredMethod(String name, Class<?>.. parameterTypes) Gets a member method of the class (you can get it as long as it exists)

The role of member methods: still executes
Method provided by Method
public Object invoke(Object obj, Object... args) triggers the execution of this method on an object.
public void setAccessible(boolean flag) is set to true, which means that checking access control (violent reflection) is prohibited

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());
    }
    
    System.out.println("-----------------------");
    //4. Get the private modified run method and get the Method object
    Method run = c.getDecalaredMethod("run");
    //Execute the run method, you need to cancel the permission check before execution
    Cat cat = new Cat();
    run.setAccessible(true);
    Object rs1 = run.invoke(cat);
    System.out.println(rs1)
    
    //5. Get 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,"鱼儿");
    System.out.println(rs2)
}

}

Get the results returned by the operation

2.6 The role of reflection

Reflection is used to write the framework, that is, no matter what type of object is input, we can get the corresponding class file to do some unified processing. Let’s write a framework that can write the attribute name and attribute value of any object to a file. It doesn’t matter how many properties the object has, or whether the object’s property names are the same.

1. First write two classes, a Student class and a Teacher class
2. Write an ObjectFrame class to represent the frame
Define a saveObject(Object obj) method in the ObjectFrame class to save any object to a file.
Parameters: Object obj: represents the object to be stored in the file
\t
3. Write the code inside the method and store the object’s attribute name and attribute value in the file.
1) What attributes are there in the parameter obj object, what are the attribute names and what are the implementation values, the object in it knows best.
2) Then obtain the member variable information of the class through reflection (variable name, variable value)
3) Write the variable name and variable value to the file

Write an ObjectFrame to represent the framework you designed. The code is as shown below

public class ObjectFrame{
    public static void saveObject(Object obj) throws Exception{
        PrintStream ps =
            new PrintStream(new FileOutputStream("Module name\src\data.txt",true));
        //1) What are the attributes in the parameter obj object, what are the attribute names, and what are the implementation values. The objects in the object know best.
//2) Then obtain the member variable information of the class through reflection (variable name, variable value)
        Class c = obj.getClass(); //Get bytecode
        ps.println("---------" + class.getSimpleName() + "---------");
        
        Field[] fields = c.getDeclaredFields(); //Get all member variables
//3) Write the variable name and variable value to the file
        for(Field field : fields){
            String name = field.getName();
            Object value = field.get(obj) + "";
            ps.println(name);
        }
        ps.close();
    }
}

Use the framework you designed to write the information of the Student object and the information of the Teacher object into the file.

Prepare the Student class and Teacher class first

public class Student{
    private String name;
    private int age;
    private char sex;
    private double height;
    private String hobby;
}
public class Teacher{
    private String name;
    private double salary;
}

Create a test class. In the test class, create a Student object and a Teacher object. Use the ObjectFrame method to write all the attribute names and attribute values of these two objects to the file.

public class Test5Frame{
    @Test
    public void save() throws Exception{
        Student s1 = new Student("Dark Horse Daniel Wu",45, 'Male', 185.3, "Basketball, Ice Hockey, Reading");
        Teacher s2 = new Teacher("博妞",999.9);
        
        ObjectFrame.save(s1);
        ObjectFrame.save(s2);
    }
}

The end result is that different types of information are saved to files

Use of 3 annotations

Annotations are used together with reflection to serve the implementation of the framework. We can understand it as the JUnit annotation, which tells the system that it will be executed after adding this annotation. It is also similar to the meaning of defining beans in spring. You can know which beans are in the system through annotations.

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

3.1 Custom annotation format

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

Insert image description here
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 attributes
    
}
@MyTest2("Sun Wukong") //Equivalent to @MyTest2(value="Sun Wukong")

3.2 What is the essence of annotation

1.MyTest1 annotations are essentially interfaces. Each annotation interface inherits the sub-Annotation interface.
2.The attributes in the MyTest1 annotation are essentially abstract methods
3.@MyTest1 is actually the implementation class object of the MyTest interface
4. The attribute values in @MyTest1(aaa=”Sun Wukong”, bbb=false, ccc={“Python”, “front-end”, “Java”}) can be obtained by calling aaa(), bbb(), ccc() method to obtain

3.3 yuan annotation

Meta-annotations are annotations that modify annotations

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


For example, the @target of test is type, and @retention is runtime.

3.4 Parsing annotations

1. If the annotation is on the class, first obtain the bytecode object of the class, and then obtain the annotation on the class
2. If the annotation is on the method, first obtain the method object, and then obtain the annotation on the method.
3. If the annotation is on a member variable, first obtain the member variable object, and then obtain the annotation on the variable.
In short: whoever the annotation is on, get it first, and then use whoever it is to get the annotation on that person.
public class AnnotationTest3{
    @Test
    public void parseClass(){
        //1. Get the Class object first
        Class c = Demo.class;
        
        //2. Parse the annotations on the Demo class
        if(c.isAnnotationPresent(MyTest4.class)){
            //Get the MyTest4 annotation on the class
            MyTest4 myTest4 = (MyTest4)c.getDeclaredAnnotation(MyTest4.class);
            //Get the attribute value of MyTests4 annotation
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(myTest4.bbb());
        }
    }
    
    @Test
    public void parseMethods(){
        //1. Get the Class object first
        Class c = Demo.class;
        
        //2. Parse the annotation MyTest4 annotation on the test1 method in the Demo class
        Method m = c.getDeclaredMethod("test1");
        if(m.isAnnotationPresent(MyTest4.class)){
            //Get the MyTest4 annotation on the method
            MyTest4 myTest4 = (MyTest4)m.getDeclaredAnnotation(MyTest4.class);
            //Get the attribute value of MyTests4 annotation
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(myTest4.bbb());
        }
    }
}

The picture above shows the annotations on the obtained classes and methods.

3.4 Application scenarios of annotations-simulating Junit to write a test framework

That is to say, annotated methods or classes will be treated specially.

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 so.
            if(m.isAnnotationPresent(MyTest.class)){
            m.invoke(a);
        }
        }
    }
}

4. Dynamic proxy

Key points: interface (the method of declaring a proxy is equivalent to abstracting what is to be done, and can be used as parameter 2 to indicate what the proxy looks like when the proxy generates a proxy object), tool classes generate dynamic proxy objects, and the Proxy class newInstamce generates a proxy object and overrides the invoke method to implement the callback function

ProxyUtil tool class, generates proxy objects for BigStar objects

public class ProxyUtil {
    public static Star createProxy(BigStar bigStar){
       /* newProxyInstance(ClassLoader loader,
                Class<?>[] interfaces,
                InvocationHandler h)
                Parameter 1: used to specify a class loader
                Parameter 2: Specify what the generated agent looks like, that is, what methods are there?
                Parameter 3: Used to specify what the generated proxy object is to do.
                */
        // Star starProxy = ProxyUtil.createProxy(s);
        // starProxy.sing("Good Day") starProxy.dance()
        Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override // callback method
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //What the proxy object has to do will be written here.
                        if(method.getName().equals("sing")){
                            System.out.println("Prepare the microphone, charge 200,000");
                        }else if(method.getName().equals("dance")){
                            System.out.println("Prepare the venue, collect 10 million");
                        }
                        return method.invoke(bigStar, args);
                    }
                });
        return starProxy;
    }
}

new proxyInstance(loader,class [] interfaces,invocationhalder):

  • 1. Define the class loader
  • 2. Define the methods that should be included in the proxy object, which can have multiple interfaces
  • 3. This is to define what the proxy object should do

public class ProxyUtil {
    public static UserService createProxy(UserService userService){
        UserService userServiceProxy
            = (UserService) Proxy.newProxyInstance(
            ProxyUtil.class.getClassLoader(),
            new Class[]{UserService.class},
            new InvocationHandler() {
                                                                            @Override
            public Object invoke( Object proxy,
                              Method method,
                                  Object[] args) throws Throwable { if(
                    method.getName().equals("login") || method.getName().equals("deleteUsers")||
                    method.getName().equals("selectUsers")){
                    //Record the millisecond value before the method is run
                    long startTime = System.currentTimeMillis();
                    //execution method
                    Object rs = method.invoke(userService, args);
                    //Record the millisecond value after executing the method
                    long endTime = System.currentTimeMillis();

                    System.out.println(method.getName() + "Method execution time:" + (endTime - startTime)/ 1000.0 + "s");
                    return rs;
               }else {
                    Object rs = method.invoke(userService, args);
                    return rs; }
           } });
        //return proxy object
        return userServiceProxy;
    }
}

The bottom layer of AOP is also a dynamic proxy implementation. What is used elsewhere is the delayed loading in mybatis, which uses CGLIB dynamic proxy to implement the search and save operation when this function is needed.