反射用不好真的会影响代码执行效率!

科技   2025-01-19 08:12   北京  

前言

相信大家对反射都不陌生,在日常开始的过程中,我们也会经常用到反射来实现某些业务场景,并且很多框架Spring、Mybatis、JDBC等…底层都有靠反射来实现,先来谈谈优缺点;

优点

例如加载类的加载、方法的调用,可以很灵活的加载类,创建对象的功能性更加强大,调用类中属性和方法的时候,无视修饰符;通过配置可以无需修改代码,来实现对不同对象的加载。

缺点

使代码的复杂度上升、如果用不好会导致执行效率比正常创建对象的效率更低。

反射源码分析

反射代码主要存在java.lang.reflect包下,Class类主要方法有/forName、newInstance、getMethod、getDeclaredMethod

getMethod

看源码基本是分三步;1:checkMemberAccess方法鉴权 2:获取method 3: method方法的拷贝‘

getDeclaredMethod

这个方法和getMethod基本步骤是一样的,不一样的是getDeclaredMethod方法是加载私有的方法,getMethod是PUBLIC加载公共方法。

invoke

再来看invoke方法,也是分为三步 1.checkAccess鉴权检验 2.获取copy时使用的MethodAccessor,MethodAccessor 也有一个invoke方法,分别有三个实现类DelegatingMethodAccessorImpl、MethodAccessorImpl 、NativeMethodAccessorImpl,可见MethodAccessor也是靠反射来实现的。3.调用MethodAccessor的invoke方法实现方法的调用。以上就是对反射源码的简单分析,有兴趣的朋友去好好看看,这里只是做个简单介绍。

反射执行效率对比

下面看看几种创建对象,并且执行对象方法的效率对比。

package com.example.system.factory;
import com.example.system.domain.ReflectEntity;import lombok.SneakyThrows;
import java.lang.reflect.Field;import java.lang.reflect.Method;
public class ReflectTest {

private Long testReflectMethod() { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Thread thread = new Thread(new Runnable() { @SneakyThrows @Override public void run() { Class clazz = Class.forName("com.example.system.domain.ReflectEntity"); Object ReflectEntity = clazz.newInstance(); Method method = clazz.getDeclaredMethod("getReflectMethod"); method.invoke(ReflectEntity); } }); } return System.currentTimeMillis() - startTime;
}
private Long testReflectField() { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Thread thread = new Thread(new Runnable() { @SneakyThrows @Override public void run() {
ReflectEntity reflectEntity = new ReflectEntity("Lxlxxx", 20, "coding"); Field field = null; try { field = reflectEntity.getClass().getDeclaredField("name"); field.set(reflectEntity, "changeLxlxxx");

} catch (Exception e) { e.printStackTrace(); } } }); } return System.currentTimeMillis() - startTime; }
private Long testNormMethod() { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Thread thread = new Thread(new Runnable() { @SneakyThrows @Override public void run() { ReflectEntity reflectEntity = new ReflectEntity("Lxlxxx", 20, "coding"); reflectEntity.setName("changeLxlxxx"); } }); } return System.currentTimeMillis() - startTime; }
private Long testNormField() { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Thread thread = new Thread(new Runnable() { @SneakyThrows @Override public void run() { ReflectEntity reflectEntity = new ReflectEntity("Lxlxxx", 20, "coding"); reflectEntity.eName = "English"; } }); } return System.currentTimeMillis() - startTime; }
public static void main(String[] args) { ReflectTest reflectTest = new ReflectTest(); final Long aLong = reflectTest.testReflectMethod(); final Long aLong1 = reflectTest.testReflectField(); final Long aLong2 = reflectTest.testNormMethod(); final Long aLong3 = reflectTest.testNormField(); System.out.printf("testReflectMethod执行时间:" + aLong); System.out.printf("testReflectField执行时间:" + aLong1); System.out.printf("testNormMethod执行时间:" + aLong2); System.out.printf("testNormField执行时间:" + aLong3); }
}

测试结果

每个方法创建10000个线程,通过反射、非反射进行对象创建已经属性调用;

执行所需要的时间,从上到下发现反射的执行时候用的最长,效率也是最低的;

反射效率低原因分析

从上面反射调用的方法时间来看testReflectMethod

testReflectField,testReflectMethod方法里面执行了 Method.invoke方法,那我们就针对这个方法来具体分析下;

1.每次都要检查方法的可见性 
2.MethodAccessor.invoke方法每次都要检验参数 

3.Method.invoke方法对入参的封装与解封 这三点我认为是影响效率比较大的三点

解决反射效率低的问题

可以选择不用JDK的反射类,因为效率实在是太低了,可以使用hutool封装的反射工具类;

<dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-all</artifactId><hutool.version>5.3.5</hutool.version></dependency>
下面来看看使用hutool的ReflectUtil反射工具类的执行效率如何;
private Long testReflectUtil(){    long startTime = System.currentTimeMillis();    for (int i = 0; i < 10000; i++) {        Thread thread = new Thread(new Runnable() {            @SneakyThrows            @Override            public void run() {                ReflectEntity t = ReflectUtil.newInstance(ReflectEntity.class);                ReflectUtil.setFieldValue(t, "name","changeLxlxxx");                ReflectUtil.invoke(t, "getReflectMethod", "");            }        });    }    return System.currentTimeMillis() - startTime;}

优化后

通过调用ReflectUtil类之后,与其他方法的执行时间进行了对比,发现执行的效率确实很高,还不到JDK原生反射一半的时间;

总结

在我们的老项目中,我也是看到使用JDK的原生反射代码,才去好奇分析下它的执行效率,果不其然效率是非常低的,存在于老项目中也是见怪不怪,往往了解底层的逻辑,才能更加帮助我们更好对项目进行优化。


链接:https://juejin.cn/post/7238792624386588709

- END -

IT168与ITPUB技术社区强强联手,收集数百款主流数据库产品,重磅推出“数据库全景图”,旨在打造一款集知识普及、产品对比、选型参考于一体的综合性资源平台。“数据库全景图(11月版)”可扫描上方左侧二维码回复关键词获取,识别右侧二维码直达“数据库全景图”链接(右上角浏览器打开获取更好体验)。

ITPUB
ITPUB官方账户,分享社区技术干货内容,了解社区最新动态,参与社区精彩活动。
 最新文章