深入分析Java反射(八)-优化反射调用性能
Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。
前一篇文章已经介绍了反射调用的底层原理,其实在实际中对大多数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,这样子能减少遍历或者判断带来的性能损耗。
方法二:缓存反射操作相关元数据
使用缓存机制缓存反射操作相关元数据的原因是因为反射操作相关元数据的实时获取是比较耗时的,这里列举几个相对耗时的场景:
- 获取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]或者建立索引类(像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)
深入分析Java反射(八)-优化反射调用性能的更多相关文章
- 深入分析Java反射(六)-反射调用异常处理
前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...
- 深入分析Java反射(一)-核心类库和方法
前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...
- java与c#的反射性能比较
java与c#都支持反射,但是从网络上搜索两大阵营对于反射的态度,基本上.net开发人员都建议慎用反射,因为会有性能开销:反到是java阵营里好象在大量肆无忌惮的使用反射.于是写了下面的测试代码: c ...
- 利用表达式树Expression优化反射性能
最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下. Excel导入是相对比较麻烦的一块, ...
- 深入分析Java反射(五)-类实例化和类加载
前提 其实在前面写过的<深入分析Java反射(一)-核心类库和方法>已经介绍过通过类名或者java.lang.Class实例去实例化一个对象,在<浅析Java中的资源加载>中也 ...
- 深入分析Java反射(四)-动态代理
动态代理的简介 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分 ...
- 反射工具类.提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class,被AOP过的真实类等工具函数.java
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.ap ...
- Java反射学习-4 - 反射调用方法
反射调用方法: package cn.tx.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Method ...
- 深入分析Java反射(二)-数组和枚举
前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...
随机推荐
- Kafka--Kafka简述
Kafka的诞生 Kafka最初是LinkedIn的一个内部基础设施系统.我们发现,虽然有很多数据库和系统可以用来存储数据,但在我们的架构里,刚好缺一个可以帮助处理持续数据流的组件. 我们希望能够把数 ...
- MySql索引原理分析
面试 问:数据库中最常见的慢查询优化方式是什么? 同学A:加索引. 问:为什么加索引能优化慢查询?同学A:...不知道同学B:因为索引其实就是一种优化查询的数据结构,比如Mysql中的索引是用B+树实 ...
- bzoj 2281: [Sdoi2011]黑白棋
再次,,,,,虚(一开始看错题了,看成一次移动一个棋子,能移动1-d个格子...这样的话有没有大神会做??本蒟蒻就教) 额,,直接%%%%把...http://hzwer.com/5760.html ...
- Flask的请求钩子与上下文简览
请求钩子(Hook) 在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:在请求开始时,建立数据库连接:在请求结束时,指定数据的交互格式.为了让>每个视图函数避免编写重复功能的代 ...
- 开源DDD设计模式框架YMNNetCoreFrameWork第四篇-增加YMNSession,增加异常处理,增加策略授权
1.增加YMNSession,可以获取当前登录的用户信息 2.增加异常处理,不要使用过滤器,过滤器只能获取到mvc异常 3.增加策略授权,策略授权支持用户角色.用户名授权,初步实现 代码地址:http ...
- 四十一、在SAP中添加多条件选择框
一.代码如下: 二.其中我们的文本替换内容如下 三.需要注意的是波浪线的用法,以及区域的添加方法.运行程序,显示如下 四.不勾选时,查询出来是去掉国际的 五.勾选之后,查询的是全部的 六.显示如下 七 ...
- 001、MySQL查询服务器版本号和当前日期
SELECT VERSION(), CURRENT_DATE; 不忘初心,如果您认为这篇文章有价值,认同作者的付出,可以微信二维码打赏任意金额给作者(微信号:382477247)哦,谢谢.
- 三、ReactJS、jsx、 Component 特性
reactjs特性: 基于组件(Component)化思考 用 JSX 进行声明式(Declarative)UI 设计 使用 Virtual DOM Component PropType 错误校对机制 ...
- javascript 对象只读
var person = {}; Object.defineProperty(person, "name", { writable: false, value: "nic ...
- 20180122 PyTorch学习资料汇总
PyTorch发布一年团队总结:https://zhuanlan.zhihu.com/p/33131356?gw=1&utm_source=qq&utm_medium=social 官 ...