Application Practice of Java Reflection Mechanism

Posted by Dave96 on Sun, 30 Jun 2019 23:03:50 +0200

This article is the author's original, reproduced please declare Blog Origin:)

Introduction

Java reflection mechanism is a very powerful function. Reflection can be seen in many large projects such as Spring and Mybatis. Through reflection mechanism, we can get the type information of objects at run time. By using this feature, we can realize design patterns such as factory mode and proxy mode, and also solve some troublesome problems such as Java generic erasure. In this paper, we will apply Java's reflection mechanism from the perspective of practical application.

Reflective Foundation

p.s: This article requires the reader to have a certain understanding of the API of reflection mechanism. If you haven't been in touch with it before, it is suggested that you look at the official documents first. Quick Start.

Before applying reflection mechanism, let's first look at how to get a reflection class Class corresponding to an object. In Java, we have three ways to get a reflection class of an object.

Through the getClass method

In Java, each Object has a getClass() method, through which we can get the corresponding reflection class of the Object:

String s = "http://www.ziwenxie.site";
Class<?> c = s.getClass();

By forName method

We can also call the static method forName():

Class<?> c = Class.forName("java.lang.String");

Use.class.

Or we can use. class directly:

Class<?> c = String.class;

Getting type information

At the beginning of the article, we mentioned that one of the great benefits of reflection is that it allows us to get type information of objects during operation. Let's take a look at it in detail through an example.

First, we create a new interface A under the typeinfo.interfacea package:

package typeinfo.interfacea;

public interface A { void f(); }

Then we create a new class C under the typeinfo.packageaccess package. Class C implements interface A, and we also create several other methods for testing. Note that the permissions of the following methods are different.

package typeinfo.packageaccess;

import typeinfo.interfacea.A;

class C implements A {
    public void f() { System.out.println("public C.f()"); }
    public void g() { System.out.println("public C.g()"); }
    protected void v () { System.out.println("protected C.v()"); }
    void u() { System.out.println("package C.u()"); }
    private void w() { System.out.println("private C.w()"); }

}

public class HiddenC {
    public static A makeA() { return new C(); }
}

In the call Hidden Method () method, we use several new API s, in which getDeclared Method () is used to obtain a method declared by the Class class reference object itself according to the method name, and then we can trigger the related method of the object by calling invoke():

package typeinfo;

import typeinfo.interfacea.A;
import typeinfo.packageaccess.HiddenC;

import java.lang.reflect.Method;

public class HiddenImplementation {
    public static void main(String[] args) throws Exception {
        A a = HiddenC.makeA();
        a.f();
        System.out.println(a.getClass().getName());
        // Oops! Reflection still allows us to call g():
        callHiddenMethod(a, "g");
        // And even methods that are less accessible!
        callHiddenMethod(a, "u");
        callHiddenMethod(a, "v");
        callHiddenMethod(a, "w");
    }

    static void callHiddenMethod(Object a, String methodName) throws Exception {
        Method g = a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a);
    }
}

From the output, we can see that whether it is public, default, protect ed or private methods, we can call them freely through reflection classes. Of course, we are only here to show the power of reflection, but this technique is not advocated in actual development.

public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()

We just tested the Method object above. After the interested readers are familiar with the reflective API, they might as well test Filed, so we won't repeat it here.

Realization of Aspect-Oriented Programming by Dynamic Agent

AOP is one of Spring's powerful features. AOP means face-to-face programming, that is, to separate business-independent code. When we need to add related transactions, we don't want to modify the business itself. What's the advantage of aspect-oriented programming over object-oriented programming? Let's take an example to show that for beginners, they often write the following code:

public class Example1 {
    public void execute() {
        // Logging
        Logger logger = Logger.getLog(...);
        // Perform performance statistics
        PerformanceUtil.startTimer(...);
        // Privilege check
        if (!user.hasPrevilege()) {
            // throw
        }
        // Execute real business
        executeTransaction();
        PerformanceUtil.endTimer();
    }
}

Although the only business we really want to execute above is executeTransaction(), the code related to logs, performance and permissions almost obscures the real business code. And in the future, if we have an Example2, it also needs to implement the same log, performance, and permission code. In this way, when we need to add relevant logic checks in the future, we need all Examples to be reconstructed, which obviously does not conform to the basic principle of object-oriented encapsulation change.

The above scenario can be solved by using template method and decorator pattern. It is implemented by dynamic proxy in Spring. Next, we simulate AOP implementation in Spring through an example.

When we want to achieve the business, the statistical procedure counts the execution time of employees'wages and checks the user's rights. First of all, we implement the Salary class, which contains some business logic to realize the statistics of employee wages.

public interface SalaryInterface {
    public void doSalary();
}
public class Salary implements SalaryInterface {
    public void doSalary() {
        ...
    }
}

Through Invocation Handler, we implement dynamic proxy. Later, before we call the related methods of obj, we will use invoke method to proxy, instead of calling obj method directly.

public class SimpleProxy implements InvocationHandler {
    private Object obj;
    private Object advice;

    // Binding proxy objects
    public Object bind(Object obj, Advice advice) {
        this.obj = obj;
        this.advice = advice;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
            obj.getClass().getInterfaces(), this)
    }

    // Implementing proxy
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwalbe {
        Object result = null;
        try {
            advice.before();
            result = method.invoke(obj, args);
            advice.after();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return result
    }
}

Simulate the Advice interface in Spring:

public interface Advice {
    public void before();
    public void after();
}

Implement Time Advice to count the execution time of the program:

public class TimeAdvice implements Advice {
    long startTime;
    long endTime;

    @Override
    public void before() {
        startTime = System.nanoTime(); // Get the start time
    }

    @Override
    public void after() {
        endTime = System.nanoTime(); // Get the end time
    }
}

The client invocation code is as follows:

public class Client {
    public static void main(String[] args) {
        SimpleProxy = new SimpleProxy();
        SalaryInterface salaryInterface =
            (SalaryInterface) simpleProxy.bind(new Salary(), new TimeAdvice());
        salaryInterface.doSalary();
    }
}

If we need new permission controls now, we implement the ControlAdvie class:

public class ControlAdvice implements Advice {
    @Override
    public void before() {
        if (...) {
            ...
        } else {
            ...
        }
    }

    @Override
    public void after() {
        ...
    }
}

Our client code only needs to be changed to simpleProxy.bind(new Salary(), new ControlAdvie), and SimpleProxy itself does not need to make any changes.

Combining with annotations

Reflection mechanism is also widely used in unit testing frameworks such as Junit, that is, through annotations. Now let's briefly understand how to get annotation information of relevant methods through reflection mechanism. For example, we have the following business scenario. When a user changes his password, in order to ensure the security of his password, we require the user's new password to meet some conditions, such as at least including a non-digital character, not with the previous secret. Conditions such as the same code, etc.

import java.lang.annotation.*

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserCase {
    public int id();
    public String description() default "no description";
}

The following is the implementation of our tool class for password detection:

public class PasswordUtils {
    @UserCase(id=47, description="Password must contain at least one numeric")
    public boolean validatePassword(String password) {
        return (password.matches("\\w*\\d\\w*"));
    }

    @UserCase(id=48)
    public String encryptPassword(String password) {
        return new StringBuilder(password).reverse().toString();
    }

    @UserCase(id=49, description="New passwords can't equal previously used ones")
    public boolean checkForNewPassword(List<String> prevPasswords, String password) {
        return !prevPasswords.contains(password);
    }
}

Using reflection, we can write clearer test code, in which getDeclared Methods () can get the related methods declared by the related objects themselves, and getAnnotation() can get the specified annotations of the Method objects.

public class UseCaseTracker {
    public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
        for(Method m : cl.getDeclaredMethods()) {
            UseCase uc = m.getAnnotation(UseCase.class);
            if(uc != null) {
                System.out.println("Found Use Case: " + uc.id() + " " + uc.description());
                useCases.remove(new Integer(uc.id()));
            }
        }

        for(int i : useCases) {
            System.out.println("Warning: Missing use case-" + i);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<Integer>();
        Collections.addAll(useCases, 47, 48, 49, 50);
        trackUseCases(userCases, PasswordUtils.class);
    }
}

Solving generic erasure

Now there's a business scenario where we have a generic collection class List < Class <? Extends Pet >, and we need to figure out how many specific Pets are in this collection class. Because of Java's generic erasure, it's certainly not possible to notice something like List <? Extends Pet > because after the compiler has done static type checking, JVM will treat all objects in the collection as Pets during run time, but it's not known whether Pet represents Cat or Dog, so the type information of objects during run time is actually lost. p.s: About generic erasure, I'm here Last article There are detailed explanations. Interested friends can have a look.

To implement our example above, let's first define several classes:

public class Pet extends Individual {
    public Pet(String name) { super(name); }
    public Pet() { super(); }
}

public class Cat extends Pet {
    public Cat(String name) { super(name); }
    public Cat() { super(); }
}

public class Dog extends Pet {
    public Dog(String name) { super(name); }
    public Dog() { super(); }
}

public class EgyptianMau extends Cat {
    public EgyptianMau(String name) { super(name); }
    public EgyptianMau() { super(); }
}

public class Mutt extends Dog {
    public Mutt(String name) { super(name); }
    public Mutt() { super(); }
}

The above Pet class inherits from Individual. The implementation of Individual class is a little more complicated. We implemented the Comparable interface and redefined the rules of class comparison. If not very clear, it doesn't matter. We have abstracted it, so it doesn't matter if we don't understand the principle of implementation.

public class Individual implements Comparable<Individual> {
    private static long counter = 0;
    private final long id = counter++;
    private String name; // name is optional

    public Individual(String name) { this.name = name; }

    public Individual() {}

    public String toString() {
        return getClass().getSimpleName() + (name == null ? "" : " " + name);
    }

    public long id() { return id; }

    public boolean equals(Object o) {
        return o instanceof Individual && id == ((Individual)o).id;
    }

    public int hashCode() {
        int result = 17;
        if (name != null) {
            result = 37 * result + name.hashCode();
        }
        result = 37 * result + (int) id;
        return result;
    }

    public int compareTo(Individual arg) {
        // Compare by class name first:
        String first = getClass().getSimpleName();
        String argFirst = arg.getClass().getSimpleName();
        int firstCompare = first.compareTo(argFirst);
        if (firstCompare != 0) {
            return firstCompare;
        }

        if (name != null && arg.name != null) {
            int secendCompare = name.compareTo(arg.name);
            if (secendCompare != 0) {
                return secendCompare;
            }
        }

        return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
    }
}

Next, we create an abstract class PetCreator, and then we can directly get the collection of related Pet classes by calling the arrayList() method. What does it mean to use the newInstance() method that we did not mention above, which returns an instance of the class that the Class class really refers to? For example, declare that new Dog().getClass().newInstance() and direct new Dog() are equivalent.

public abstract class PetCreator {
    private Random rand = new Random(47);

    // The List of the different getTypes of Pet to create:
    public abstract List<Class<? extends Pet>> getTypes();

    public Pet randomPet() {
        // Create one random Pet
        int n = rand.nextInt(getTypes().size());

        try {
            return getTypes().get(n).newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public Pet[] createArray(int size) {
        Pet[] result = new Pet[size];

        for (int i = 0; i < size; i++) {
           result[i] = randomPet();
        }
        return result;
    }

    public ArrayList<Pet> arrayList(int size) {
        ArrayList<Pet> result = new ArrayList<Pet>();
        Collections.addAll(result, createArray(size));
        return result;
    }
}

Next let's implement the abstract class above and explain the following code. In the following code, we declare two collection classes, allTypes and types, in which allTypes contains all the classes we declared above, but we only have two specific types, Mutt and Egypian Mau, so the pets we really need to come out of are just types. The types included, and then we can get all the types contained in the types by calling getTypes().

public class LiteralPetCreator extends PetCreator {
    @SuppressWarnings("unchecked")
    public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList(
        Arrays.asList(Pet.class, Dog.class, Cat.class, Mutt.class, EgyptianMau.class));

    private static final List<Class<? extends Pet>> types = allTypes.subList(
        allTypes.indexOf(Mutt.class), allTypes.size());

    public List<Class<? extends Pet>> getTypes() {
        return types;
    }
}

The overall logic is complete, and finally we implement the TypeCounter class to count the number of related Pet classes in the collection. Explain the isAssignalbeFrom() method, which can determine whether a reflection class is a subclass or an indirect subclass of a reflection class. getSuperclass() as its name implies, is the parent of a reflection class.

public class TypeCounter extends HashMap<Class<?>, Integer> {
    private Class<?> baseType;

    public TypeCounter(Class<?> baseType) {
        this.baseType = baseType;
    }

    public void count(Object obj) {
        Class<?> type = obj.getClass();
        if (!baseType.isAssignableFrom(type)) {
            throw new RuntimeException(
                obj + " incorrect type " + type + ", should be type or subtype of " + baseType);
        }
        countClass(type);
    }

    private void countClass(Class<?> type) {
        Integer quantity = get(type);
        put(type, quantity == null ? 1 : quantity + 1);
        Class<?> superClass = type.getSuperclass();
        if (superClass != null && baseType.isAssignableFrom(superClass)) {
            countClass(superClass);
        }
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder("{");

        for (Map.Entry<Class<?>, Integer> pair : entrySet()) {
            result.append(pair.getKey().getSimpleName());
            result.append("=");
            result.append(pair.getValue());
            result.append(", ");
        }

        result.delete(result.length() - 2, result.length());
        result.append("} ");
        return result.toString();
    }
}

References

THINKING IN JAVA

Topics: Java Spring Programming Mybatis