Author: DeppWang, original address

In building the Wheel: Implementing a Simple Spring IoC Container, we discussed Spring’s use of reflection when creating Bean instances and dependency injection. In this article, we’ll take a look at reflection in Spring and how it works.

Reflection in Spring

1.1. Reflection when creating Bean instances

// Get the class object according to the class path through the class loaderClass<? > clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
// Generate Bean instances from class objects
return clz.newInstance();
Copy the code

Reflection is reflected in clz.newinstance (); , the core code can be divided into two parts:

Get all Constructor information for the current class PetStoreService using reflection (Constructor object)

// java.lang.Class.java
// Call native methods with publicOnly set to false
res = getDeclaredConstructors0(publicOnly);
// Native method, which gets Constructor information from the CLASS file in the JVM and converts it into a Constructor object
private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
Copy the code

2. Use reflection to generate instances from the default constructor

// sun.reflect.NativeConstructorAccessorImpl.java
// Call the native method. Var1 represents the constructor argument, which is null
return newInstance0(this.c, var1);
// the native method, which actually generates the instance, executes the class file constructor 
      
private static native Object newInstance0(Constructor
        var0, Object[] var1);
Copy the code

Constructor dependency injection reflection

// Get all the Constructor information for the current class by reflection (Constructor object)Constructor<? >[] candidates = beanClass.getDeclaredConstructors();// Sets the constructor parameter instance
Object[] argsToUse = new Object[parameterTypes.length];
argsToUse[i] = getBean(beanNames.get(i));
// Implement the instantiation Bean using a Constructor object with parameters. Use reflection as above (newInstance0), but with more parameters
return constructorToUse.newInstance(argsToUse);
Copy the code

1.3 reflection of setter() methods for dependency injection

// Get all the Method information of the current class by reflection (Method object)
Method[] methods = bean.getClass().getDeclaredMethods();
// Get the method parameter instance
Object propertyBean = getBean(propertyName);
// Call setter() methods via reflection execution. Invoke: Invoke a method with the propertyBean as an argument to the method
method.invoke(bean, propertyBean);
Copy the code

bean.getClass().getDeclaredMethods(); Core code in:

// java.lang.Class.java
// Call native methods, publicOnly is false
getDeclaredMethods0(publicOnly);
// Native Method, which gets Method information from the class file in the JVM and converts it to Method
private native Method[]      getDeclaredMethods0(boolean publicOnly);
Copy the code

method.invoke(bean, propertyBean); Core code in:

// sun.reflect.NativeMethodAccessorImpl.java
// Call native methods var1: bean, var2: propertyBean
return invoke0(this.method, var1, var2);
// native method to run bytecode instructions in class files
private static native Object invoke0(Method var0, Object var1, Object[] var2);
Copy the code

1.4. @AutoWired reflection during dependency injection

// Get all the fields of the current class by reflection (Field object)
Field[] fields = bean.getClass().getDeclaredFields();
// Determine if the field has an @autowired annotation
Annotation ann = field.getAnnotation(Autowired.class);
// Set fields to be concatenable, which is equivalent to changing non-public (private, default, protect) to public
field.setAccessible(true);
// Set the value of the field by reflection
field.set(bean, getBean(field.getName()));
Copy the code

bean.getClass().getDeclaredFields(); Core code in:

// java.lang.Class.java
// Call native methods with publicOnly set to false
getDeclaredFields0(publicOnly);
// Native method, get the Field information of class file from JVM, and convert it to Field
private native Field[]       getDeclaredFields0(boolean publicOnly);
Copy the code

field.set(bean, getBean(field.getName())); Core code in:

// sun.reflect.UnsafeObjectFieldAccessorImpl.java
// Call the native method and set the field value at the offset fieldOffset of the target object var1 to var2. Var1 is the bean and var2 is the parameter instance
unsafe.putObject(var1, this.fieldOffset, var2);

// sun.misc.Unsafe.java
// Native method, directly modify the heap object field data
public native void putObject(Object var1, long var2, Object var4);
Copy the code

Class files and class objects

Class files are compiled from Java files that contain tables of fields, tables of methods,

methods (constructors), and so on.

When the class loader loads the class file into the virtual machine metadata area (method area, JDK1.7), the virtual machine creates a corresponding class object (class instance). The class file is converted from a static structure stored on disk to a runtime structure stored in memory.

We can think of a class (a class file) as having one class object, shared by all objects of the current class. Class objects serve as the entry point to the class files stored in the JVM.

package java.lang;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

public final class Class<T> {
    private native Field[]       getDeclaredFields0(boolean publicOnly);
    private native Method[]      getDeclaredMethods0(boolean publicOnly);
    private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);

    // ReflectionData Cache reflection object
    private static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatileConstructor<T>[] publicConstructors; . }}Copy the code

2.1. Method of obtaining class objects

// 1
Class cls = object.getClass();
// Object.java
public final nativeClass<? > getClass();// use the class loader
Class cls = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");

// 3, through the Class Class, and essentially through the Class loader
Class cls = Class.forName("org.deppwang.litespring.v1.service.PetStoreService");
// Class.java
private static nativeClass<? > forName0(String name,booleaninitialize, ClassLoader loader, Class<? > caller)Copy the code

Three, reflection method

The following are common reflection methods.

3.1 Feild related

Field[] fields = cls.getFields(); // Get all public fields (including parent classes)
Field[] fields = cls.getDeclaredFields(); // Get all fields of the current class (excluding the parent class), both public and non-public
Field field = cls.getDeclaredField("fieldName"); // Specify a Field for the current class
field.set(Object, Object); // Set (modify) the field value
field.get(Object); // Get the field value
Copy the code

Field. Get (Object)

// Call native method to get the corresponding value of the field
return unsafe.getObject(var1, this.fieldOffset);

// Native method to get objects from the heap at the specified location
public native Object getObject(Object var1, long var2);
Copy the code

3.2. Method correlation

Method[] methods = cls.getMethods(); // Get all public methods (including parent classes)
Method[] methods = cls.getDeclaredMethods(); // Get all methods of the current class (excluding the parent class), both public and non-public
method.invoke(Object instance, Object... parameters); // Run the method
Copy the code

Run method usage scenarios: either modify the object’s data, such as void setter(); Or get the return result of the execution method.

String result = method.invoke().toString();
Copy the code

(3) Constructor

Constructor<? >[] constructors = cls.getConstructors();// Get all public Constructor (including parent class)Constructor<? >[] constructors = cls.getDeclaredConstructors();Get all the Constructor (excluding the parent) of the current class, both public and non-public
constructor.newInstance(Object... parameters); // Run the constructor
Copy the code

When no constructor is explicitly written, the Java compiler builds a default constructor

for the class.

4. Native methods

Java 1.1 added the Java Native Interface (JNI), which is a very inclusive programming Interface that allows us to call Native methods from Java applications, Native methods are written in other languages (C, C++, assembly language, etc.). Native methods are used to implement functionality that Java cannot handle.

4.1. Simple examples

A simple example of using the Java Native Interface (JNI) in Java.

  • Environment: JDK8, macOS 10.15
// Main.java
public class Main {
    public native int intMethod(int i);
    static {
        // Load libmain.dylib at startup
        System.loadLibrary("Main");
    }
    public static void main(String[] args) {
        System.out.println(new Main().intMethod(2)); }}Copy the code
// Main.c:
// introduce main.h
#include "Main.h"

// inherit Java_Main_intMethod from main. h
JNIEXPORT jint JNICALL Java_Main_intMethod( JNIEnv *env, jobject obj, jint i)
{
    return i * i;
}
Copy the code

Compile and run:

// Generate both main. class and main.h
javac Main.java -h .
// Generate libmain.dylib from main.c
gcc -dynamiclib -O3 \
    -I/usr/include \
    -I$JAVA_HOME/include \
    -I$JAVA_HOME/include/darwin \
    Main.c -o libMain.dylib
// Specify the path of the library as the current path
java -cp . -Djava.library.path=$(pwd) Main
Copy the code

Output:

4
Copy the code
/* main.h.h as a header file */
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Main */

#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" {
#endif
/* * Class: Main * Method: intMethod * Signature: (I)I */
JNIEXPORT jint JNICALL Java_Main_intMethod
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif
Copy the code
javac Main.java -h .
// Can be split into two commands
javac Main.java
javah -jni Main
Copy the code

Principle of 4.2,

Dylib is loaded into the VM when main. class is run. The JVM calls the Java_Main_intMethod of libmain. dylib, passing in the argument, and libmain. dylib is run directly by the system.

  • *env is used to convert Java type data to and from native (in this case C) type data
  • Jint is also a Java data type, and Java primitive data types can be mapped (used) without being converted by *env
/*C code*/
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    /*Get the native string from javaString*/
    const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);

    /*Do something with the nativeString*/

    /*DON'T FORGET THIS LINE!!! * /
    (*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
Copy the code

4.3, reference

  • A Simple Java Native Interface (JNI) example in Java and Scala
  • Java native keyword example
  • Java Native Interface: JNI Example
  • Java Programming Ideas – AppendixAThe use ofJAVAcode
  • Java native Interface – WiKi

Five, the summary

Reflection reflection, where does reflection literally mean?

It can be understood that the reflection object is obtained through the native method, and the reflection object is manipulated and reflected to the original object just like a mirror.

We find that the relationship between reflection and native methods is:

  • Get field, method, constructor object, native() method implementation
  • Obtain field value, set and modify field value, native() method implementation
  • Run method, native() method implementation
  • Run constructor, native() method implementation

We can conclude that reflection is implemented by native methods.

If we say that a function is achieved by reflection, we can also say that

  • By reflection method
  • Implemented through the reflection API
  • Through the native method

Reflection is the ability to obtain class file information, run class file bytecode instructions, and manipulate object data in an unconventional way.

In summary: Reflection is the ability of the runtime to retrieve and modify object data.

About runtime: Java is a static language, compiled first, run later. Code is not executed at compile time; it is executed at run time.

6. Read more

  • JAVA Reflection Principle
  • Liao Xuefeng Java tutorial – Reflection
  • Reflection in Java
  • oracle reflect docs
  • What is reflection and why is it useful?
  • Understanding sun.misc.Unsafe
  • Guide to sun.misc.Unsafe