深刻分析Java反射(八)-優化反射調用性能

Java反射的API在JavaSE1.7的時候已經基本完善,可是本文編寫的時候使用的是Oracle JDK11,由於JDK11對於sun包下的源碼也上傳了,能夠直接經過IDE查看對應的源碼和進行Debug。java

前一篇文章已經介紹了反射調用的底層原理,其實在實際中對大多數Java使用者來講更關係的是如何提高反射調用的性能,本文主要提供幾個可行的方案。另外,因爲方法調用時頻率最高的反射操做,會着重介紹方法的反射調用優化。數組

方法一:選擇合適的API

選擇合適的API主要是在獲取反射相關元數據的時候儘可能避免使用遍歷的方法,例如:緩存

  • 獲取Field實例:儘可能避免頻繁使用Class#getDeclaredFields()或者Class#getFields(),應該根據Field的名稱直接調用Class#getDeclaredField()或者Class#getField()
  • 獲取Method實例:儘可能避免頻繁使用Class#getDeclaredMethods()或者Class#getMethods(),應該根據Method的名稱和參數類型數組調用Class#getDeclaredMethod()或者Class#getMethod()
  • 獲取Constructor實例:儘可能避免頻繁使用Class#getDeclaredConstructors()或者Class#getConstructors(),應該根據Constructor參數類型數組調用Class#getDeclaredConstructor()或者Class#getConstructor()

其實思路很簡單,除非咱們想要獲取Class的全部Field、Method或者Constructor,不然應該避免使用返回一個集合或者數組的API,這樣子能減小遍歷或者判斷帶來的性能損耗。性能優化

方法二:緩存反射操做相關元數據

使用緩存機制緩存反射操做相關元數據的緣由是由於反射操做相關元數據的實時獲取是比較耗時的,這裏列舉幾個相對耗時的場景:ide

  • 獲取Class實例:Class#forName(),此方法能夠查看源碼,耗時相對其餘方法高得多。
  • 獲取Field實例:Class#getDeclaredField()Class#getDeclaredFields()Class#getField()Class#getFields()
  • 獲取Method實例:Class#getDeclaredMethod()Class#getDeclaredMethods()Class#getMethod()Class#getMethods()
  • 獲取Constructor實例:Class#getDeclaredConstructor()Class#getDeclaredConstructors()Class#getConstructor()Class#getConstructors()

這裏舉個簡單的例子,須要反射調用一個普通JavaBean的Setter和Getter方法:性能

// JavaBean
@Data
public class JavaBean {

    private String name;
}

public class Main {

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

	// 解析的時候儘可能放在<cinit>裏面
	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 = new ReflectionMetadata();
				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 new IllegalStateException(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 new IllegalStateException(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 new IllegalStateException(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)) {
				return metadata.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)) {
				return metadata.getReadMethod().invoke(target);
			}
		}
		throw new IllegalStateException();
	}

	@Data
	private static class ReflectionMetadata {

		private Class<?> targetClass;
		private Field field;
		private Method readMethod;
		private Method writeMethod;
	}
}
複製代碼

簡單來講,解析反射元數據進行緩存的操做最好放在靜態代碼塊或者首次調用的時候(也就是懶加載),這樣可以避免真正調用的時候老是須要從新加載一次反射相關元數據。測試

方法三:反射操做轉變爲直接調用

"反射操做轉變爲直接調用"並非徹底不依賴於反射的類庫,這裏的作法是把反射操做相關元數據直接放置在類的成員變量中,這樣就能省去從緩存中讀取反射相關元數據的消耗,而所謂"直接調用"通常是經過繼承或者實現接口實現。有一些高性能的反射類庫也會使用一些創新的方法:例如使用成員屬性緩存反射相關元數據,而且把方法調用經過數字創建索引[Number->Method]或者創建索引類(像CGLIBFastClass),這種作法在父類或者接口方法比較少的時候會有必定的性能提高,可是實際上性能評估須要從具體的場景經過測試分析結果而不能盲目使用,使用這個思想的類庫有CGLIBReflectASM等。"反射操做轉變爲直接調用"的最典型的實現就是JDK的動態代理,這裏翻出以前動態代理那篇文章的例子來講:優化

// 接口
public interface Simple {

    void sayHello(String name);
}
// 接口實現
public class DefaultSimple implements Simple {

    @Override
    public void sayHello(String name) {
        System.out.println(String.format("%s say hello!", name));
    }
}
// 場景類
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");
    }
}

// 代理類
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 new UndeclaredThrowableException(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 new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(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 new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製代碼

這樣作的話Simple接口實例雖然最終是經過反射調用sayHello(String var1)方法,可是相關元數據在靜態代碼塊中建立而且已經緩存在類成員屬性中,那麼反射調用方法的性能已經優化到極致,剩下的都只是Native方法的耗時,這一點使用者在編碼層面已經沒有辦法優化,只能經過升級JVM(JDK)、使用JIT編譯器等非編碼層面的手段提高反射性能。this

小結

本文主要從編碼層面分析反射操做一些性能優化的可行經驗或者方案,或許有其餘更好的優化方案,具體仍是須要看使用場景。編碼

我的博客

(本文完 e-a-20181216 c-2-d)

相關文章
相關標籤/搜索