API отражения Java был в основном усовершенствован в Java SE 1.7.Но эта статья была написана с использованием Oracle JDK11., так как JDK11 также загрузил исходный код в пакете sun, вы можете просматривать соответствующий исходный код и выполнять отладку непосредственно через IDE.
помещение
В предыдущей статье были представлены основные принципы вызовов отражения. На самом деле, для большинства пользователей Java на практике речь идет скорее о том, как повысить производительность вызовов отражения. В этой статье в основном предлагается несколько возможных решений. Кроме того, из-за наиболее частой операции отражения во время вызова метода мы сосредоточимся на оптимизации вызова отражения метода.
Способ 1: выберите правильный API
Выбор подходящего API в основном заключается в том, чтобы избежать использования методов обхода при получении метаданных, связанных с отражением, таких как:
- Получить экземпляр поля: старайтесь избегать частого использования
Class#getDeclaredFields()илиClass#getFields(), который следует вызывать непосредственно на основе имени поляClass#getDeclaredField()илиClass#getField(). - Получить экземпляр метода: старайтесь избегать частого использования
Class#getDeclaredMethods()илиClass#getMethods(), который следует вызывать на основе имени метода и массива типов параметровClass#getDeclaredMethod()илиClass#getMethod(). - Получите экземпляр Constructor: старайтесь избегать частого использования
Class#getDeclaredConstructors()илиClass#getConstructors(), который должен вызываться в соответствии с массивом типов параметров ConstructorClass#getDeclaredConstructor()илиClass#getConstructor().
На самом деле идея очень проста: если мы не хотим получить все Поле, Метод или Конструктор Класса, нам следует избегать использования API, возвращающего коллекцию или массив, что может уменьшить потерю производительности, вызванную обходом или оценкой.
Способ 2. Кэширование метаданных, связанных с операциями отражения
Причина использования механизма кэширования для кэширования метаданных, связанных с операцией отражения, заключается в том, что получение в реальном времени метаданных, связанных с операцией отражения, требует много времени.Вот несколько относительно трудоемких сценариев:
- Получить экземпляр класса:
Class#forName(), этот метод может просматривать исходный код, и время, отнимающее гораздо больше времени, чем другие методы. - Получить экземпляр поля:
Class#getDeclaredField(),Class#getDeclaredFields(),Class#getField(),Class#getFields(). - Получите экземпляр метода:
Class#getDeclaredMethod(),Class#getDeclaredMethods(),Class#getMethod(),Class#getMethods(). - Получите экземпляр конструктора:
Class#getDeclaredConstructor(),Class#getDeclaredConstructors(),Class#getConstructor(),Class#getConstructors().
Вот простой пример, в котором необходимо вызвать методы Setter и Getter общего JavaBean путем отражения:
// 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;
}
}
Короче говоря, операцию анализа метаданных отражения для кэширования лучше всего размещать в статическом блоке кода или при ее первом вызове (то есть отложенной загрузке), что позволяет избежать необходимости перезагрузки метаданных, связанных с отражением, когда она на самом деле звонил.
Способ 3: операция отражения преобразуется в прямой вызов
"Преобразование операций отражения в прямые вызовы" не является библиотекой классов, которая вообще не зависит от отражения. Метод здесь заключается в том, чтобы поместить метаданные, связанные с операциями отражения, непосредственно в переменные-члены класса, чтобы сохранить необходимость для чтения операций, связанных с отражением, из кеша.Потребление метаданных и так называемый «прямой вызов» обычно достигается за счет наследования или реализации интерфейса. Некоторые высокопроизводительные библиотеки отражения также используют некоторые инновационные методы: например, использование свойств элементов для кэширования метаданных, связанных с отражением, и вызовы методов индексирования по номеру [Число->Метод] или классы индексирования (например,CGLIBизFastClass),Этот подход будет иметь определенное улучшение производительности, когда есть несколько родительских классов или методов интерфейса, но на самом деле оценка производительности должна анализировать результаты конкретных сценариев посредством тестирования и не может использоваться вслепую., библиотека классов, использующая эту идею, имеетCGLIB,ReflectASMЖдать. Наиболее типичной реализацией «преобразования операций отражения в прямые вызовы» является динамический прокси 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-компилятора и других методов, не связанных с кодированием.
резюме
В этой статье в основном анализируются некоторые возможные опыты или решения для оптимизации производительности операций отражения на уровне кодирования.В зависимости от сценария использования могут быть и другие лучшие решения по оптимизации.
личный блог
(Конец этой статьи e-a-20181216 c-2-d)
Технический публичный аккаунт ("Throwable Digest"), который время от времени выкладывает оригинальные технические статьи автора (никогда не занимайтесь плагиатом и не перепечатывайте):