Reflection is an important advanced feature in Java. It is widely used in many well-known open source frameworks, such as the Spring family and Mybatis. It is also the basis for annotations and dynamic proxies. However, directly introducing the use of reflection API makes people still have a vague understanding of reflection after reading, and it is difficult to take the initiative to use it. This blog post attempts to provide a brief and comprehensive introduction to reflection.

What is reflection?

Reflection refers to the mechanism by which an object, a Class, or a string (the full name of a Class) can get an instance of a Class or interface, and use this instance to get all the information about the Class and call its member methods.

A Class instance contains all the information about a Class, so you can create an object with it. Reflection is a Class instance that obtains information from an object.

Java reflection can be explained in the following figure

As you can see, reflection is the use of Class classes at the code level, so it’s worth a brief introduction to Class classes.

Class Class

The Class Class represents classes and interfaces at runtime (enum is a special Class,annotation is a special interface). The JVM creates unique class objects (classes have no public constructor) for each type (type, which is broader than classes, including classes, interfaces, and arrays) when it is loaded from a.class file.

There are three ways to get a Class instance

  1. getClass(), applies to objects
  2. .classProperty, for classes
  3. Class.forName(), class full name string.

The signature of the class is as follows

public final class Class<T> implements java.io.Serializable.GenericDeclaration.Type.AnnotatedElement {}
Copy the code

Reflection instance

The Student class is used to illustrate the use of reflection, including creating objects and calling member methods. For brevity, the Student class retains only the name and Student id. The class definition is as follows:

// Lombok plugin for automatic generation of getters, setters, toStrings, constructors, etc
@Data// equivalent to @getter@setter@requiredargsconstructor @toString @equalSandHashCode.
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private  String name;
    private String id;

}

Copy the code

The code for creating Student is usually as follows

@Test
public void createObjectByReflection(a) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { Class<? > studentClass02= Class.forName("demo.reflection.Student");
    Object obj=studentClass02.getConstructor(String.class,String.class).newInstance("Jack Bauer _Constructor"."20200121");
    Student student=(Student) obj;
    System.out.println(student);

    // Modify attributes through reflection
    Field name = student.getClass().getDeclaredField("name");//getField can access only public attributes
    name.setAccessible(true);
    name.set(student,"Little army _Field");// The first argument is object
    System.out.println(student);

    // Use setter methods to modify properties
    Method setName = student.getClass().getDeclaredMethod("setName", String.class);
    setName.invoke(student,"Flower _Method");// The first argument is object
    System.out.println(student);
}
Copy the code

The first parameter of a method is the object instantiated by the Class. The first parameter of a method is the object instantiated by the Class.

By contrast, we can look at code that creates and modifies objects directly with new, as follows:

public void createObjectByNew(a){
    // Create an object
    Student student = new Student("Jack Bauer _Constructor"."20200121");
    // Modify attributes
    student.setName("Flower _Method");
}
Copy the code

Comparing the two methods of creating an object, we can see that creating an object via reflection and calling a Method are a bit more verbose, as they both need to get the parts to be modified (Constructor, Field, Method) before they can be modified. So why is reflection still so widely used?

Pros and cons of reflection

advantages

Reflection has irreplaceable advantages for the following aspects.

  1. You can call certain methods and set certain properties without knowing the class, and many frameworks apply this primarily.

    . For example, a Student in the class. GetDeclaredMethod (” elegantly-named setName “, String class) is aimed at elegantly-named setName method, has nothing to do with the previous class, that is to say, You can replace Student with classes like String, Integer, Boolean, etc. I’ll just focus on the setName method in the class.

  2. Objects can be created in a uniform manner using strings representing the full name of a class. It improves the versatility and flexibility of the program

    This feature is done primarily by calling the class.forname () function.

  3. Reflection enables annotations and dynamic proxies

disadvantages

The disadvantages of reflection are mainly in the following three aspects

  1. Introduces additional performance overhead. Reflection involves dynamic parsing at runtime and cannot be optimized by the JVM
  2. Encapsulation is broken to some extent. Reflection can be calledsetAccessible(true)To visit theprivateDecorated properties and methods.

Notes on reflection applications

Annotations are labels used to represent metadata and are widely used in SpringBoot and Spring MVC. Annotations can be used on classes, interfaces, methods, properties, and ** to provide additional information. ** Annotations do not have a direct effect on the code, but serve as a marker. You need to write additional code to make use of annotations, otherwise annotations are useless. Fortunately, both frameworks and Java compilers already provide annotation handling code.

The Java language’s built-in annotations are shown below.

Definition of annotations

The syntax for custom annotations in Java is @interface, as shown in the following example

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface MyAnnotation{  
    int value(a) default 1;  
}  
Copy the code

An annotation is a special annotation that uses a strictly restricted method signature to represent attributes. The restrictions on method signatures are as follows:

  1. Method has no parameters
  2. The return value of the method must be of the following types:. Primitive data type, String, enumeration type, annotation type, Class type, one-dimensional array type of the above types

It is recommended that you use default to set the default value for the property (method name).

Use of annotations

Annotations related apis in Java reflection include:

  1. Constructor: Xxx. IsAnnotationPresent (Class)

    For example, the code to determine if the @myAnnotation annotation exists in the Student class looks like this:

    Student.class.isAnnotationPresent(MyAnnotation.class);
    Copy the code

2. Read the notation: xxx. getAnnotation(Class)

; For example, reading the value property in the @MyAnnotation annotation

MyAnnotation test = Student.class.getAnnotation(MyAnnotation.class);
int vaule = test.vaule();
Copy the code

Annotation instance

The Student id must be 4 in length. If the Student id does not meet the requirement, throw an exception.

First we define annotations:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IDAuthenticator {
    int length(a) default 8;
}
Copy the code

Then apply the comment:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private  String name;
    @IDAuthenticator(length = 4)
    private String id;

}
Copy the code

Parse annotations at the end

public  void check(Student student) throws IllegalAccessException {
    for(Field field:student.getClass().getDeclaredFields()){

        if(field.isAnnotationPresent(IDAuthenticator.class)){
            IDAuthenticator idAuthenticator = field.getAnnotation(IDAuthenticator.class);
            field.setAccessible(true);
            // Only id has @idauthenticator annotation,
            // If field is id, @idAuthenticator is not empty.
            // The essence of annotations is labels, which play a screening role
            Object value=field.get(student);
            if(value instanceof String){
                String id=(String) value;
                if(id.length()! =idAuthenticator.length()){throw  new  IllegalArgumentException("the length of "+field.getName()+" should be "+idAuthenticator.length()); }}}}Copy the code

test

@Test
public void useAnnotation(a){
    Student student01 = new Student("Xiao Ming"."20210122");
    Student student02 = new Student("Little army"."2021");
    Student student03 = new Student("Flower"."20210121");
    
    for(Student student:new Student[]{student01,student02,student03}){
        try{
            check(student);
            System.out.println(" Student "+student+" checks ok ");
        } catch (IllegalArgumentException | IllegalAccessException e) {
            System.out.println(" Student "+student+" checks failed "+e); }}}Copy the code

The test results are as follows

As you can see from the examples above, the essence of an annotation is a tag that can be used to perform special processing on certain properties and methods

Dynamic proxy for reflection applications

Proxy is a design pattern used to enhance an existing class. Enhanced content includes log and parameter check. The proxy object and the original object usually have the same method name. A common enhancement is to implement interfaces, while dynamic proxies are mechanisms that create proxy objects directly at run time and dispatch all method calls to a single method on another object. The core of this mechanism is the InvocationHandler, whose purpose is to execute the proppant interface methods. The purpose of introducing the InvocationHandler interface here is to satisfy the single responsibility principle, leaving the proxy object to do the proxy and the method calls to the InvocationHandler.

In computer science, there are two extensions of the original idea of an agent, which is to delegate tasks to other programs and only want results. This meaning is mainly used for computer networks, such as VPN, and proxy Internet access; The other is to hand over objects to other programs that need enhanced objects, such enhancements as logging, user authentication, performance monitoring, and transaction processing. This meaning applies to proxy patterns in design patterns and dynamic proxies in Java, expressed in UML diagrams as follows:

In Java, proxy instances are associated with a InvocationHandler Object (which implements the InvocationHandler interface) to distribute method calls to the Invoke () method.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

Here’s the simplest example:

public class DynamicProxyDemo {
    public static void main(String[] args) {
        // Define an instance of InvocationHandler, which is responsible for implementing method calls to the interface
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);// Enhancements outside the method content
                if(method.getName().equals("morning")){
                    System.out.println("Good morning, "+args[0]);// This implements the contents of the morning method in the interface
                }
                return null; }};//2. Create an interface instance
        Hello  hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),// The ClassLoader of the interface
                newClass<? >[]{Hello.class},/ / interface
                handler);// Pass in a handler to handle the calling method
        / / use
        hello.morning("Bob"); }}interface Hello{
    void morning(String name);
}
Copy the code

** You can see that the dynamic Proxy creates the Proxy object with proxy.newProxyInstance and delegates the interface methods to InvocationHandler. ** So the member attributes of the implementation class of InvocationHandler are generally the implementation class of the interface.

A flying interface is defined by six classes, including Helicopter, Jet, Airliner, Eagle, Sparrow and Swan. The codes are as follows:

interface CanFly {
    void fly(a);
}
class Helicopter implements CanFly {
    @Override
    public void fly(a) {
        System.out.println("I'm a helicopter. I can fly."); }}class Jet implements CanFly {
    @Override
    public void fly(a) {
        System.out.println("I'm a jet. I can fly."); }}class Airliner implements CanFly {... }class Eagle  implements CanFly {... }class Sparrow  implements CanFly {... }class Swan   implements CanFly {... }Copy the code

There is a requirement to count the running time of the fly() method on each class. An intuitive way is to generate a proxy class for each class, as follows:

class HelicopterProxy implements CanFly{
    private Helicopter helicopter ;

    public HelicopterProxy(Helicopter helicopter) {
        this.helicopter = helicopter;
    }

    @Override
    public void fly(a) {
        long start =System.currentTimeMillis();
        helicopter.fly();// The original code,
        long end=System.currentTimeMillis();
        System.out.println("running time of fly method is "+ (end-start)+"ms"); }}class JetProxy implements CanFly{
    private Jet jet ;

    public JetProxy(Helicopter helicopter) {
        this.jet = jet;
    }

    @Override
    public void fly(a) {
        long start =System.currentTimeMillis();
        jet.fly();
        long end=System.currentTimeMillis();
        System.out.println("running time of fly method is "+ (end-start)+"ms"); }}/ /...

Copy the code

The code above is called static proxy, and you can see that it has a number of disadvantages, as follows:

  1. The strong coupling between the proxy class and the original implementation class leads to the repeated occurrence of the same logic code, and too many implementation classes will increase the programming burden. The only difference between JetProxy and HelicopterProxy is that the implementation class is different. The rest of the logic is exactly the same.

  2. The content of the enhancement is strongly coupled to the proxy class, resulting in different enhancement corresponding to different proxy classes (class explosion).

To solve the above problems, Java dynamic proxy can be used, the core of which is to implement the InvocationHandler interface, which only has the invoke method, the implementation code is as follows:

class FlyInvocationHandler implements InvocationHandler{
    private final Object obj;
    public FlyInvocationHandler(Object obj) {
        this.obj=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        long start =System.currentTimeMillis();// AOP, enhanced content
        result=method.invoke(obj,args);
        long end=System.currentTimeMillis();
        System.out.println("In class "+obj.getClass().getName()+" running time of fly method is "+ (end-start)+"ms");
        returnresult; }}Copy the code

The test code is as follows:

public static void flyProxy(a) {
    // Dynamic proxy, experience, only the interface implementation content is different, the rest of the content is the same!

    InvocationHandler handler01=new FlyInvocationHandler(new Helicopter());
    InvocationHandler handler02=new FlyInvocationHandler(new Jet());
    InvocationHandler handler03=new FlyInvocationHandler(new Airliner());
    InvocationHandler handler04=new FlyInvocationHandler(new Eagle());
    InvocationHandler handler05=new FlyInvocationHandler(new Sparrow());
    InvocationHandler handler06=new FlyInvocationHandler(new Swan());

    InvocationHandler[] handlers={handler01,handler02,handler03,handler04,handler05,handler06};
    for(InvocationHandler handler:handlers){

        CanFly canFly=(CanFly) Proxy.newProxyInstance(CanFly.class.getClassLoader(),
                                                      new Class<?>[]{CanFly.class},
                                                      handler);
        canFly.fly();
    }
}
Copy the code

The test results are as follows:

The best way to understand dynamic proxies is to read dynamically generated bytecode, which can be persisted in memory to disk using the following command:

public static void main(String[] args) {
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
    helloProxy();
}
Copy the code

Well, the content of this is here, if you feel the above content to help you, please click like attention, thank you.