Java reflection API in JavaSE1.7 has been basically perfect, but this article is written using Oracle JDK11, because JDK11 for the source code under the Sun package also uploaded, you can directly view the corresponding source code and Debug through IDE.

The premise

The previous article covered the underlying principles of reflection invocation, but in practice it is more important for most Java users to improve the performance of reflection invocation, and this article provides several possible solutions. In addition, reflection call optimization of methods will be emphasized because of the most frequent reflection operations during method calls.

Method 1: Choose the right API

The main reason for choosing the appropriate API is to avoid using traversal methods when retrieving reflection related metadata, such as:

  • Get Field instances: Try to avoid frequent useClass#getDeclaredFields()orClass#getFields(), should be called directly from the name of FieldClass#getDeclaredField()orClass#getField().
  • Get Method instances: Avoid frequent useClass#getDeclaredMethods()orClass#getMethods()Should be called from an array of Method names and parameter typesClass#getDeclaredMethod()orClass#getMethod().
  • Get the Constructor instance: Avoid frequent useClass#getDeclaredConstructors()orClass#getConstructors()Should be called from the Constructor parameter type arrayClass#getDeclaredConstructor()orClass#getConstructor().

The idea is simple: unless we want to retrieve all fields, methods, or Constructor from a Class, we should avoid apis that return a collection or array to reduce the performance cost of traversal or judgment.

Method 2: Cache metadata related to reflection operations

The reason why the cache mechanism is used to cache the metadata related to reflection operation is that it takes time to obtain the metadata related to reflection operation in real time. Here are some time-consuming scenarios:

  • Get Class instance:Class#forName(), this method can view the source code, time-consuming compared to other methods much higher.
  • Get Field instance:Class#getDeclaredField(),Class#getDeclaredFields(),Class#getField(),Class#getFields().
  • Get Method instance:Class#getDeclaredMethod(),Class#getDeclaredMethods(),Class#getMethod(),Class#getMethods().
  • Get the Constructor instance:Class#getDeclaredConstructor(),Class#getDeclaredConstructors(),Class#getConstructor(),Class#getConstructors().

Here’s a simple example that requires reflection to call Setter and Getter methods of a normal JavaBean:

// JavaBean
@Data
public class JavaBean {

    private String name;
}

public class Main {

	private static finalMap<Class<? >, List<ReflectionMetadata>> METADATA =new HashMap<>();
	private static finalMap<String, Class<? >> CLASSES =new HashMap<>();

	// try to place it in 
      
        when parsing
      
	static{ Class<? > clazz = JavaBean.class; CLASSES.put(clazz.getName(), clazz); List<ReflectionMetadata> metadataList =new ArrayList<>();
		METADATA.put(clazz, metadataList);
		try {
			for (Field f : clazz.getDeclaredFields()) {
				ReflectionMetadata metadata = newReflectionMetadata(); metadataList.add(metadata); metadata.setTargetClass(clazz); metadata.setField(f); String name = f.getName(); Class<? > type = f.getType(); metadata.setReadMethod(clazz.getDeclaredMethod(String.format("get%s%s", Character.toUpperCase(name.charAt(0)), name.substring(1))));
				metadata.setWriteMethod(clazz.getDeclaredMethod(String.format("set%s%s", Character.toUpperCase(name.charAt(0)), name.substring(1)), type)); }}catch (Exception e) {
			throw newIllegalStateException(e); }}public static void main(String[] args) throws Exception {
		String fieldName = "name";
		Class<JavaBean> javaBeanClass = JavaBean.class;
		JavaBean javaBean = new JavaBean();
		invokeSetter(javaBeanClass, javaBean, fieldName , "Doge");
		System.out.println(invokeGetter(javaBeanClass,javaBean, fieldName));
		invokeSetter(javaBeanClass.getName(), javaBean, fieldName , "Throwable");
		System.out.println(invokeGetter(javaBeanClass.getName(),javaBean, fieldName));
	}

	private static void invokeSetter(String className, Object target, String fieldName, Object value) throws Exception {
		METADATA.get(CLASSES.get(className)).forEach(each -> {
			Field field = each.getField();
			if (field.getName().equals(fieldName)) {
				try {
					each.getWriteMethod().invoke(target, value);
				} catch (Exception e) {
					throw newIllegalStateException(e); }}}); }private static void invokeSetter(Class
        clazz, Object target, String fieldName, Object value) throws Exception {
		METADATA.get(clazz).forEach(each -> {
			Field field = each.getField();
			if (field.getName().equals(fieldName)) {
				try {
					each.getWriteMethod().invoke(target, value);
				} catch (Exception e) {
					throw newIllegalStateException(e); }}}); }private static Object invokeGetter(String className, Object target, String fieldName) throws Exception {
		for (ReflectionMetadata metadata : METADATA.get(CLASSES.get(className))) {
			if (metadata.getField().getName().equals(fieldName)) {
				returnmetadata.getReadMethod().invoke(target); }}throw new IllegalStateException();
	}

	private static Object invokeGetter(Class
        clazz, Object target, String fieldName) throws Exception {
		for (ReflectionMetadata metadata : METADATA.get(clazz)) {
			if (metadata.getField().getName().equals(fieldName)) {
				returnmetadata.getReadMethod().invoke(target); }}throw new IllegalStateException();
	}

	@Data
	private static class ReflectionMetadata {

		privateClass<? > targetClass;private Field field;
		private Method readMethod;
		privateMethod writeMethod; }}Copy the code

Simply put, parsing reflection metadata for caching is best done in a static code block or at the time of the first call (i.e., lazy loading) to avoid the need to reload reflection metadata every time the actual call is made.

Method three: Reflection operations become direct calls

“Reflection operations into direct call” is not completely does not depend on the reflection of the library, here is the reflection operation related metadata placed directly in the member variables of the class, so you can save from the cache read the consumption of reflection related metadata, and the so-called “direct call” is usually through inheritance or implementing an interface implementation. Some high-performance reflection libraries also use innovative approaches: For example, caching reflected metadata using member attributes and indexing Method calls by Number->Method or indexing classes (like CGLIB’s FastClass) can provide performance gains when there are fewer parent classes or interface methods. But in practice, performance evaluation needs to test and analyze the results in a specific scenario, rather than blindly use them. Libraries that use this idea include CGLIB, ReflectASM, and others. The most typical implementation of reflection operations into direct calls is the JDK dynamic proxy. Here is an example of dynamic proxy in the previous article:

/ / interface
public interface Simple {

    void sayHello(String name);
}
// Interface implementation
public class DefaultSimple implements Simple {

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

    public static void main(String[] args) throws Exception {
        Simple simple = new DefaultSimple();
        Object target = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Simple.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Before say hello...");
                method.invoke(simple, args);
                System.out.println("After say hello...");
                return null; }}); Simple proxy = (Simple) target; proxy.sayHello("throwable"); }}/ / the proxy class
public final class $Proxy0 extends Proxy implements Simple {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final void sayHello(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

In this case, the Simple interface instance eventually calls the sayHello(String var1) method through reflection, but the related metadata is created in the static code block and cached in the class member attribute, so the performance of the reflection call method has been optimized to the extreme, and the rest is just the time of Native method. There is no way for users to optimize this at the coding level, except by upgrading the JVM(JDK), using the JIT compiler and other non-coding methods to improve reflection performance.

summary

This paper mainly analyzes some feasible experience or schemes for performance optimization of reflection operation from the coding level. There may be other better schemes, depending on the application scenario.

Personal blog

  • Throwable’s Blog

(E-A-20181216 C-2-D)

Technical official account (Throwable Digest), push the author’s original technical articles from time to time (never plagiarize or reprint) :