Three or four examples of Java reflection

Keywords: Java Spring RESTful

Annotation provides a secure annotation like mechanism to associate any information or metadata with program elements (classes, methods, member variables, etc.).

Some predefined annotations in JDK

@Override: check whether the method marked by the annotation inherits from the parent class (Interface)

@Deprecated: the method marked by the annotation indicates that the method is obsolete

@SuppressWarnings: Annotation for suppressing warnings. Generally, the parameter passed is all, such as @ SuppressWarnings("all")

Custom annotation

Format:

public @interface Annotation name{
     Attribute list;
}

Essence: Annotation essentially uses an interface that inherits the Annotation interface by default

Properties: abstract methods in interfaces

requirement:

The return value type of property (abstract method) needs to be the following types

  • Basic data type
  • String
  • enumeration
  • annotation
  • Arrays of the above types

The attribute is defined, and it needs to be assigned when used:

  • If the default keyword is used to give the default initial value of the attribute when defining the attribute, the attribute can not be assigned when annotation is used

  • If only one attribute needs to be assigned and the name of the attribute is value, value can be omitted and the value can be defined directly.

  • When assigning an array value, the value is wrapped with {}. If the array summary has only one value, {} can be omitted

Meta annotation: an annotation used to describe an annotation

@Target: Describe where annotations can work, such as classes, methods, etc
	ElementType Value:
        TYPE:Can act on classes
        METHOD:Can act on Methods
        FIELD:Can act on member variables
@Retention: Describes the stage in which annotations are retained
    RetentionPolicy Value
        @Retention(RetentionPolicy.RUNTIME)
        (1)RUNTIME:General custom annotation RUNTIME That is, the annotation representing the table description acts on the runtime stage
@Documented:Describes whether annotations are extracted to api In document
@Inherited:Describes whether annotations are inherited by subclasses

Common uses of annotations:

Generate annotations for the document, such as @ author, @ param.

Track code dependencies and implement alternative configuration file functions, such as spring mvc annotations.

Format check during compilation, such as @ override.

Code generation and completion are performed during compilation, such as @ Data of lombok plug-in.

Custom annotations replace configuration files

Create a normal class:

package com.lhq.annotation;

public class Person {
    private String name;
    private int age;

    ...

    public void eat(){
        System.out.println("call eat method!");
    }
}

Create annotation:

@Target(ElementType.TYPE) // Only works on classes
@Retention(RetentionPolicy.RUNTIME)
public @interface pro {
    String className();
    String methodName();
}

Use one:

@pro(className = "com.lhq.annotation.Person",methodName = "eat")
public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // Gets the bytecode file of this class
        Class demoClass = Demo.class;
        pro p = (pro)demoClass.getAnnotation(pro.class);
        // This line of code is equivalent to producing a subclass implementation object of annotation interface in memory, which is equivalent to the following code:

    /*class proImpl implements pro{
        @Override
        public String className() {
            return "com.lhq.annotation.Person";
        }
        @Override
        public String methodName() {
            return "eat";
        }
     */
        String className = p.className();
        String methodName = p.methodName();

        // Get the class object according to the reflection mechanism class name
        Class cls = Class.forName(className);
        // Create objects from class objects
        Object o = cls.newInstance();
        Method method = cls.getMethod(methodName);
        //Call the invoke method to execute the method method in the object
        method.invoke(o); // output: call eat method!
    }
}

Usage 2: configuration file

// yadi.properties
// className=com.lhq.annotation.Person
// methodName=eat

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, IOException {
        // Create Properties object
        Properties pro = new Properties();

        //Load the contents of the properties file into the pro object through the class loader of this class
        ClassLoader classLoader = Demo2.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("com/lhq/annotation/yadi.properties");
        pro.load(is);

        //Use the getProperty method in the properties object to obtain value using key
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        // Get the class object according to the reflection mechanism class name
        Class cls = Class.forName(className);
        // Create objects from class objects
        Object o = cls.newInstance();
        Method method = cls.getMethod(methodName);
        //Call the invoke method to execute the method method in the object
        method.invoke(o); //output: call eat method!
    }
}

Assignment and verification by annotation

Assignment notes:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface InitSex {
    enum SEX_TYPE {MAN,WOMAN};
    SEX_TYPE sex() default SEX_TYPE.MAN;
}

Verification notes:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface ValidateAge {
    int min() default 18;
    int max() default 99;
    int value() default 20;
}

**General class: * * use annotation

public class User {
    private String username;
    @ValidateAge(min = 20,max = 35,value = 22)
    private int age;
    @InitSex(sex = InitSex.SEX_TYPE.MAN)
    private String sex;
	...
}

Test:

public class Test {
    public static void main(String[] args) throws IllegalAccessException {
        User user = new User();
        initUser(user);
        // The age is 0 and the verification is passed
        // output: modify the attribute value. The modified value is MAN
        boolean checkResult = checkUser(user);
        // output: age value does not meet the conditions
        printResult(checkResult);
        // output: verification failed
        // Reset the age and verify the pass
        user.setAge(22);
        checkResult = checkUser(user);
        printResult(checkResult);
        // output: verification passed
    }

    static void initUser(User user) throws IllegalAccessException {
        // Get all the properties in the User class (getFields cannot get the private property:)
        // getFields: reflecting all the accessible public fields of the class
        // getDeclaredFields: objects reflecting all the fields declared by the class or interface
        Field[] fields = User.class.getDeclaredFields();
        for(Field field:fields) {
            if(field.isAnnotationPresent(InitSex.class)) {
                InitSex init = field.getAnnotation(InitSex.class);
                field.setAccessible(true);
                // Set attribute gender
                field.set(user,init.sex().toString());
                System.out.println("Finish modifying the attribute value. The modified value is:" + init.sex().toString());
            }
        }
    }

    static boolean checkUser(User user) throws IllegalAccessException {
        // Get all the properties in the User class (getFields cannot get the private property)
        Field[] fields = User.class.getDeclaredFields();
        boolean result = true;
        // Traverse all attributes
        for (Field field : fields) {
            // If there is this annotation on the attribute, the assignment operation is performed
            if (field.isAnnotationPresent(ValidateAge.class)) {
                ValidateAge validateAge = field.getAnnotation(ValidateAge.class);
                field.setAccessible(true);
                int age = (Integer) field.get(user);
                if (age < validateAge.min() || age > validateAge.max()) {
                    result = false;
                    System.out.println("Age value does not meet the criteria");
                }
            }
        }
        return result;
    }

    static void printResult(boolean checkResult) {
        if (checkResult) {
            System.out.println("Verification passed");
        } else {
            System.out.println("Verification failed");
        }
    }
}

Assign a value to the private property

public class PrivateSet {
    private String read;
    public String getReadOnly() {
        return read;
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        PrivateSet t = new PrivateSet();
        Field f = t.getClass().getDeclaredField("read");
        f.setAccessible(true);
        f.set(t,"write something...");
        System.out.println(t.getReadOnly());
        // output: write something...
    }
}

Why do you speak slowly

The source code of invoke is as follows:

public final class Method extends Executable {
    
    ...
        
    @CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        // Permission check
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        // ============================================
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
        // ============================================
    }
}

In fact, an interface MethodAccessor is maintained inside the Method object, which has two implementation classes, of which NativeMethodAccessorImpl is used to implement local native calls. DelegatingMethodAccessorImpl, as its name suggests, is a delegation implementation class, which delegates the invoke operation to the native Method.

public class ReflectSlow {
    public static void target(int i) {
        new Exception("#"+i).printStackTrace();
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> kclass = Class.forName("com.lhq.annotation2.ReflectSlow");
        // First, Class.forName belongs to the native method, which needs to undergo language execution level conversion. That is, the switch from java to c and then to java.
        Method method = kclass.getMethod("target", int.class);
        // The getMethod operation will traverse the public methods of the class. If it does not hit, it will also look in the parent class. And return the
        // A copy of the method object. After the search is successful, the copy object will occupy heap space and cannot be inlined optimized,
        // On the contrary, it will also cause the increase of gc frequency. It also has an impact on performance.
        method.invoke(null,0);
    }
}

Output:

/** output:
* java.lang.Exception: #0
* 	at com.lhq.annotation2.ReflectSlow.target(ReflectSlow.java:8)
* 	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
* 	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
* 	at sun.reflect.DelegatingMethodAccessorImpl. invoke(DelegatingMethodAccessorImpl.java:43)
* 	at java.lang.reflect.Method.invoke(Method.java:498)
* 	at com.lhq.annotation2.ReflectSlow.main(ReflectSlow.java:14)
*/

Replace with method.invoke(null,i) that circulates multiple times

When the program calls the 16th time, the call stack is changed to GeneratedMethodAccessor1 instead of the native method. This is because the Jvm maintains a threshold - Dsun.reflect.inflationThreshold, which is 15 by default. When the reflection native calls more than 15 times, it will trigger the Jvm to dynamically generate bytecode, and all subsequent operations will call the dynamic implementation.

Compared with the native implementation, the dynamic implementation is much more efficient, because the native implementation needs to switch to the C language at the Java language level, and then switch to the Java language again. However, because the dynamic implementation needs to generate bytecode for the first generation, this operation is time-consuming. Therefore, compared with a single call, native is much faster than dynamic implementation.

To sum up, delegate implementation. If you use less, you can directly use native. Although the native method needs to be switched, the generation of bytecode is slower. If you use more invoke, the generated bytecode will be valuable, and the bytecode will be generated. The switching is omitted and it is fast. Both: slow.

And the annotation processor. Let's open another one.

reference resources

An article to fully master Java custom annotation

Customization of Java annotations

Java uses custom annotations instead of configuration files (Introduction to annotations)

Why is the reflex slow

Why are reflection calls slow? Fine push reflection details!

This article was published on orzlinux.cn

Posted by flaquito on Thu, 30 Sep 2021 12:49:23 -0700