“This is the 23rd day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Reflective overview

The Java reflection mechanism refers to the fact that in the running state of a Java program, all properties and methods of any class can be obtained. For a given object, you can call any of its properties and methods. This method of dynamically retrieving the contents of a class and dynamically calling an object is called reflection. Java’s reflection mechanism allows programmers to obtain information about classes in a more diverse and flexible way when they are unknown to them. Calling corresponding methods in classes is a mechanism for Java to increase its flexibility and dynamics. Reflection can dynamically compile and create objects, greatly inspired the flexibility of programming language, strengthen the characteristics of polymorphism, further enhance the abstract ability of object-oriented programming, in a lot of frames are used in large numbers, so it can be said that the soul of the framework is: reflection technology.

These are all very official explanations, and an overview of the power of reflection technology, so let’s take a closer look at the use of reflection.

Class loading process

When a program wants to use a class, if the class has not been loaded into memory, the system will load, connect, initialize the class to achieve three steps.

  • Load: Reading a class file into memory and creating a class object for it. The system creates a class object for any class that is used
  • Connect: Connect is divided into three steps (validate, prepare, parse)

Validation: verifies that the class has the correct internal structure and coordinates with other classes. Preparation: Allocates memory for static members of the class and sets default initialization values. Resolution: replaces symbolic references in the binary data of the class with direct references

  • Initialization: Initialization assigns correct values to all static variables. Note the difference between the initialization operation, which allocates memory for static members and sets default initialization values, and the preparation operation, which sets correct values. For example: static int num = 1024, a member variable that is set to 0 during preparation and 1024 only during initialization.

Class loader

After we understand the loading process of a class, let’s take a look at who completes the loading operation of a class: the class loader. The class loader is responsible for loading. Class files into memory and generating corresponding class objects for them. The class loader consists of the following three loaders:

  1. Bootstrap ClassLoader: the root ClassLoader, also known as the Bootstrap ClassLoader, is responsible for loading Java core classes, such as the System and String classes
  2. Extension ClassLoader: The Extension ClassLoader is responsible for loading jar packages in the Extension directory of the JRE
  3. System ClassLoader: The System ClassLoader is responsible for loading the class files from Java commands and the JAR packages and classpath specified by the classpath variable when the Java VIRTUAL machine starts

Get the Class object

Now that we have the theory in hand, we can start by looking at how to get a Class object for a Class (there are three ways). Start by creating a base class for testing:

package com.wwj.reflect;

public class Programmer {
    private String name;
    public int age;
    private String address;

    public Programmer(a) {}private Programmer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Programmer(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName(a) {
        return name;
    }

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

    public int getAge(a) {
        return age;
    }

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

    public String getAddress(a) {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void test(a) {
        System.out.println("Test -- method with no parameter and no return value");
    }

    public void test2(String str) {
        System.out.println("Test2 -- Method with no return value");
    }

    public String test3(String str, int num) {
        System.out.println("Test3 -- Parameter return method");
        return str + "--" + num;
    }

    private void test4(a) {
        System.out.println("Test4 -- Private method");
    }
    
	@Override
    public String toString(a) {
        return "Programmer{" +
                "name='" + name + '\' ' +
                ", age=" + age +
                ", address='" + address + '\' ' +
                '} '; }}Copy the code

So the first way to get a Class Object is through the Object getClass() method:

Programmer programmer = new Programmer();
Class pClass = programmer.getClass();
Copy the code

The second way is through the static class attribute:

Class pClass = Programmer.class;
Copy the code

The third way is through the static method forName() in the Class:

Class pClass = Class.forName("com.wwj.reflect.Programmer");
Copy the code

Get constructor

Once we have the Class object, we can use it to get the members of the Class and use it. Let’s first look at how to get the constructor of the Class.

	public static void main(String[] args) throws ClassNotFoundException {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor[] constructors = pClass.getConstructors();
        for(Constructor constructor : constructors) { System.out.println(constructor); }}Copy the code

Running results:

public com.wwj.reflect.Programmer(java.lang.String,int,java.lang.String)
public com.wwj.reflect.Programmer()
Copy the code

The console only prints two constructors, but obviously there are three constructors in the Programmer class whose private constructors are not available. So getConstructors() in the Class can only get public constructors. To get all constructors, use getDeclaredConstructors() :

	public static void main(String[] args) throws ClassNotFoundException {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor[] constructors = pClass.getDeclaredConstructors();
        for(Constructor constructor : constructors) { System.out.println(constructor); }}Copy the code

Running results:

public com.wwj.reflect.Programmer(java.lang.String,int,java.lang.String)
private com.wwj.reflect.Programmer(java.lang.String,int)
public com.wwj.reflect.Programmer()
Copy the code

Usually, we don’t need this many constructors, often we only need one constructor.

1. Get the no-parameter constructor

Let’s start with how to get the no-argument constructor of a class.

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        System.out.println(object);
    }
Copy the code

We can get a single constructor using the Class’s getConstructor() method. Passing no arguments means getting a constructor with no arguments. We then create a Programmer object by calling the newInstance() method from the returned constructor object. So the result should be information for the Programmer class.

Programmer{name='null', age=0, address='null'}
Copy the code
2. Get the parameter constructor

We can get a constructor with arguments using the same method as getConstructor(), but we pass in the argument and return the constructor object for that argument.

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor(String.class,int.class,String.class);
        Object object = constructor.newInstance("Zhang".18."Hangzhou");
        System.out.println(object);
    }
Copy the code

Running results:

Programmer{name=' Programmer ', age=18, address=' Programmer '}Copy the code
3. Get the private constructor

As mentioned earlier, the getConstructor() method does not get private constructors, so we can use getDeclaredConstructors() instead, using the same method as getConstructor().

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor declaredConstructor = pClass.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);// Cancel the access check
        Object object = declaredConstructor.newInstance("Bill".20);
        System.out.println(object);
    }
Copy the code

Running results:

{name=' Programmer ', age=20, address='null'}Copy the code

It is important to note that while the getDeclaredConstructor() method can obtain private constructors, the Java language’s access checking mechanism throws an illegal access exception when creating an object, so we need to use the setAccessible() method to cancel the access check. If the parameter is true, the access check is cancelled. Objects can be created normally only after the access check is cancelled.

Get member variables

Let’s look at how to get a Class member variable from a Class object.

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Field[] fields = pClass.getFields();
        for(Field field : fields) { System.out.println(field); }}Copy the code

Running results:

public int com.wwj.reflect.Programmer.age
Copy the code

As with the constructor, the getFields() method does not get private member variables of the class. Instead, we can get them by using getDeclaredFields() :

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Field[] fields = pClass.getDeclaredFields();
        for(Field field : fields) { System.out.println(field); }}Copy the code

Running results:

private java.lang.String com.wwj.reflect.Programmer.name
public int com.wwj.reflect.Programmer.age
private java.lang.String com.wwj.reflect.Programmer.address
Copy the code
1. Get public member variables
	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Field ageField = pClass.getField("age");
        ageField.set(object, 20);
        System.out.println(object);
    }
Copy the code

Running results:

Programmer{name='null', age=20, address='null'}
Copy the code

The Class object’s getField() method allows you to get a member variable with the specified property name, but to assign a value to a property, you first create a Programmer object and then call the set() method of the member variable object, passing in the assigned object and property value. This logic is actually the opposite of normal object creation assignment. Reflection calls methods through member variable objects and passes in class objects and parameter values.

2. Obtain private member variables

Private member variables are obtained in the same way as private constructors, using getDeclaredField() and unchecking access before assignment.

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Field nameField = pClass.getDeclaredField("name");
        nameField.setAccessible(true);// Cancel the access check
        nameField.set(object,"Bill");
        System.out.println(object);
    }
Copy the code

Running results:

Programmer{name= 0, age=0, address='null'}Copy the code

Get member method

Public member methods can be obtained by using getMethods(), and private member methods can be obtained by using getDeclaredMethods(). This will not be repeated, but how to obtain a single member method will be explained next.

1. Get the member method with no parameters and no return value
	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getMethod("test");
        method.invoke(object);
    }
Copy the code

Running results:

Test -- Method with no parameter and no return valueCopy the code

Similarly, the getMethod() method gets a member method with the corresponding parameter name. This method takes two arguments: the first argument is the method name; The second parameter is the method parameter type. There is no need to pass in the second parameter because it is a no-argument method. Once the object of the member method is retrieved, the invoke() method of the same object is called and the object that needs to execute the method is passed in to successfully execute the method.

2. Get the member method with parameters and no return value

Getting a parameterized member method is easy by passing in the parameter type in the getMethod() method:

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getMethod("test2", String.class);
        method.invoke(object, "Fifty");
    }
Copy the code

Running results:

Test2 -- Method with no return valueCopy the code
3. Get member methods with parameters and return values

Getting the member method with the parameter return value is also quite simple, but with one more return value handler:

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getMethod("test3", String.class, int.class);
        Object obj = method.invoke(object, "Daisy".20);
        System.out.println(obj);
    }
Copy the code

Running results:

Test3 -- parameter return method zhao 6 --20Copy the code
4. Get private member methods

GetDeclaredMethod (); getDeclaredMethod(); getDeclaredMethod();

	public static void main(String[] args) throws Exception {
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getDeclaredMethod("test4");
        method.setAccessible(true);
        method.invoke(object);
    }
Copy the code

Running results:

Test4 -- Private methodsCopy the code

Use reflection to ignore generic checking

Now that we’ve covered the basics of reflection, let’s use generics to solve a problem: ignore Java’s generic checking. Let’s look at some code like this:

	public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add(1024);
    }
Copy the code

Since the list collection’s generics are specified as strings, the collection will only store strings, so the compiler will report an error when we put it in 1024. Is there any way to put other types into the collection? There is a way, and that is through reflection. Because the Java generics mechanism only works at compile time, it does not run with generics, a phenomenon called generic erasure. Because of this feature, we can override compile-time generic checking by reflection to store data of other types into a collection of specified types.

	public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        Class listClass = list.getClass();
        Method addMethod = listClass.getMethod("add", Object.class);
        addMethod.invoke(list, 1024);
        System.out.println(list);
    }
Copy the code

Running results:

[hello, world, 1024]
Copy the code

The int is then successfully stored in the collection.

A dynamic proxy

Dynamic proxy is an advanced application of reflection technology. Its purpose is to provide a proxy for other objects to control access to one object. The proxy class is responsible for preprocessing messages for the delegate class, filtering and forwarding messages, and for subsequent processing of messages after they are executed by the delegate class. In Java, the JDK provides the Proxy class and the InvocationHandler interface to generate dynamic Proxy objects. However, it is important to note that the JDK provides proxies only for interfaces. If we need to Proxy ordinary classes, we can use Cglib. Due to the limited space, the dynamic proxy will not be introduced in detail, through a case to let you understand the dynamic proxy. Create a new interface ICat:

interface ICat{
	public void run(a);
}
Copy the code

Create a new class that inherits the interface:

public class Cat implements ICat{

	@Override
	public void run(a){
		System.out.println("Meow, meow! A cat is running."); }}Copy the code

This is a running cat. Usually we use dynamic proxies to enhance the methods of a class. For example, in this class, we can enhance the run() method to also catch mice:

	public static void main(String[] args) throws Exception {
        final ICat cat = new Cat();/ / the original object
        ICat catProxy = (ICat) Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] objs)
                    throws Throwable {
                // Enhance the run method
                if (method.getName().equals("run")) {
                    method.invoke(cat, objs);// Call the method of the original object, preserving the function of the original method
                    // New functionality
                    System.out.println("Catch a mouse.");
                }
                return null; }}); catProxy.run(); }Copy the code

Running results:

Meow, meow! A cat is running to catch a mouseCopy the code

We’ll focus on the invoke() method, which takes three arguments:

  • Proxy: the proxy instance on which the method is invoked
  • Method: The method instance corresponding to the interface method invoked on the proxy instance. The declaration class of the Method object will be the interface in which the Method is declared, and this interface can be a superinterface of the proxy interface from which the proxy class inherits the Method
  • Objs: An array of objects containing parameter values for method calls passed to the proxy instance, or null if interface methods do not use parameters. Parameters of the base type are wrapped in instances of the appropriate base wrapper class

The return value is the return value of the proxy method. Since the run() method does not return a value, it simply returns null. The invoke() method is called and the object and parameter value objs are passed to execute the method. We can then add the functionality below, making the method more feature-rich than the original method.

The last

This article overall is simple, suitable for beginner learners, although simple, but also wrote a very long, from eight until about 11 PM, the purpose is also hope that we can quickly grasp reflex technology, late reflection technology in the framework of the study is of vital importance to understand the reflection for the underlying implementation framework you can know more.

Finally, I wish everyone a happy holiday!