In depth analysis of java reflection (I) – core class libraries and methods

premise

The java reflection API is in javase 1 7, but Oracle jdk11 was used when writing this article, because jdk11 also uploaded the source code under the sun package. You can directly view the corresponding source code and debug through the IDE.

This paper mainly introduces the basic concept of reflection and the common methods of core classes class, constructor, method, field and parameter.

This article is very long. Please prepare a comfortable posture to read.

What is reflection

Reflection is the ability of a programming language that can check and dynamically call classes, constructs, methods, properties, etc. at run time. It can even feel the name of classes, methods, etc. at compile time. Oracle's official tutorial on java reflection points out that reflection is used by applications to check or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced function that needs to be used by developers with basic knowledge of the Java language.

Reflection has many advantages. As mentioned earlier, you can check or modify the runtime behavior of the application, suppress modifiers, restrict direct access to private properties, and so on. Here are some disadvantages:

The class libraries related to reflection in JDK are concentrated in Java Lang.reflect package and Java Lang package, Java Lang.reflect package and Java Lang package is a part of Java. Lang package that developers can use directly The implementation class of the interface in lang.reflect package is stored in sun In the reflect package, generally, the class libraries under the sun package may change with the platform upgrade. Generally, use them as little as possible, otherwise the original code may not run normally due to the JDK upgrade. Some reflection related class libraries are stored in JDK internal. In the reflect package, this package is used internally in the JDK, and it is generally not recommended to abuse the class library. It can be understood as Java Lang.reflect package and Java The class library in Lang package is a developer oriented class library.

Graphic reflection core class system

java. The core classes of lang.reflect package include core class, constructor, method, field and parameter. Their basic system is as follows:

java. Lang. class class inheritance system:

java. lang.reflect. Constructor class inheritance system:

java. lang.reflect. Method class inheritance system:

java. lang.reflect. Field class inheritance system:

java. lang.reflect. Parameter class inheritance system:

As can be seen from their class inheritance diagram:

Next, we will briefly analyze the functions provided by annotatedelement, accessibleobject, member, genericdeclaration and executable, and then focus on the common methods of class, constructor, method, field and parameter.

Let's talk about a rule first. In class, the getxxx () method is different from the getdeclaredxxx () method. The operation method of annotation type is an exception, because annotation based modifiers must be public:

If you want to obtain the methods of all modifiers of a class, including the methods in all parent classes, it is recommended to call getdeclaraedmethods() recursively (the so-called recursive call is to recursively call getdeclaraedmethods() method from the parent class of the target class until the parent class is of object type. For this idea, please refer to the relevant tool classes in the spring framework). Obtaining all fields and constructors of a class can also be operated similarly. You can refer to or directly use the relevant methods of the tool class reflectionutils in spring@ Inherited meta annotation is a tag annotation, @ inherited describes that a marked annotation type can be inherited. Expand it in detail when analyzing annotatedelement.

Type interface

java. lang.reflect. The type interface is the common parent class of all types in Java. These types include original type, generic type, array type, type variable and basic type. The interface definition is as follows:

public interface Type {

    default String getTypeName() {
        return toString();
    }
}

Annotatedelement interface

Annotatedelement is an interface. The methods it defines are mainly related to annotation operations, such as judging the existence of annotations and obtaining annotations.

A simple example:

public class Main {

    public static void main(String[] args) {
        Class<?> clazz = Sub.class;
        System.out.println("-----getAnnotations-----");
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.toString());
        }
        System.out.println("-----getDeclaredAnnotation-->SupperAnnotation-----");
        SupperAnnotation declaredSupperAnnotation = clazz.getDeclaredAnnotation(SupperAnnotation.class);
        System.out.println(declaredSupperAnnotation);
        System.out.println("-----getAnnotation-->SupperAnnotation-----");
        SupperAnnotation supperAnnotation = clazz.getAnnotation(SupperAnnotation.class);
        System.out.println(supperAnnotation);
        System.out.println("-----getDeclaredAnnotation-->SubAnnotation-----");
        SubAnnotation declaredSubAnnotation = clazz.getDeclaredAnnotation(SubAnnotation.class);
        System.out.println(declaredSubAnnotation);
        System.out.println("-----getDeclaredAnnotationsByType-->SubAnnotation-----");
        SubAnnotation[] declaredSubAnnotationsByType = clazz.getDeclaredAnnotationsByType(SubAnnotation.class);
        for (SubAnnotation subAnnotation : declaredSubAnnotationsByType) {
            System.out.println(subAnnotation);
        }
        System.out.println("-----getDeclaredAnnotationsByType-->SupperAnnotation-----");
        SupperAnnotation[] declaredSupperAnnotationsByType = clazz.getDeclaredAnnotationsByType(SupperAnnotation.class);
        for (SupperAnnotation supperAnnotation1 : declaredSupperAnnotationsByType) {
            System.out.println(supperAnnotation1);
        }
        System.out.println("-----getAnnotationsByType-->SupperAnnotation-----");
        SupperAnnotation[] supperAnnotationsByType = clazz.getAnnotationsByType(SupperAnnotation.class);
        for (SupperAnnotation supperAnnotation2 : supperAnnotationsByType) {
            System.out.println(supperAnnotation2);
        }
    }


    @SupperAnnotation
    private static class Supper {

    }

    @SubAnnotation
    private static class Sub extends Supper {

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    private @interface SupperAnnotation {

        String value() default "SupperAnnotation";
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Target(ElementType.TYPE)
    private @interface SubAnnotation {

        String value() default "SubAnnotation";
    }
}

Output after operation:

-----getAnnotations-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotation-->SupperAnnotation-----
null
-----getAnnotation-->SupperAnnotation-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)
-----getDeclaredAnnotation-->SubAnnotation-----
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotationsByType-->SubAnnotation-----
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotationsByType-->SupperAnnotation-----
-----getAnnotationsByType-->SupperAnnotation-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)

You can try to comment out @ inherited and run it again to compare the results. If @ inherited is commented out, @ supplerannotation in its parent class supplier can never be obtained from the sub class. Class, constructor, method, field and parameter all implement the annotatedelement interface, so they all have the function of operating annotations.

Member interface

The member interface annotation provides some descriptions of member attributes. The main methods are as follows:

These methods are easy to understand except issynthetic (). Generally speaking, synthetic is a field, method, class or other structure introduced by the compiler, which is mainly used inside the JVM. Some tips are made to follow some specifications to bypass these specifications. It feels like cheating. It is only done openly by the compiler, and ordinary developers do not have permission (but in fact, it can be used sometimes). The following example refers to the synthetic Java composite type:

public class Main {

    private static class Inner {
    }
    static void checkSynthetic (String name) {
        try {
            System.out.println (name + " : " + Class.forName (name).isSynthetic ());
        } catch (ClassNotFoundException exc) {
            exc.printStackTrace (System.out);
        }
    }
    public static void main(String[] args) throws Exception
    {
        new Inner ();
        checkSynthetic ("com.fcc.test.Main");
        checkSynthetic ("com.fcc.test.Main$Inner");
        checkSynthetic ("com.fcc.test.Main$1");
    }
}
//打印结果:
com.fcc.test.Main : false
com.fcc.test.Main$Inner : false
com.fcc.test.Main$1 : true
//编译结果,生成三个class文件: Main.class/Main$Inner/Main$1.class
// $FF: synthetic class
class Main$1 {
}

The inner inner class is private. If a class with an internal class has no relationship between the internal and external classes after compilation, the private internal class does not have an external constructor by default after compilation (if a public constructor is manually given in the above code, main $1 will not appear). However, we also know that the external class can reference the internal class. After compilation, there are two unrelated classes, A class does not have an external constructor, but another class does have permission to instantiate the instance object of this class (this is the key point. Even if the internal class does not have a public constructor, the external class has the permission to instantiate the internal class object). In this case, the compiler will generate a composite class, that is, main $1, An empty class with nothing (yes, nothing, not even a constructor). However, I still don't understand the implementation principle here. I originally thought that the composite class is a copy of the internal class. The external class accesses the internal class. The compiler thinks that it only interacts with the composite class, but only the external class has access. In fact, no matter how the internal class changes, the composite class is just an empty class, It's a bit like a marker (the real effect is unknown).

Accessibleobject class

Accessibleobject is an ordinary Java class that implements the annotatedelement interface, but the implementation of non default methods corresponding to annotatedelement directly throws exceptions, that is, the interface methods of annotatedelement must be implemented by subclasses of accessibleobject. Personally, I think accessibleobject should be designed as an abstract class. Accessibleobject is in jdk1 1. It has been improved in jdk9. Some new methods have been added. The following are common methods:

Generally speaking, we need to judge whether the modifier is public through the getmodifiers () method. If it is not public, we need to call setaccessible (true) to suppress the modifier, otherwise an exception will be thrown because of unauthorized access.

Genericdeclaration interface

The genericdeclaration interface inherits from annotatedelement. Its source code is as follows:

public interface GenericDeclaration extends AnnotatedElement {

    public TypeVariable<?>[] getTypeParameters();
}

A new method gettypeparameters () is added to return the TypeVariable array of type variables. The TypeVariable here is a type variable. Its definition is as follows:

public interface TypeVariable<D extends GenericDeclaration> extends Type,AnnotatedElement {
    //获得泛型的类型(Type)上限数组,若未明确声明上边界则默认为Object
    Type[] getBounds();
    //获取声明该类型变量实体(即获得类、方法或构造器名)
    D getGenericDeclaration();
    //获得泛型参数的字面量名称,即K、V、E之类名称
    String getName();
    //获得泛型的注解类型(AnnotatedType)上限数组,若未明确声明上则为长度为0的空数组
    AnnotatedType[] getAnnotatedBounds();
}

Later articles will expand when introducing generics.

Executable class

Executable is an abstract class, which inherits from accessibleobject and implements the member and genericdeclaration interfaces. The implementation classes of executable are method and constructor. Its main function is to extract some common methods from method and constructor, such as annotation operation, parameter operation, etc., which will not be expanded in detail here.

Modifier

Modifier mainly provides a series of static methods to judge the specific type of modifier parameters based on int type. This modifier parameter comes from getmodifiers () methods of class, constructor, method, field and parameter. The main methods of modifier are described below:

Class class

Class implements serializable, genericdeclaration, type and annotatedelement interfaces. It provides methods such as type judgment, type instantiation, obtaining method list, obtaining field list, obtaining parent class generic type, etc. The main methods are as follows:

Getname(), getcanonicalname() and getsimplename() are all used to obtain the name of the class, but there are differences. The following is an example to illustrate:

public class Main {

	public static void main(String[] args) {
		Supper<String,List<Integer>> supper = new Supper<>();
		Class<?> clazz = supper.getClass();
		System.out.println("name->" + clazz.getName());
		System.out.println("canonicalName->" + clazz.getCanonicalName());
		System.out.println("simpleName->" + clazz.getSimpleName());
		System.out.println("======================================");
		String[][] strings = new String[1][1];
		System.out.println("name->" + strings.getClass().getName());
		System.out.println("canonicalName->" + strings.getClass().getCanonicalName());
		System.out.println("simpleName->" + strings.getClass().getSimpleName());
	}

	private static class Supper<K,V> {
		private K key;
		private V value;
        //省略setter和getter方法
	}
}

Output results after operation:

name->club.throwable.reflect.Main$Supper
canonicalName->club.throwable.reflect.Main.Supper
simpleName->Supper
======================================
name->[[Ljava.lang.String;
canonicalName->java.lang.String[][]
simpleName->String[][]

It is simply understood as:

The following is another example of instantiating objects and operations through class names. It can be seen from the example that instantiated objects can not rely on the new keyword, which is the strength of reflection:

public class Main3 {

	public static void main(String[] args) throws Exception {
		Class<?> clazz = Class.forName("club.throwable.reflect.Main3$Supper");
		Supper supper = (Supper) clazz.newInstance();
		System.out.println(supper.sayHello("throwable"));
	}

	public static class Supper {

		public String sayHello(String name) {
			return String.format("%s say hello!",name);
		}
	}
}

One thing to note here, class The forname method can only be used on classes whose modifier is public. If it is used on other modifier classes, an exception will be thrown. Then, if the modifier of the above supplier class is modified to private, how can it be instantiated normally? This problem will be solved when analyzing the constructor below. In addition, class The forname method is not the only way to obtain class instances. There are three ways to summarize:

Generally speaking, using "class name. Class" is simple, safe and efficient. Because it will be checked at compile time, it does not need to be placed in the try statement block, and it eliminates the call to the forname () method (forname () method is a time-consuming method), so it is relatively efficient.

Finally, analyze these difficult methods getenclosingclass(), getenclosingconstructor(), getenclosingmethod():

When we create a new class, this class can make the member class defined in another class, the internal class defined in the constructor, and the internal class defined in the method. The class, construct or method that defines the current class can be obtained inversely through the current class. These three cases correspond to the above three methods. for instance:

Use example of getenclosingclass() method:

public class Main5 {

    public static void main(String[] args) throws Exception{
        Class<Outter.Inner> clazz = Outter.Inner.class;
        Class<?> enclosingClass = clazz.getEnclosingClass();
        System.out.println(enclosingClass.getName());
    }
    // Inner类是Outter类的成员类
    public static class Outter {

        public static class Inner {

        }
    }
}

Output results:

org.throwable.inherited.Main5$Outter

Here, inner is the currently defined class. It is the static member class of outer, or outer is the closed class of inner. The class instance of outer is obtained through the getenclosingclass() method of inner's class.

Use example of getenclosingconstructor() method:

public class Main6 {

    public static void main(String[] args) throws Exception {
        Outter outter = new Outter();
    }

    public static class Outter {

        //Outter的无参数构造器
        public Outter() {
            //构造中定义的内部类
            class Inner {

            }

            Class<Inner> innerClass = Inner.class;
            Class<?> enclosingClass = innerClass.getEnclosingClass();
            System.out.println(enclosingClass.getName());
            Constructor<?> enclosingConstructor = innerClass.getEnclosingConstructor();
            System.out.println(enclosingConstructor.getName());
        }
    }
}

Output results:

org.throwable.inherited.Main6$Outter
org.throwable.inherited.Main6$Outter

Here, inner is the construction internal class defined in the nonparametric construction of outer, and it can only be used in the nonparametric construction of outer. The nonparametric construction of outer is obtained through the getenclosingconstructor() method of inner's class.

Use example of getenclosingmethod() method:

public class Main7 {

    public static void main(String[] args) throws Exception {
        Outter outter = new Outter();
        outter.print();
    }

    public static class Outter {

        public void print(){
            //方法print中定义的内部类
            class Inner {

            }

            Class<Inner> innerClass = Inner.class;
            Class<?> enclosingClass = innerClass.getEnclosingClass();
            System.out.println(enclosingClass.getName());
            Method enclosingMethod = innerClass.getEnclosingMethod();
            System.out.println(enclosingMethod.getName());
        }
    }
}

Output results:

org.throwable.inherited.Main7$Outter
print

Here, inner is the internal class of the method defined in the print method of outer, and it can only be used in the print method of outer. The print method of outer is obtained through the getenclosingmethod () method of inner's class. This method may not be commonly used, but you can see similar class definition logic in the source code of the jdbctemplate of a certain version of spring JDBC.

As mentioned earlier, there are differences between getxxx() method and getdeclaredxxx() method. Here is a comparison table:

How to get the field list in class:

Method to get method list in class:

Method to get constructor list in class:

Constructor class

Constructor is used to describe the constructor of a class. In addition to obtaining the annotation information of structures, parameters and parameters, it also plays a very important role in inhibiting the instantiation of modifiers, while the instantiation method newinstance of class can only instantiate classes with public modifiers. The main methods of constructor are as follows:

Let's take an example to illustrate that using the construction instantiation object can suppress the access permission control of modifiers:

public class Main8 {

    public static void main(String[] args) throws Exception{
        Class<Supper> supperClass = Supper.class;
        Constructor<Supper> constructor = supperClass.getDeclaredConstructor();
        constructor.setAccessible(Boolean.TRUE);
        Supper supper = constructor.newInstance();
        supper.sayHello("throwable");
    }

    private static class Supper {

        public void sayHello(String name) {
            System.out.println(String.format("%s say hello!",name));
        }
    }
}

Output results:

throwable say hello!

This is why the instantiation of classes in the implementation framework of some IOC containers preferentially depends on parameterless construction. If the class#newinstance method is used, the above code call logic will throw exceptions.

Method class

Method is used to describe the method of a class. In addition to the annotation information of the method, it can also obtain the annotation information of the method parameters, return values and other information. Method common methods are as follows:

Pay attention to the invoke (object obj, object... Args) method. The first is the object to call this method. The parameters and return values of the remaining methods are the return values of the method. If the modifier of the method is not public, you need to call setaccessible (boolean flag) to suppress the permission judgment of the method access modifier before calling the invoke method, otherwise an exception will be thrown. An example is as follows:

public class Main10 {

    public static void main(String[] args) throws Exception{
        Class<Supper> supperClass = Supper.class;
        Supper supper = supperClass.newInstance();
        Method sayHello = supperClass.getDeclaredMethod("sayHello",String.class);
        sayHello.setAccessible(Boolean.TRUE);
        sayHello.invoke(supper,"throwable");
    }

    public static class Supper{

        private void sayHello(String name){
            System.out.println(String.format("%s say hello!",name));
        }
    }
}

Output results:

throwable say hello!

Field class

The field class is used to describe the attribute or member variable in a class. Through the field, you can obtain the annotation information and generic information of the attribute, obtain and set the value of the attribute, etc. The main methods of field are as follows:

The annotation is ignored here, and field implements getboolean, setboolean and other methods in the fieldaccessor interface. The following is an example to illustrate the use of field:

public class Main12 {

    public static void main(String[] args) throws Exception {
        Class<Supper> supperClass = Supper.class;
        Supper supper = supperClass.newInstance();
        Method sayHello = supperClass.getDeclaredMethod("sayHello");
        sayHello.setAccessible(Boolean.TRUE);
        Field name = supperClass.getDeclaredField("name");
        name.setAccessible(Boolean.TRUE);
        name.set(supper,"throwable");
        System.out.println("Field get-->" + name.get(supper));
        sayHello.invoke(supper);
        name.set(supper,"throwable-10086");
        System.out.println("Field get-->" + name.get(supper));
        sayHello.invoke(supper);
    }

    public static class Supper {

        private String name;

        private void sayHello() {
            System.out.println(String.format("%s say hello!",name));
        }
    }
}

Output results:

Field get-->throwable
throwable say hello!
Field get-->throwable-10086
throwable-10086 say hello!

Parameter class

Parameter is used to describe the parameters of method or constructor. It is mainly used to obtain the name of the parameter. Because there is no concept of formal parameters in Java, that is, parameters have no name. Jdk1. 8. A parameter is added to fill this problem. If the - parameters parameter is added when using the javac compiler, it will be generated The class file stores additional meta information of parameters, which will lead to The size of the class file increases. When you type javac - help, you will see the - parameters option. The method to get the parameter is the getparameters method of method or the parent class executable of constructor. Generally speaking, parameter is a fallback scheme for obtaining parameter names because jdk1 There was no such class before 8, and even if you used jdk1 8 if the javac compiler does not add the - parameters parameter, the parameter names obtained through the parameter will be "arg0", "arg1" Meaningless parameter name like 'argn'. In the general framework, other methods are used to resolve the parameter names of methods or constructors. Refer to the source code of spring. Specifically, localvariabletableparameternnamediscoverer uses ASM to resolve and read the bytecode of class files and extract the parameter names. The main methods of parameter are as follows:

For example, add the parameter - parameters during compilation:

public class Main11 {

    public static void main(String[] args) throws Exception {
        Class<Supper> supperClass = Supper.class;
        Method sayHello = supperClass.getDeclaredMethod("sayHello",String.class);
        sayHello.setAccessible(Boolean.TRUE);
        Parameter[] parameters = sayHello.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println("isNamePresent->" + parameter.isNamePresent());
            System.out.println("isImplicit->" + parameter.isImplicit());
            System.out.println("getName->" + parameter.getName());
            System.out.println("=====================");
        }

    }

    public static class Supper {

        private void sayHello(String name) {
            System.out.println(String.format("%s say hello!",name));
        }
    }
}

Output results:

isNamePresent->true
isImplicit->false
getName->name
=====================

If the compilation parameter - parameters is not set, the following results will be output:

isNamePresent->false
isImplicit->false
getName->arg0
=====================

Summary

This article begins with an introduction to the basics of reflection. Later, it spends a lot of time listing the API and API use of relevant class libraries. Only by mastering these class libraries can we easily carry out reflection programming.

Personal blog

(end of this paper e-a-2018122)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>