Millet two sides • send sub questions • talk about your understanding of reflection

Posted by ou812 on Wed, 23 Feb 2022 07:42:31 +0100

Because I'm going to start the autumn recruitment next year, I'm going to write an article guided by the interview recently Considering that most of the interview questions provided on the Internet are just a few lines, that is, the recitation version, commonly known as eight part essay, I think this can not meet the demands of most students, but I don't know why What I hope is to take the interview questions as the guide and establish a complete knowledge system to make the eight part essay valuable, rather than one hammer in the East and one stick in the West. Therefore, the follow-up preparation is guided by the face Sutra posted on Niuke, and provide a recitation version + detailed version for each interview question. Students who have met can read the recitation version directly, and students who do not know much can read it together with the detailed version All interview questions have been sorted according to the order of knowledge system, and I will add some supplementary questions to improve them

Java reflection mechanism is really a huge obstacle for Xiaobai. For other things, it's nothing more than more content and more reading. Reflection really means that no matter how many times you read it, you still don't understand it. Moreover, all major textbooks in the school should not reflect this chapter, and some of them pass by. To tell you the truth, before this article, I didn't fully understand reflection. After all, I can't use it in ordinary development. However, after reading this article, I believe you have no doubt about reflection.

🔈 The recitation version of this topic is pasted at the end of the article

The thinking map of the full text is as follows:

1. Throw a brick to attract jade: why use reflection

As we said earlier, the use of interfaces improves the maintainability and scalability of the code and reduces the coupling degree of the code. Let's take an example:

First, we have an interface X and its method test, and two corresponding implementation classes A and B:

public class Test {
    
    interface X {
     public void test();
 }

    class A implements X{
        @Override
        public void test() {
             System.out.println("I am A");
        }
    }

    class B implements X{
        @Override
        public void test() {
            System.out.println("I am B");
    }
}

In general, we need to directly create a new implementation class. See the following code:

public class Test {    

    ......

 public static void main(String[] args) {
        X a = create1("A");
        a.test();
        X b = create1("B");
        b.test();
    }

    public static X create1(String name){
        if (name.equals("A")) {
            return new A();
        } else if(name.equals("B")){
            return new B();
        }
        return null;
    }

}

According to the above method, if hundreds of different X implementation classes need to be created, don't we need to write thousands of if statements to return different X objects?

Let's see how the reflection mechanism works:

public class Test {

    public static void main(String[] args) {
  X a = create2("A");
        a.test();
        X b = create2("B");
        b.testReflect();
    }
    
 // Use reflection mechanism
    public static X create2(String name){
        Class<?> class = Class.forName(name);
        X x = (X) class.newInstance();
        return x;
    }
}

Pass in the package name and class name to the create2() method, dynamically load the specified class through the reflection mechanism, and then instantiate the object.

After reading the above example, I believe you have a certain understanding of reflection. Reflection has the following four functions:

  • The class of any object is known at runtime (dynamic compilation).
  • Construct the object of any class at run time.
  • Know the member variables and methods of any class at run time.
  • Call the methods and properties of any object at run time.

The above function of dynamically obtaining information and dynamically calling objects is called the reflection mechanism of Java language.

2. Understand Class

To understand reflection, we must first understand Class, because Class is the basis of reflection implementation.

During the program running, the JVM always maintains a type ID called runtime for all objects. This information tracks the complete structure information of the class to which each object belongs, including package name, class name, implemented interface, owned methods and fields. You can access this information through a special Java class, which is the class class. We can understand a class class as a class type. A class object is called a class type object. A class object corresponds to one loaded into the JVM Class file.

In general, there must be classes before objects. Taking the following code as an example, the normal loading process of a class is as follows:

import java.util.Date; // Prior class

public class Test {
    public static void main(String[] args) {
        Date date = new Date(); // Object after
        System.out.println(date);
    }
}

First, the JVM will compile your code into a The Class bytecode file is then loaded into the memory of the JVM by the Class Loader. At the same time, a Class object of Date Class will be created and stored in the heap (note that this is not a new object, but a Class type object). Before creating a Date object, the JVM will first check whether its Class is loaded and find the Class object corresponding to the Class. If it is loaded, allocate memory for it, and then initialize new Date().

It should be noted that each Class has only one Class object, that is, if we have the second new Date() statement, the JVM will not generate another Class object of Date because one already exists. This also enables us to use the = = operator to compare two Class objects:

System.out.println(date.getClass() == Date.getClass()); // true

OK, then after loading a Class, a Class object will be generated in the method area of the heap memory, which contains the complete Class structure information. We can see the Class structure through this Class object, just like a mirror. So we call it reflection.

Say it in more detail and explain it again. As mentioned above, under normal circumstances, there must be classes before objects. We call this normal situation "positive". Then the "anti" in reflection can be understood as finding the class to which the object belongs (the source of the object) according to the object

Date date = new Date();
System.out.println(date.getClass()); // "class java.util.Date"

Through reflection, that is, after calling the getClass() method, we get the Class object corresponding to the Date Class, see the structure of the Date Class, and output the full name of the Class to which the Date object belongs, that is, we find the source of the object. Of course, there is more than one way to get Class objects.

3. Four ways to obtain Class objects

From the source code of Class, we can see that its constructor is private, that is, only the JVM can create Class objects. We can't directly new a Class object like an ordinary Class.

We can only get a Class object from an existing Class. Java provides four ways:

The first one: you can use it when you know the specific class:

Class alunbarClass = TargetObject.class;

However, we generally do not know the specific Class. We basically get the Class object by traversing the Class under the package. The Class object obtained in this way will not be initialized.

The second: through class Forname() pass in the full class name to get:

Class alunbarClass1 = Class.forName("com.xxx.TargetObject");

The actual call inside this method is forName0:

The second boolean parameter indicates whether the class needs to be initialized. By default, it needs to be initialized. Once initialized, the static block code execution of the target object will be triggered, and the static parameter will be initialized again.

The third method: through the object instance instance Getclass() get:

Date date = new Date();
Class alunbarClass2 = date.getClass(); // Gets the Class object of the object instance

The fourth type: through the class loader xxclassloader Loadclass() pass in class path to get

class clazz = ClassLoader.LoadClass("com.xxx.TargetObject");

The Class object obtained through the Class loader will not be initialized, which means that static blocks and static objects will not be executed without some steps including initialization. Here's a comparison with forName.

4. Construct an instance of a class through reflection

We introduced the method of obtaining Class objects above. After successful acquisition, we need to construct an instance of the corresponding Class. Here are three methods. The first one is the most common, and the last one can be understood by everyone.

① Use class newInstance

for instance:

Date date1 = new Date();
Class alunbarClass2 = date1.getClass();
Date date2 = alunbarClass2.newInstance(); // Create an instance with the same class type as alunbarClass2

Created an instance with the same class type as alunbarClass2.

It should be noted that the newInstance method calls the default constructor (parameterless constructor) to initialize the newly created object. If this class does not have a default constructor, an exception will be thrown.

② Get the construction method first and then call it through reflection

Since not all classes have parameterless constructors or class constructors are private, in this case, if we want to instantiate objects through reflection, class Newinstance cannot be satisfied.

At this point, we can use the Constructor's newInstance method to obtain the Constructor first, and then execute the Constructor.

It is easy to see from the above code that constructor Newinstance can carry parameters, while class Newinstance is parameterless, which is why it can only call parameterless constructors.

Don't confuse the two newinstance methods. If the constructor of the called class is the default constructor, use class Newinstance () is a good choice. One sentence of code is OK; If you need to call the class's parameterized constructor, private constructor, etc., you need to use the constructor newInstance()

Constructor.newInstance is the method that executes the constructor. Let's take a look at the channels through which the constructor can be obtained. The following methods are easy to remember and understand. The return value is received through the Cnostructor type.

Batch get constructor:

1) Get all "public" constructors

public Constructor[] getConstructors() { }

2) Get all construction methods (including private, protected, default and public)

public Constructor[] getDeclaredConstructors() { }

Single get constructor:

1) Gets a "public" constructor of the specified parameter type

public Constructor getConstructor(Class... parameterTypes) { }

2) Gets a constructor of a specified parameter type, which can be private, protected, default, or public

public Constructor getDeclaredConstructor(Class... parameterTypes) { }

for instance:

package fanshe;

public class Student {
 //(default construction method)
 Student(String str){
  System.out.println("(default)Construction method of s = " + str);
 }
 // Nonparametric construction method
 public Student(){
  System.out.println("The public and parameterless construction method is called to execute...");
 }
 // Construction method with one parameter
 public Student(char name){
  System.out.println("full name:" + name);
 }
 // Construction method with multiple parameters
 public Student(String name ,int age){
  System.out.println("full name:"+name+"Age:"+ age);//There is a problem with the implementation efficiency of this, which will be solved later.
 }
 // Protected construction method
 protected Student(boolean n){
  System.out.println("Protected construction method n = " + n);
 }
 // Private construction method
 private Student(int age){
  System.out.println("Private construction method age:"+ age);
 }
}

----------------------------------
    
public class Constructors {
 public static void main(String[] args) throws Exception {
  // Load Class object
  Class clazz = Class.forName("fanshe.Student");
        
  // Get all public constructor methods
  Constructor[] conArray = clazz.getConstructors();
  for(Constructor c : conArray){
   System.out.println(c);
  }
        
  // Get all construction methods (including: private, protected, default, public)
  conArray = clazz.getDeclaredConstructors();
  for(Constructor c : conArray){
   System.out.println(c);
  }
        
  // Get public and parameterless construction methods
        // Because it is a parameterless construction method, the type is null and can be written without writing: what is needed here is the type of a parameter. Remember that it is the type
  // The class object that describes the parameterless constructor is returned.
  Constructor con = clazz.getConstructor(null);
  Object obj = con.newInstance(); // Call construction method
  
  // Get private constructor
  con = clazz.getDeclaredConstructor(int.class);
  System.out.println(con);
  con.setAccessible(true); // In order to call the private method / domain, we need to cancel the security check
  obj = con.newInstance(12); // Call construction method
 }
}

③ Using the open source library Objenesis

Objenesis is an open source library. Like the second method above, you can call any constructor, but the encapsulation is relatively simple:

public class Test {
    // No parameterless constructor exists
    private int i;
    public Test(int i){
        this.i = i;
    }
    public void show(){
        System.out.println("test..." + i);
    }
}

------------------------
    
public static void main(String[] args) {
        Objenesis objenesis = new ObjenesisStd(true);
        Test test = objenesis.newInstance(Test.class);
        test.show();
    }

It is very simple to use. Objenesis is implemented by the subclass objenesisobjenesistd. The detailed source code will not be studied here. Just understand it.

5. Get member variables through reflection and use

Similar to obtaining constructors, obtaining member variables is also divided into batch obtaining and single obtaining. The return value is received through the Field type.

Batch acquisition:

1) Get all public fields

public Field[] getFields() { }

2) Get all fields (including private, protected and default)

public Field[] getDeclaredFields() { }

Single acquisition:

1) Gets a public field with the specified name

public Field getField(String name) { }

2) Gets a field with a specified name, which can be private, protected and default

public Field getDeclaredField(String name) { }

After getting the member variables, how to modify their values?

The set method contains two parameters:

  • obj: which object needs to modify this member variable
  • Value: to which value

for instance:

package fanshe.field;

public class Student {
 public Student(){
        
 }
 
 public String name;
 protected int age;
 char sex;
 private String phoneNum;
 
 @Override
 public String toString() {
  return "Student [name=" + name + ", age=" + age + ", sex=" + sex
    + ", phoneNum=" + phoneNum + "]";
 }
}

----------------------------------
    
public class Fields {
    public static void main(String[] args) throws Exception {
        // Get Class object
        Class stuClass = Class.forName("fanshe.field.Student");
        // Get public parameterless constructor
        Constructor con = stuClass.getConstructor();
  
  // Get private constructor
  con = clazz.getDeclaredConstructor(int.class);
  System.out.println(con);
  con.setAccessible(true); // In order to call the private method / domain, we need to cancel the security check
  obj = con.newInstance(12); // Call construction method
        
        // Get all public fields
        Field[] fieldArray = stuClass.getFields();
        for(Field f : fieldArray){
            System.out.println(f);
        }

         // Get all fields (including private, protected and default)
        fieldArray = stuClass.getDeclaredFields();
        for(Field f : fieldArray){
            System.out.println(f);
        }

        // Gets the public field of the specified name
        Field f = stuClass.getField("name");
        Object obj = con.newInstance(); // Call the constructor to create an instance of this class
        f.set(obj, "Lau Andy"); // Assign a value to the name attribute in the Student object


        // Get private field
        f = stuClass.getDeclaredField("phoneNum");
        f.setAccessible(true); // Violent reflex, lifting private restrictions
        f.set(obj, "18888889999"); // Assign a value to the phoneNum property in the Student object
    }
}

6. Get member methods through reflection and call

Similarly, the Method of obtaining members can also be divided into batch acquisition and single acquisition. The return value is received through the Method type.

Batch acquisition:

1) Get all "public methods" (including the methods of the parent class and, of course, the Object class)

public Method[] getMethods() { }

2) Get all member methods, including private (excluding inherited)

public Method[] getDeclaredMethods() { }

Single acquisition:

Get a member method with the specified method name and parameter type:

public Method getMethod(String name, Class<?>... parameterTypes)

How do I call methods after I get them?

The invoke method contains two parameters:

  • obj: which object will call this method
  • args: the argument passed when the method is called

for instance:

package fanshe.method;
 
public class Student {
 public void show1(String s){
  System.out.println("Called: public, String Parametric show1(): s = " + s);
 }
 protected void show2(){
  System.out.println("Called: protected, parameterless show2()");
 }
 void show3(){
  System.out.println("Called: default, parameterless show3()");
 }
 private String show4(int age){
  System.out.println("Called, private, and has a return value, int Parametric show4(): age = " + age);
  return "abcd";
 }
}

-------------------------------------------
public class MethodClass {
 public static void main(String[] args) throws Exception {
  // Get Class object
  Class stuClass = Class.forName("fanshe.method.Student");
        // Get public parameterless constructor
        Constructor con = stuClass.getConstructor();
        
  // Get all public methods
  stuClass.getMethods();
  Method[] methodArray = stuClass.getMethods();
  for(Method m : methodArray){
   System.out.println(m);
  }
        
  // Get all methods, including private ones
  methodArray = stuClass.getDeclaredMethods();
  for(Method m : methodArray){
   System.out.println(m);
  }
        
  // Get public show1() method
  Method m = stuClass.getMethod("show1", String.class);
  System.out.println(m);
  Object obj = con.newInstance(); // Call the constructor to instantiate a Student object
  m.invoke(obj, "veal");
  
  // Get private show4() method
  m = stuClass.getDeclaredMethod("show4", int.class);
  m.setAccessible(true); // Lifting private restrictions
  Object result = m.invoke(obj, 20);
  System.out.println("Return value:" + result);
 }
}

7. Advantages and disadvantages of reflection mechanism

Advantages: it is flexible and can dynamically obtain instances of classes at run time.

Disadvantages:

1) Performance bottleneck: reflection is equivalent to a series of interpretation operations to inform the JVM of what to do. The performance is much slower than that of direct Java code.

2) Security issue: reflection mechanism destroys encapsulation, because private methods and fields of a class can be obtained and called through reflection.

8. Classic application scenarios of reflection

Reflection is not directly used in our actual programming, but in fact, many designs are related to the reflection mechanism, such as:

  • Dynamic agent mechanism
  • Connect to the database using JDBC
  • Spring / Hibernate Framework (in fact, it is related to reflection mechanism because dynamic proxy is used)

Why dynamic proxy uses reflection mechanism will be explained in detail in the next article.

JDBC connection database

In the operation of JDBC, if you want to connect to the database, you must follow the following steps:

  • Pass class Forname() loads the driver for the database (loaded by reflection)
  • Connect to the database through the DriverManager class. The parameters include the connection address, user name and password of the database
  • Receive Connection through Connection interface
  • Close connection
public static void main(String[] args) throws Exception {  
        Connection con = null; // Database connection object  
        // 1. Load the driver through reflection
        Class.forName("com.mysql.jdbc.Driver"); 
        // 2. Connect to the database  
        con = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/test","root","root"); 
        // 3. Close the database connection
        con.close(); 
}

Spring framework

Reflection mechanism is the soul of Java framework design. The interior of the framework has been encapsulated, and we basically don't need to write it ourselves. In addition to Hibernate, Spring also uses many reflection mechanisms. The most typical is that Spring loads beans (creates objects) through xml configuration files, that is, Spring's IoC. The process is as follows:

  • Load the configuration file and get the Spring container
  • Use the reflection mechanism to obtain the Class instance of a Class according to the incoming string
// Get the IoC container of Spring and get the object according to the id
public static void main(String[] args) {
    // 1. Use the ApplicationContext interface to load the configuration file and get the spring container
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    // 2. Use the reflection mechanism to obtain the Class instance of a Class according to this string
    IAccountService aService = (IAccountService) ac.getBean("accountServiceImpl");
    System.out.println(aService);
}

In addition, because Spring AOP uses dynamic proxy, it also uses reflection mechanism, which I will explain in detail in the Spring series.

Finally, put the recitation version of this question:

🥸 Interviewer: talk about your understanding of reflection (when you encounter this seemingly open-ended problem, of course, the more comprehensive it is, the better. Function + principle + advantages and disadvantages + applicable scenarios) 😎 Veal: first, the role of reflection. Why use reflection? Reflection has the following four functions:

  • The class of any object is known at runtime (dynamic compilation).
  • Construct the object of any class at run time.
  • Know the member variables and methods of any class at run time.
  • Call the methods and properties of any object at run time.

This function of dynamically obtaining information and dynamically calling object methods is called the reflection mechanism of Java language. Then the specific principle of reflection: Under normal circumstances, there must be a class first and then a new object, right? The normal loading process of a class is as follows: First, the JVM will compile our code into a The Class bytecode file is then loaded into the memory of the JVM by the ClassLoader. At the same time, the Class object of this Class will be created and stored in the heap (note that this is not the new object, but the type object of the Class). Before creating this Class object, the JVM will first check whether its Class is loaded and find the Class object corresponding to the Class. If it is loaded, allocate memory for it, and then initialize new. OK, then after loading a Class, a Class object is generated in the method area of the heap memory and contains the complete structure information of the Class. We can see the structure of the Class through this Class object, just like a mirror. So we call it reflection. In more detail, in general, there must be classes before objects. We call this general situation "positive". Then the "anti" in reflection can be understood as finding the class to which the object belongs (the source of the object) according to the object Through reflection, that is, after calling the getClass() method, we get the Class object corresponding to this Class, see the structure of this Class, and output the full name of the Class to which the Class object belongs, that is, we find the source of the object. Of course, in addition to calling getClass(), there are three other methods to obtain Class objects, balabala The advantage of reflection is that it is flexible and can dynamically obtain instances of classes at run time. However, reflection also has obvious disadvantages: 1) Performance bottleneck: reflection is equivalent to a series of interpretation operations to inform the JVM of what to do. The performance is much slower than that of direct Java code. 2) Security issue: reflection mechanism destroys encapsulation, because private methods and fields of a class can be obtained and called through reflection. Reflection is not directly used in our actual programming, but in fact, many designs are related to the reflection mechanism, such as:

  • Dynamic agent mechanism
  • Connect to the database using JDBC
  • Spring / Hibernate Framework (in fact, because dynamic proxy is used, it is related to reflection mechanism, which can be extended as appropriate) After this, the interviewer is likely to continue to expand the dynamic agent or JVM according to what you say I'll leave first

Running water doesn't compete first. It's talking. I'm veal. See you in the next article 👋