Total solution of java reflection

Posted by dkjohnson on Wed, 09 Feb 2022 07:05:18 +0100

introduction

How many ways to create objects in java?

1. Use the new keyword

2. Use clone method

3. Use deserialization

4. Use reflection

5. Use Unsafe

For a detailed explanation of these methods of creating objects, please see this article Five ways to create objects in java

Next, we mainly introduce the reflection related knowledge in detail

Introduction to reflection

Reflection contains a word "anti", so if you want to explain reflection, you must start with "positive".
Generally, when we use a class, we must know what class it is and what it is used for. So we instantiate this class directly, and then operate with this class object.

Apple apple = new Apple(); //Direct initialization, "orthophoto"
apple.setPrice(4);
Copy code

Initializing class objects like this can be understood as "positive".
Reflection doesn't know what the class object I want to initialize is at the beginning. Naturally, I can't use the new keyword to create the object.
At this time, we use the reflection API provided by JDK to make reflection calls:

Class clz = Class.forName("com.eft.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
Copy code

The execution results of the above two pieces of code are actually exactly the same. But the idea is completely different. The first code has determined the class to run (Apple) before running (compile time), while the second code knows the class to run (com.eft.reflect.Apple) through the string value at run time.

So what is reflection?

Reflection is to know what the class to operate on is at runtime, and you can get the complete construction of the class at runtime and call the corresponding method.

Official definition

Java reflection mechanism is to know all the properties and methods of any class in the running state; For any object, you can call any of its methods and properties; This kind of dynamically obtained information and the function of dynamically calling the method of the object are called the reflection mechanism of Java language.

An important point of reflection mechanism is "runtime", which allows us to load, explore and use completely unknown information during compilation class file. In other words, Java programs can load a program whose name is known only at runtime class file, and then learn its complete structure, and generate its object entity, or set the value of its fields (variables), or call its methods (Methods).

Popular generalization

Reflection is a technique that allows you to get information about objects (such as classes, properties, methods, etc.) by name

core

Java reflection mechanism is a key property of Java language as a "quasi dynamic" language. The core of java reflection mechanism is to allow the internal information of class classes with known names (including its modifiers (such as public, static, etc.), superclass (such as Object), implementation interfaces (such as Serializable), as well as all information of fields and methods) to be obtained through Java Reflection APIs at runtime, and generate such classes dynamically, And call its method or modify its domain (even the domain or method declared as private)

function

  • Judge the class of any object at run time;
  • Construct the object of any class at runtime;
  • Judge the member variables and methods of any class at run time;
  • Call the method of any object at run time;
  • Generate dynamic proxy

Reflection principle

Loading of process classes:

 


The complete process of class loading is as follows:
(1) At compile time, the java compiler compiles well Java files are generated on disk Class file The class file is a binary file that contains the machine code that only the JVM can recognize.
(2) The classloader in the JVM reads the bytecode file, takes out the binary data, loads it into memory and parses it Class file. The class loader will obtain the binary byte stream of the class according to the fully qualified name of the class; Then, the static storage structure represented by the byte stream is transformed into the runtime data structure of the method area; Next, generate a Java. Net file representing this class in memory Lang. class object.
(3) After loading, the JVM starts the connection phase (including verification, preparation and parsing). After this series of operations, the variables of the class will be initialized.

 

To use reflection, you first need to obtain the Class object corresponding to the Class to be operated on. In Java, no matter how many objects of a Class are generated, these objects will correspond to the same Class object. This Class object is generated by the JVM, through which we can know the structure of the whole Class. So, Java Lang. Class can be regarded as the entry point of all reflection API s.
The essence of reflection is to map various components of Java classes into Java objects at runtime.

Simple example

Through the previous introduction - the example of creating an object using reflection, we learned the steps of creating an object using reflection:

Gets the Class object instance of the Class

Class clz = Class.forName("com.eft.reflect.Person");
Copy code

Get the Constructor object according to the Class object instance

Constructor constructor = clz.getConstructor();
Copy code

Use the newInstance method of the Constructor object to get the reflection class object

Object personObj = constructor.newInstance();
Copy code

If you want to call a method, you need to go through the following steps:

  • Gets the Method object of the Method
Method setNameMethod = clz.getMethod("setName", String.class);
Copy code
  • Using invoke method to call method
setNameMethod.invoke(personObj, "Hot and Sour Soup");
Copy code

Test code for calling method through reflection:

Class clz = Person.class;
Method setNameMethod = clz.getMethod("setName", String.class);
// Person person= (Person) clz.getConstructor().newInstance();
Person person= (Person) clz.newInstance();
setNameMethod.invoke(person, "Hot and sour soup 888");//Call setName method and pass in parameters

Method getNameMethod = clz.getMethod("getName", null);
String name= (String) getNameMethod.invoke(person,null);//Call the getName method to get the return value

System.out.println("name: " +name);

Operation results:
name: Hot and sour soup 888
 Copy code

So far, we have been able to master the basic use of reflection. However, if you want to further master reflection, you need to have a deeper understanding of the common API s of reflection

Reflection API details

In JDK, reflection related API s can be divided into the following three aspects:

1, Gets the Class object of the reflection

Each type (e.g. String,Integer,Person...) Will be loaded into the "method area" of the virtual machine memory when used for the first time, including attribute fields, method bytecode and other information defined in the Class. Using Class java in Java Lang. Class to point to a type information. Through this Class object, we can get all the internal information of this Class.

Class has no public constructor. The class object is automatically constructed by the Java virtual machine when loading the class and by calling the defineClass method in the class loader.

There are three main methods to obtain a Class object:

Use class literal constants or TYPE fields

  • Class Class, such as person class
    • Class literal constants can be applied not only to ordinary classes, but also to interfaces, arrays and basic data types
    • This method is not only simpler, but also safer, because it will be checked at compile time and eliminates the call to the forName method, so it is also more efficient. It is recommended to use the form of ". class"
  • Boolean.TYPE, such as integer TYPE
    • TYPE is a standard field of the wrapper TYPE of the basic data TYPE. It is a reference to the Class object of the corresponding basic data TYPE

Equivalent on both sides of the table:

boolean.classBoolean.TYPE
char.classCharacter.TYPE
byte.classByte.TYPE
short.classShort.TYPE
int.classInteger.TYPE
long.classLong.TYPE
float.classFloat.TYPE
double.classDouble.TYPE
void.classVoid.TYPE

This method is the most direct, but it can only obtain the class objects of the classes I know, that is, the objects of the classes used in the project can pass through the class Class method obtains its class object, but one disadvantage of this method is that it cannot obtain its class object for unknown classes or invisible classes.

Object getClass()

For example: person getClass() the ancestor Class Object in Java provides a method getClass() to obtain the Class Object of the current instance. This method is the most used method in development. Similarly, it cannot obtain unknown classes, such as the Class Object of the implementation Class of an interface.

API:

public final native Class<?> getClass();
Copy code

This is a Native Method (a Native Method is an interface for Java calling non java code), and subclass rewriting is not allowed, so theoretically, all instances of types have the same getClass method.

use:

Integer integer = new Integer(12);
Class clz=integer.getClass();
Copy code

Class.forName("class full path")

For example: class forName("com.eft.xx.Person")
This method is most commonly used. You can get the Class object of any Class if the Class exists. Otherwise, ClassNotFoundException will be thrown. In this way, we only need to know the full path (fully qualified name) of the Class to get its Class object (if it exists)

API:

//Since the Class type information of the method area is uniquely determined by the Class loader and the fully qualified name of the Class,
//Therefore, if you want to find such a Class, you must provide Class loader and fully qualified Class name,
//This forName overloaded method allows you to pass in the class loader and the fully qualified name of the class to match the type information of the method area
//Parameter description name:class name, whether to load static block in initialize
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException{
    ..
    }   

// By default, the forName method uses the caller's class loader to load the class The class file is loaded into the jvm
//If the initialize passed in here is true, the static block in the class will be executed
public static Class<?> forName(String className)
    throws ClassNotFoundException {
    return forName0(className, true, ClassLoader.getCallerClassLoader());
}
Copy code

use:

Class clz = Class.forName("com.eft.reflect.Person");
Copy code

2, Determine whether it is an instance of a class

  • Use the instanceof keyword
  • Use isInstance method (Native method) of Class object
public class InstanceofDemo {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        if (arrayList instanceof List) {
            System.out.println("ArrayList is List");
        }
        if (List.class.isInstance(arrayList)) {
            System.out.println("ArrayList is List");
        }
    }
}
//Output:
//ArrayList is List
//ArrayList is List
 Copy code

The classes loaded by different loaders do not belong to the same category (instant package name and class name are the same), and the classes of the created objects are also different, as follows:

ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream("./bean/" + fileName);
                    if (is == null) {
                        return super.loadClass(name);//Returns the parent class loader
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (Exception e) {
                    throw new ClassNotFoundException();
                }
            }
        };

Object obj = null;
Class clz=myLoader.loadClass("eft.reflex.bean.Person");
System.out.println("person Loaded by custom class loader");
obj = clz.newInstance();
System.out.println(obj instanceof Person);


Operation results:
person Loading completed by custom class loader
person The static block of is called
false
 Copy code

The principle should be: the jvm will use the default class loader to load the class into the method area according to the operator on the right of instanceof, and then find the corresponding class in the method area according to the class reference address information in the object header of the left operator object. If the found class is the class just loaded, the result is true, otherwise it is false. For this example, the class pointed to by the obj object has been loaded into the method area before the object is created. During the instanceof operation, the class already existing in the method area is not loaded with the default loader at this time, so the jvm thinks that the class has not been loaded, so the class pointed to by the operator on the right will be loaded at this time, So the result of this example is false. If you change the class name in parentheses to the class name of the test class, the result is similar, but the test class will be loaded before the main method is executed.

3, Get the constructor through reflection and create an instance object

There are two main ways to create Class objects through reflection: through the newInstance() method of Class object and through the newInstance() method of Constructor object.

The first method: through the newInstance() method of the Class object.

  • public T newInstance()
    • The called constructor is required to be visible, otherwise the illegalaccessexception XXX can not access a member of class EFT will be thrown reflex. Exception of singleton with modifiers "private"
    • You can only call the parameterless constructor, which is the default constructor

The second method: through the newInstance() method of the Constructor object
Several APIs involved in this operation are as follows:

  • public Constructor<?> [] getconstructors() / / get all visible constructors of class objects
  • public Constructor<?> [] getdeclaraedconstructors() / / get all constructors of class objects

Note: 1 The constructor arrays obtained by getconstructors and getDeclaredConstructors are out of order, so do not obtain the specified construction method through index 2 The difference between getxxxx and getdeclaredxxx is that the method with Declared will not return the parent member, but will return the private member; The method without Declared is just the opposite. The following similar methods will not be repeated

  • public Constructor getConstructor(Class<?>... parameterTypes)
    • Gets the specified visible constructor. The parameters are: the parameter type array of the specified constructor
    • If the constructor is invisible or does not exist, a NoSuchMethodException is thrown

Use examples:

Class p = Person.class;
Constructor constructor1 = p.getConstructor();//Gets a constructor without any parameters
Constructor constructor = p.getConstructor(String.class,int.class);//Get the constructor Person(String name,int age)
Copy code
  • public Constructor getDeclaredConstructor(Class<?>... parameterTypes)
    • Gets the specified constructor. The parameters are: the parameter type array of the specified constructor
    • Regardless of constructor visibility, you can get

Use examples:

Class p = Person.class;
Constructor constructor = p.getDeclaredConstructor(String.class,int.class);//Get the constructor Person(String name,int age)
Copy code
  • setAccessible and newInstance methods of Constructor
//To turn off access checking, you need to set this to true before you can access invisible constructors through reflection
//However, the compiler does not allow normal code to use this field because it is only applicable to reflection
public void setAccessible(boolean flag) 

//Create an object with variable length parameters, but you must provide an exact parameter for each parameter when calling the constructor
public T newInstance(Object ... initargs)
    
Use examples:
//Suppose Person has a construction method of private Person(String name) {}
Constructor constructor = Person.class.getConstructor(String.class);
constructor.setAccessible(true);
Person person = (Person)constructor.newInstance("Hot and Sour Soup");
Copy code

A complete example of using Constructor to create objects: see above for details Create an object using reflection - call the constructor of the class object

4, Get the properties and methods of the class through reflection

Using reflection, you can obtain a series of properties and methods of Class objects. Next, list the relevant API s in Class

Class name

  • public String getName() / / get the full path name of the class (return the representation of the class in the virtual machine)
  • public String getCanonicalName() / / get the full pathname of the class (return a more understandable representation)
  • public String getSimpleName() / / get the class name without package name

So what's the difference between the above three? Take a chestnut
Common class name:

Class clz=Person.class;
System.out.println(clz);
System.out.println(clz.toString());
System.out.println(clz.getName());
System.out.println(clz.getCanonicalName());
System.out.println(clz.getSimpleName());

Operation results:
class reflex.Person
class reflex.Person//Class rewrites the toString method and calls the getName() method inside
reflex.Person
reflex.Person
Person
 Copy code

Array:

Class clz=Person[][].class;
System.out.println(clz.getName());
System.out.println(clz.getCanonicalName());
System.out.println(clz.getSimpleName());

Operation results:
[[Lreflex.Person; 
reflex.Person[][]
Person[][]
Copy code

Modifier

  • public native int getModifiers(); // Get modifier

Modifiers are wrapped in an int, and each modifier is a flag bit (set or reset). You can use Java lang.reflect. Use the following methods in the modifier class to verify modifiers:

Modifier.isAbstract(int mod)
    Modifier.isFinal(int mod)
    Modifier.isInterface(int mod)
    Modifier.isNative(int mod)
    Modifier.isPrivate(int mod)
    Modifier.isProtected(int mod)
    Modifier.isPublic(int mod)v
    Modifier.isStatic(int mod)
    Modifier.isStrict(int mod)//true if mod contains the strict FP (strict float point) modifier; Otherwise: false.
    Modifier.isSynchronized(int mod)
    Modifier.isTransient(int mod)
    Modifier.isVolatile(int mod)
Copy code

Use examples:

Class clz= Person.class;
int modifier = clz.getModifiers();
System.out.println("Whether the modifier is public: " + Modifier.isPublic(modifier));

Operation results:
true
 Copy code

Modifier uses & operation internally to make judgment, such as

public static boolean isPublic(int var0) {
    return (var0 & 1) != 0;
}
Copy code

Package information

  • public Package getPackage() / / get package information

From the Package object, you can access Package information such as name. You can also access the information specified in the Manifest file where the Package is located in the JAR file on the classpath. For example, you can specify the version number of the Package in the Manifest file. Can be in Java Learn more about Package classes in lang.Package.

Parent class

  • public native Class<? super T> getSuperclass(); // Get direct parent

The Class object of the parent Class is a Class object like other Class objects, and reflection can continue to be used

Implemented interface

  • public native Class<?> [] getInterfaces(); // Get the list of implemented interfaces
  • A Class can implement multiple interfaces. Therefore, an array of classes is returned. In the Java reflection mechanism, the interface is also represented by a Class object.
  • Note: only the interface declared and implemented by the given class will be returned. For example, if the parent class B of class a implements an interface C, but class A does not declare that it also implements C, then C will not be returned to the array. Even if class a actually implements interface C, because its parent class B implements C.

In order to get a complete list of interfaces implemented by a given class, you need to recursively access the class and its superclass

  • public Type[] getGenericInterfaces() //getGenericInterface returns types including generics

field

  • public Field[] getFields() / / get all visible Field information. The Field array saves a Field instance for each Field declared in the class
  • Public field [] getdeclaraedfields() / / get all field information
  • public Field getField(String name) / / get character information through the field name. The field must be visible, otherwise an exception will be thrown
  • Public field getdeclaraedfield (string name) / / get the visible character information through the field name

About Field

  • public String getName() / / get the field name
  • public Class<?> Gettype() / / get the type of a field

Use examples:

Class clz = Person.class;
Field field = clz.getDeclaredField("name");

System.out.println("Get field name:" + field.getName());
System.out.println("Get field type:" +field.getType());

Operation results:
Get field name: name
 Get field type: class java.lang.String
 Copy code
  • public Object get(Object obj) / / get the value of the field
  • public void set(Object obj, Object value) / / set the value of the field,

be careful:

  1. If the obtained field is not visible, you must use setAccessible(true) to set it accessible before accessing it through set and get
  2. If it is a static field, obj passes in null instead of a specific object; However, if a specific object is passed, it can operate normally

Use examples:

Person person= (Person) clz.newInstance();
field.setAccessible(true);//Set as accessible
field.set(person, "Hot and Sour Soup");//Set the field value through the set method
System.out.println("adopt get Value obtained:"+field.get(person));

Operation results:
adopt get Obtained value: hot and sour soup
 Copy code

When reflection encounters a final decorated field

Take the following example:

public class FinalTest {

    public static void main(String[] args )throws Exception {
        Field nameField = OneCity.class.getDeclaredField("name");

        nameField.setAccessible(true);
        nameField.set(null, "Shenzhen");
        System.out.println(OneCity.getName());

    }
}

class OneCity {
    private static final String name = new String("Beijing");

    public static String getName() {
        return name;
    }
}

Copy code

Output result:

Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field eft.reflex.OneCity.name to java.lang.String
 Copy code

So how to modify its value with reflection?

At this time, we need to make a more thorough reflection - self reflection on the classes in the Java reflection package. The Field object has an attribute called modifiers, which indicates whether the attribute is a combination of public, private, static, final and other modifiers. Here, the modifiers are also reflected, and then the final constraint of nameField is removed, returning to the above situation. The complete code is as follows:

public class FinalTest {

    public static void main(String[] args )throws Exception {
        Field nameField = OneCity.class.getDeclaredField("name");

        Field modifiersField = Field.class.getDeclaredField("modifiers"); //①
        modifiersField.setAccessible(true);
        modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); //②

        nameField.setAccessible(true); //This can't be less. Unless the private is also removed, it may have to be public
        nameField.set(null, "Shenzhen");
        System.out.println(OneCity.getName()); //Output Shenzhen

    }
}

class OneCity {
    private static final String name = new String("Beijing");

    public static String getName() {
        return name;
    }
}
Copy code

Find the Field modifiers at ①, which is also a private variable, so setAccessible(true) is also required. Then, change the modifiers value of nameField at ②, which is to use the bitwise inversion ~ and then remove the final from the modification set by bitwise AND ~ operation. Other features such as private and static remain unchanged. Think again about modifierfield Setint() can change private to public, so there is no need to setAccessible(true) when modifying name.

By removing the final of the attribute, the name is successfully changed to Shenzhen.

Note that the reason why OneCity's name is assigned new String("Beijing") above is to prevent the Java compiler from inline name into the getName() method, and make the method body of getName() return "Beijing", so that getName() always outputs "Beijing".

method

  • public Method[] getMethods() get all visible methods
  • public Method[] getDeclaredMethods() gets all methods, whether visible or not
  • public Method getMethod(String name, Class<?>... parameterTypes)
    • Get method by method name and parameter type
    • If the method you want to access is not visible, an exception will be thrown
    • If the method you want to access has no parameters, pass {null as the parameter type array or no value)
  • public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    • Get method by method name and parameter type
    • If the method you want to access has no parameters, pass {null as the parameter type array or no value)

About Method

  • public Class<?> [] getparametertypes() / / get all parameter types of the method
  • public Class<?> Getreturntype() / / get the return value type of the method
  • public Object invoke(Object obj, Object... args) / / call the method
    • obj: the object to call the method; args: the specific parameters of the method. An accurate parameter must be provided for each parameter
    • If the method is static, here obj passes in null
    • If the method has no parameters, args will pass null or not

Use examples:

Person Class has such a method:
private void testMethod(String param){
    System.out.println("Called testMethod method,The parameters are:"+param);
}



//Call method through reflection
Class clz = Person.class;
Method method=clz.getDeclaredMethod("testMethod",String.class);
method.setAccessible(true);
method.invoke(clz.newInstance(),"I am the specific parameter value");

Operation results:
Called testMethod method,The parameter is: I am the specific parameter value
 Copy code

For more API about Method, check the source code by yourself

Using Java reflection, you can check the methods of a class and call them at run time. This can be used to detect which get and set methods a given class has. You can scan all the methods of a class and check whether each method is a get or set method.
The following is a section of code used to find the get and set methods of the class:

public static void printGettersSetters(Class aClass){

        Method[]methods = aClass.getMethods();

        for(Methodmethod : methods){
           if(isGetter(method))System.out.println("getter: " + method);
           if(isSetter(method))System.out.println("setter: " + method);
        }
}

public staticboolean isGetter(Method method){
        if(!method.getName().startsWith("get"))      return false;
        if(method.getParameterTypes().length!= 0)   return false; 
        if(void.class.equals(method.getReturnType())return false;
        return true;

}

public staticboolean isSetter(Method method){
    if(!method.getName().startsWith("set"))return false;
    if(method.getParameterTypes().length!= 1) return false;
    return true;
}
Copy code

annotation

  • public Annotation[] getAnnotations() / / get all annotations of the current member, excluding inherited ones; (since jdk1.5)
  • Public annotation [] getdeclaraedannotations() / / get all annotations including inheritance; (since jdk1.5)

For notes, I'll explain them in detail next time

Reflection and array

Array: defines multiple variables of the same type
As we all know, array is a special type, which is dynamically generated by virtual machine at runtime, so it will be slightly different when reflecting this type.
Because the array Class is directly created dynamically by the virtual machine runtime, you can't get the Constructor from the Class instance of an array type, and the compiler has no chance to generate the default Constructor for the Class. So you can't create an instance object of this Class through the Constructor in the conventional way.
If you have to try to use Constructor to create a new instance, the runtime program will tell you that you can't match a Constructor. like this:

Class<String[]> cls = String[].class;
Constructor constructor = cls.getConstructor();
String[] strs = (String[]) constructor.newInstance();
Copy code

The program will throw an exception of NoSuchMethodException, telling you that no parameterless constructor can be found in the Class instance

How do we dynamically create an array??
There is a class in Java lang.reflect. Array provides some static methods for dynamically creating and obtaining an array type

  • public static Object newInstance(Class<?> componentType, int length)
    • //Create a one-dimensional array. componentType is the array element type and length is the array length
  • public static Object newInstance(Class<?> componentType, int... dimensions)
    • //Variable parameter dimensions, which specifies the single dimension length of multiple dimensions
  • public static native void set(Object array, int index, Object value)
    • Set the array whose index position is index as value
  • public static native Object get(Object array, int index)
    • Gets the element at the index position of the array array

Add the following to get the API of component type in Class:

  • public native Class<?> getComponentType();
    • If class is an array type, get the type of its element. If it is a non array, return null

One dimensional array instance

//Use reflection to define an int type, 3-length array
int[] intArray = (int[]) Array.newInstance(int.class, 3);

Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);

System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));

//Gets an array of class objects
Class stringArrayClass = Array.newInstance(int.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());


//Gets the component type of the array
String[] strings = new String[3];
Class stringArrayClass2 = strings.getClass();
Class stringArrayComponentType = stringArrayClass2.getComponentType();
System.out.println(stringArrayComponentType);

Operation results:
intArray[0] = 123
intArray[1] = 456
intArray[2] = 789
is array: true
class java.lang.String
 Copy code

Multidimensional array:

// Create a three-dimensional array with the length of each dimension of 5, 10 and 15 respectively
int[] dims = new int[] { 5, 10,15 };
Person[][][] array = (Person[][][]) Array.newInstance(Person.class, dims); // Variable parameters can also be written as follows: object array = array newInstance(Integer.TYPE, 5,10,15);

Class<?> classType0 = array.getClass().getComponentType();    // Returns the array element type
System.out.println("3D array element type:"+classType0);    // The elements of a three-dimensional array are two-dimensional arrays

Object arrayObject = Array.get(array, 2);// Get the element with index 2 in the three-dimensional array and return a two-dimensional array
System.out.println("Two dimensional array element type:"+arrayObject.getClass().getComponentType());

Object oneObject = Array.get(arrayObject, 0);// Get the array with index 0 in the two-dimensional array, and return a one-dimensional array
System.out.println("One dimensional array element type:"+oneObject.getClass().getComponentType());

Array.set(oneObject,14,new Person("Hot and Sour Soup",18));//The element set to the position where the array index is 3

System.out.println("Location of element not set:"+array[0][0][0]);
System.out.println("The location of the element has been set:"+array[2][0][14]);

Operation results:
3D array element type:class [[Left.reflex.bean.Person;
Two dimensional array element type:class [Left.reflex.bean.Person;
One dimensional array element type:class eft.reflex.bean.Person
person The static block of is called
 Location of element not set: null
 The location of the element has been set: Person{name='Hot and Sour Soup', age=18}
Copy code

Reflection and generics

Genericity is a concept within the scope of the Java compiler. It can provide a certain security check before the program runs, and reflection occurs at run time. That is, if you call a generic method by reflection, you will actually bypass the genericity check of the compiler. Let's look at a piece of code:

ArrayList<Integer> list = new ArrayList<>();
list.add(23);
//list.add("fads"); Compilation failed

Class<?> cls = list.getClass();
Method add = cls.getMethod("add",Object.class);
add.invoke(list,"hello");
for (Object obj:list){
    System.out.println(obj);
}

Operation results:
23
hello
 Copy code

Finally, you will find that we take a string from the integer container, because the virtual machine just finds the type information of the ArrayList class from the method area at runtime, parses its add method, and then executes this method. Unlike ordinary method calls, the compiler will detect whether the method exists and whether the parameter types match before calling. Therefore, without this layer of security check of the compiler, it is easier to encounter problems when calling methods reflexively.

Use reflection to get generic information

In practical application, in order to obtain information related to generics, Java adds several types to represent types that cannot be classified into Class class, but have the same name as the basic data type. The following two types are usually used:

  • GenericType: indicates that an element type is a parameterized type or an array type of type variables. @since 1.5
  • ParameterizedType: represents a parameterized type.      @since 1.5

Why introduce these two types? In fact, when obtaining member variables through reflection, the Field class has a method of getType, which can obtain the properties of the Field, but if this property is generic, it can't be obtained, so the above two types are introduced.

example:

public class Person {
    ...
    private Map<String,Integer> map;     
    ...
}

Class<Person> clazz = Person.class;
Field f = clazz.getDeclaredField("map");

//Only common types can be obtained through the getType method
System.out.println("map The types of are:" + f.getType()); //Print Map

//1. Get the generic type of f
Type gType = f.getGenericType();

//2. If gType is a generic type object
if(gType instanceof ParameterizedType)
{
    ParameterizedType pType = (ParameterizedType)gType;
    //Get original type
    Type rType = pType.getRawType();
    System.out.println("The original type is: " + rType);

    //Get generic parameters for generic types
    Type[] gArgs = pType.getActualTypeArguments();
    //Print generic parameters
    for(int i=0; i < gArgs.length; i ++)
    {
        System.out.println("The first"+ i +"The generic types are:" + gArgs[i]);
    }
}
else {
    System.out.println("Failed to get generic information");

}

Operation results:
map The types of are: interface java.util.Map
 The original type is: interface java.util.Map
 The 0th generic type is: class java.lang.String
 The first generic type is: class java.lang.Integer
 Copy code

Reflection source code and performance overhead

Just list the source code of individual methods. Others who are interested can view the source code by themselves (most of them are native methods)

Call the invoke() method

After getting the Method object, the process of calling the invoke method is as follows:

 

 

As you can see, call method After invoking, you will directly call MethodAccessor invoke. MethodAccessor is an instance shared by all methods with the same name mentioned above, which is created by ReflectionFactory. The creation mechanism adopts a method called inflation (after JDK1.4): if the cumulative number of calls of this method is < = 15, NativeMethodAccessorImpl will be created, and its implementation is to directly call the native method to realize reflection; If the cumulative number of calls of this method is more than 15, the MethodAccessorImpl assembled by bytecode will be created by java code. (the number of inflation and 15 can be adjusted in the jvm parameter).
To call MyClass Taking mymethod (string s) as an example, the generated MethodAccessorImpl bytecode is translated into Java code as follows:

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {    
   public Object invoke(Object obj, Object[] args)  throws Exception {
       try {
           MyClass target = (MyClass) obj;
           String arg0 = (String) args[0];
           target.myMethod(arg0);
       } catch (Throwable t) {
           throw new InvocationTargetException(t);
       }
   }
}
Copy code

As for the implementation of native method, because it is more in-depth, this paper will not discuss it

Comparison between calling method directly and calling method through reflection

    public static void main(String[] args) throws Exception {
       // directCall();// Direct call
        reflectCall();//Reflection call
    }

    public static void target(int i) {
    }

    //Direct call
    private static void directCall() {
        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            MethodTest.target(128);
        }
    }

    //Reflection calls the same method
    private static void reflectCall() throws Exception {
        Class<?> klass = Class.forName("eft.reflex.MethodTest");
        Method method = klass.getMethod("target", int.class);

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, 128);
        }
    }
    
Operation results:
Direct call result:
...
121
126
105
115
100 (Take the last 5 values as the peak performance after preheating)

Reflection call result:
...
573
581
593
557
594 (Take the last 5 values as the peak performance after preheating)

Copy code

Result analysis: as the performance benchmark, ordinary calls take more than 100 seconds, and the time spent by reflection calls is about 4 times that of the benchmark

Why does reflection incur performance overhead?

Let's first look at the bytecode file using reflection call:

63: aload_1                         // Load Method object
64: aconst_null                     // Static method, the first parameter of reflection call is null
65: iconst_1
66: anewarray                       // Generate an Object array with a length of 1
69: dup
70: iconst_0
71: sipush        128
74: invokestatic Integer.valueOf    // Automatically boxing 128 into Integer
77: aastore                         // Save to Object array
78: invokevirtual Method.invoke     // Reflection call
 Copy code

You can see the two actions before the reflection call

  • Method.invoke is a variable length parameter method. The last parameter will be an Object array at the bytecode level
    • The Java compiler will generate an Object array with a length of input parameters at the method call, and store the input parameters into the array one by one
  • The Object array cannot store basic types. The Java compiler will automatically box the passed basic types

The above two steps bring performance overhead and GC

How to reduce the cost?

    1. Add the start JVM parameter: - DJava lang.Integer. IntegerCache. High = 128, reduce packing

    After testing, the peak performance is 280.4ms, which is 2.5 times of the benchmark time

    1. Reduce the automatic generation of Object array. The test code is as follows:
 private static void reflectCall() throws Exception {
        Class<?> klass = Class.forName("eft.reflex.MethodTest");
        Method method = klass.getMethod("target", int.class);

        // Construct parameter array outside loop
        Object[] arg = new Object[1];
        arg[0] = 128;

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, 128);
        }
    }
Copy code

Bytecode:

80: aload_2                         // Load Method object
81: aconst_null                     // Static method, the first parameter of reflection call is null
82: aload_3
83: invokevirtual Method.invoke     // Reflection call, no anewarray instruction
 Copy code

After testing, the peak performance is 312.4ms, which is 2.8 times of the benchmark time

    1. Turn off the inflation mechanism
    • -Dsun. reflect. Noifflation = true, turn off the Inflation mechanism, and the reflection call will directly use the dynamic implementation at the beginning, rather than the delegated implementation or local implementation (that is, the invoke method will be implemented in java at the beginning rather than the native method)
    • Turn off permission verification: each reflection call will check the permission of the target method
// -Djava.lang.Integer.IntegerCache.high=128
// -Dsun.reflect.noInflation=true
public static void main(String[] args) throws Exception {
        Class<?> klass = Class.forName("eft.reflex.MethodTest");
        Method method = klass.getMethod("target", int.class);
        // Turn off permission check
        method.setAccessible(true);

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, 128);
        }
    }
Copy code

Peak performance: 186.2ms, 1.7 times the benchmark time

Advantages and disadvantages of reflection

advantage

1. Increase the flexibility of the program and avoid writing the program into the code.

Example: an interface is defined, and there are 20 classes that implement the interface. There are many places where the implementation class is used in the program. If the configuration file is not used for handwriting, the code will change a lot, because every place needs to be changed and it is not easy to locate. If you write the interface and implementation class in the configuration file before writing, you only need to change the configuration file next time, Using reflection (java API has been encapsulated and can be used directly with Class.newInstance()) can be completed

2. The code is concise, the reuse rate of the code is improved, and the external call is convenient

shortcoming

  • Performance overhead - some Java virtual machine optimizations cannot be performed because reflection involves dynamically parsed types. Therefore, the performance of reflection operation is worse than that of non reflection operation, which should be avoided in the code segments frequently called by performance sensitive applications.
  • Breaking encapsulation - reflection can ignore permission checking when calling methods, which may break encapsulation and lead to security problems.
  • Fuzzy program internal logic - programmers want to see the logic of the program in the source code. Reflection bypasses the technology of the source code, which will bring maintenance problems. Reflective code is more complex than the corresponding direct code.
  • Internal exposure - since reflection allows code to perform operations that are illegal in non reflective code, such as accessing private fields and methods, the use of reflection may lead to unexpected side effects, which may lead to code dysfunction and may undermine portability. Reflective code breaks the abstraction, so it may change behavior as the platform upgrades.

Java reflection can access and modify private member variables. Is it meaningful to encapsulate them as private?

Since thieves can access and move smuggled furniture, is it meaningful to package it as a security door? This is the same truth, and Java provides us with a security management mechanism - Security Manager from the application layer. Each Java application can have its own security manager, which will check the access rights of the resources to be protected and other specified operation rights at the running stage, so as to protect the system from malicious operation attacks, so as to achieve the security policy of the system. Therefore, when reflection is used, there are internal security controls. If these are prohibited by security settings, the reflection mechanism cannot access private members.

Does reflection really degrade the performance of your program?

1. Reflection is about 50 ~ 100 times slower than direct call, but you need to execute 1 million times before you feel it
2. To judge the performance of a function, you need to execute the function 1 million times or even 10 million times
3. If you only call reflection occasionally, please forget the performance impact of reflection
4. If you need a lot of call reflection, please consider caching.
5. Your programming idea is the most important factor limiting the performance of your program

Reflection in development

Factory mode: if reflection is used in the factory class, there is no need to modify the factory class factory after adding a new class, as shown in the following example

Through class. In JDBC database forName(Driver). To get the database connection driver

Develop common Frameworks - the most important use of reflection is to develop various common frameworks. Many frameworks (such as Spring) are configured (such as configuring JavaBean s and filters through XML files). In order to ensure the universality of the framework, they may need to load different objects or classes and call different methods according to the configuration file. At this time, reflection - dynamic loading of objects to be loaded at runtime must be used.

Dynamic proxy - in aspect programming (AOP), specific methods need to be intercepted. Usually, dynamic proxy mode is selected. At this time, reflection technology is needed.

Annotation - annotation itself only plays the role of marking. It needs to use the reflection mechanism to call the annotation interpreter and execute the behavior according to the annotation mark. Without reflection, annotations are no more useful than annotations.

Extensibility capabilities - applications can use external user-defined classes by creating extensibility object instances with fully qualified names.

Example of factory mode using reflection:

//Implement factory mode with reflection mechanism:
interface fruit{
    public abstract void eat();
}
class Apple implements fruit{
    public void eat(){
        System.out.println("Apple");
    }
}
class Orange implements fruit{
    public void eat(){
        System.out.println("Orange");
    }
}
class Factory{
    public static fruit getInstance(String ClassName){
        fruit f=null;
        try{
            f=(fruit)Class.forName(ClassName).newInstance();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}

client:
class hello{
    public static void main(String[] a){
        fruit f=Factory.getInstance("Reflect.Apple");
        if(f!=null){
            f.eat();
        }
    }
}
Copy code

Reflection and Introspection

Introspection (introspection):
Introspection is based on reflection, that is, repackaging reflection. It is mainly used to operate JavaBean s. Through introspection, you can get the getter/setter of bean s
Generally speaking, JavaBeans have a self-examination mechanism, which can set their values without knowing what properties JavaBeans have. The core is also a reflection mechanism

Generally, when developing a framework, when you need to operate a JavaBean, if you always use reflection to operate, it will be very troublesome; So sun company developed a set of API to operate JavaBeans

Introspection is a default processing method of Bean class attributes and events in Java language. For example, if there is an attribute name in class A, we can get its value through getname and setname or set a new value. Access the name attribute through getName/setName, which is the default rule. Java provides a set of API to access the getter/setter method of a property. Through these APIs, you don't need to understand this rule (but you'd better find out). These APIs are stored in the package Java Beans.
The general approach is to obtain the BeanInfo information of an object through class Introspector, and then obtain the property descriptor through BeanInfo. Through this property descriptor, we can obtain the getter/setter methods corresponding to a property, and then we can call these methods through reflection mechanism. Let's take a look at an example. This example prints out all attribute names and values of an object:

package introspector;
//These APIs are in Java Beans (under rt.jar package)
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class IntrospectorDemo{
    String name;
    public static void main(String[] args) throws Exception{
        IntrospectorDemo demo = new IntrospectorDemo();
        // If you don't want to list the properties of the parent class,
        //The second parameter of getBeanInfo fills in the information of the parent class
        BeanInfo bi = Introspector.getBeanInfo(demo.getClass(), Object. class );//The Object class is the root parent of all Java classes
        PropertyDescriptor[] props = bi.getPropertyDescriptors();//Gets the descriptor of the attribute
        for ( int i=0;i<props.length;i++){
            System.out.println("Gets the of the property Class Object:"+props[i].getPropertyType());
            props[i].getWriteMethod().invoke(demo, "Hot and Sour Soup" );//Get the setName method and call it with invoke
            System.out.println("Read attribute value:"+props[i].getReadMethod().invoke(demo, null ));
        }
    }

    public String getName() {
        return name;
    }

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

Operation results:
Gets the of the property Class Object: class java.lang.String
 Read attribute value: hot and sour soup
 Copy code

JDK introspective class library: PropertyDescriptor class:
The PropertyDescriptor class represents that a JavaBean class exports a property through memory. Main methods:
1. getPropertyType(), get the Class object of the property;
2. getReadMethod(), get the method used to read the attribute value; (as the getName method above)
3.getWriteMethod(), get the method used to write the attribute value; (such as the method of obtaining setName above)
4.hashCode(), get the hash value of the object;
5. setReadMethod(Method readMethod), set the method used to read the attribute value;
6. setWriteMethod(Method writeMethod), set the method used to write the attribute value.

Apache has developed a set of simple and easy-to-use API s to manipulate Bean properties - BeanUtils toolkit.

reference material

zhuanlan.zhihu.com/p/34168509

fanyilun.me/2015/10/29/...

zhongmingmao.me/2018/12/20/...

 

Topics: Java