Java-反射性能优化和工具包ReflectASM

Java反射动态的获取到对象的信息以及灵活的调用对象方法等,但是听说效率很慢,测试下。

普通创建对象对比反射创建对象

public class TestUser {
    private Integer id;
    private String name;

    public String sayHi(){ return "hi";}

    public Integer getId() { return id;}
    public void setId(Integer id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
}

public class TestDemo {
    public static void main(String[] argv){
        testCommon();
        testReflexNoCache();
    }

    public void testCommon(){
        long start = System.currentTimeMillis();
        TestUser user = null;
        int i = 0;
        while(i<1000000){
            ++i;
            user = new TestUser();
        }
        long end = System.currentTimeMillis();
        System.out.println("普通对象创建耗时:"+(end - start ) + "ms");
    }
    
    // 通过反射方式创建TestUser对象
    public void testReflexNoCache() throws Exception {
        long start = System.currentTimeMillis();
        TestUser user = null;
        int i = 0;
        while(i<1000000){
            ++i;
            user = (TestUser) Class.forName("ReflexDemo.TestUser").newInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("无缓存反射创建对象耗时:"+(end - start ) + "ms");
    }
}

输出:
普通对象创建耗时:10ms
无缓存反射创建对象耗时:966ms

创建100W个对象的情况下,反射居然慢了90倍左右!

通过缓存反射方式创建对象

public void testReflexWithCache() throws Exception {
        long start = System.currentTimeMillis();
        TestUser user = null;
        Class rUserClass = Class.forName("RefleDemo.TestUser");
        int i = 0;
        while(i<1000000){
            ++i;
            user = (TestUser) rUserClass.newInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("通过缓存反射创建对象耗时:"+(end - start ) + "ms");
    }

输出:
通过缓存反射创建对象耗时:41ms

通过代码我们可以发现,是Class.forName这个方法比较耗时它实际上调用了一个本地方法,通过这个方法来要求JVM查找并加载指定的类

所以我们在项目中使用的时候,可以把Class.forName返回的Class对象缓存起来,下一次使用的时候直接从缓存里面获取,这样就极大的提高了获取Class的效率。同理,在我们获取Constructor、Method等对象的时候也可以缓存起来使用,避免每次使用时再来耗费时间创建。

为什么Java反射性能慢效率低
程序运行期的即时编译器(JIT 编译器,Just In Time Compiler)把字节码文件编译成机器码的过程;其中即时编译器(JIT)在运行期的优化过程对于程序运行来说更重要,Java虚拟机在编译阶段的代码优化就在这里进行,由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能要比非反射操作慢,因此应该避免在对性能敏感的应用程序中频繁使用Java反射来创建对象。

反射调用方法

public void testReflexMethod() throws Exception {
        long start = System.currentTimeMillis();
        Class testUserClass = Class.forName("RefleDemo.TestUser");
        TestUser testUser = (TestUser) testUserClass.newInstance();
        Method method = testUserClass.getMethod("sayHi");
        int i = 0;
        while(i<100000000){
            ++i;
            method.invoke(testUser);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射调用方法耗时:"+(end - start ) + "ms");
    }

输出:
反射调用方法耗时:330ms

通过setAccessible(true)的方式可以关闭安全检查

public void testReflexMethod() throws Exception {
        long start = System.currentTimeMillis();
        Class testUserClass = Class.forName("RefleDemo.TestUser");
        TestUser testUser = (TestUser) testUserClass.newInstance();
        Method method = testUserClass.getMethod("sayHi");
        int i = 0;
        while(i<100000000){
            ++i;
            method.setAccessible(true);
            method.invoke(testUser);
        }
        long end = System.currentTimeMillis();
        System.out.println("setAccessible=true 反射调用方法耗时:"+(end - start ) + "ms");
    }

输出:
setAccessible=true 反射调用方法耗时:188ms

查看API可以了解到,jdk在设置获取字段,调用方法的时候会执行安全访问检查,而此类操作会比较耗时,所以通过setAccessible(true)的方式可以关闭安全检查,从而提升反射效率

高性能反射工具包ReflectASM

ReflectASM通过字节码生成的方式实现了更为高效的反射机制。执行时会生成一个存取类来 set/get 字段,访问方法或创建实例。

依赖:

<!--java 反射工具包 -->
<!-- https://mvnrepository.com/artifact/com.esotericsoftware/reflectasm -->
<dependency>
   <groupId>com.esotericsoftware</groupId>
   <artifactId>reflectasm</artifactId>
   <version>1.11.3</version>
</dependency>

asm框架包依赖:

<dependency>
   <groupId>org.ow2.asm</groupId>
   <artifactId>asm</artifactId>
   <version>9.2</version>
</dependency>

ConstructorAccess反射来调用构造方法

  • public static ConstructorAccess get(Class type):生成字节码的方式创建 ConstructorAccess
    • public abstract T newInstance():创建实例。

创建测试类User:

class User {
    public int age;
    public String name;

    public User() {}

    public User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" + "age=" + age + ", name='" + name + '\'' + '}';
    }
    
    public void update(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

ReflectAsm反射来调用构造方法

public class ReflectAsmDemo {
    public static void main(String[] args) throws Exception {
        testConstructorAccess();
    }

    /**
     * ReflectAsm反射来调用构造方法
     */
    public static void testConstructorAccess() {
        ConstructorAccess<User> constructorAccess = ConstructorAccess.get(User.class);

        User user = constructorAccess.newInstance();
        System.out.println(user);
    }
}

输出:
User{age=0, name='null'}

FieldAccess反射获取属性

  • public static FieldAccess get(Class type):生成字节码的方式创建 FieldAccess
    • public void set(Object instance, String fieldName, Object value):设置fieldName属性值。
    • public Object get(Object instance, String fieldName):获取fieldName属性值。
    • public int getFieldCount():获取字段总个数。
    • public String[] getFieldNames():获取字段名称数组。
    • public int getIndex(String fieldName):获取指定字段index。
public class ReflectAsmDemo {
    public static void main(String[] args) throws Exception {
        testFieldAccess();
    }

    /**
     * ReflectAsm反射来set/get字段值
     */
    public static void testFieldAccess() {
        FieldAccess fieldAccess = FieldAccess.get(User.class);


        String[] fieldNames = fieldAccess.getFieldNames();
        for (String fieldName : fieldNames) {
            System.out.println("fieldName=" + fieldName);
        }

        System.out.println("FieldCount=" + fieldAccess.getFieldCount());
        System.out.println("index=" + fieldAccess.getIndex("name"));

        User target = new User(28,"lili");
        fieldAccess.set(target, "age", 1);
        int age = (Integer)fieldAccess.get(target, "age");
        System.out.println(age);
    }
}

输出:
fieldName=age
fieldName=name
FieldCount=2
index=1
1

FieldAccess访问私有字段,会报错Unable to find non-private field: …。
如果想访问私有字段,可以使用反射功能先放开权限。
Field field = Animal.class.getDeclaredField(“id”);
field.setAccessible(true);

MethodAccess反射调用方法

  • public static MethodAccess get(Class type):生成字节码的方式创建 MethodAccess
    • public String[] getMethodNames():获取方法名称数组。
    • public Object invoke(Object object, String methodName, Object… args):用方法名定位反射方法
    • public int getIndex(String methodName, Class… paramTypes):用方法和字段的索引定位反射方法,性能高
    • public abstract Object invoke(Object var1, int index, Object… var3):index是上面函数获取的索引index。
public class ReflectAsmDemo {
    public static void main(String[] args) throws Exception {
        testMethodAccess();
        testMethodIndexAccess();
    }

    /**
     * ReflectAsm反射调用方法
     * 用名称定位反射方法
     */
    public static void testMethodAccess() {
        User target = new User();
        MethodAccess methodAccess = MethodAccess.get(User.class);

        String[] methodNames = methodAccess.getMethodNames();
        for (String methodName : methodNames) {
            System.out.println("methodName=" + methodName);
        }

        methodAccess.invoke(target, "update", 1, "jack");
        System.out.println(target);
    }

    /**
     * ReflectAsm反射调用方法
     * 用方法和字段的索引定位反射方法,性能高
     */
    public static void testMethodIndexAccess() {
        User target = new User();
        MethodAccess methodAccess = MethodAccess.get(User.class);
        int index = methodAccess.getIndex("update", int.class, String.class);

        methodAccess.invoke(target, index, 1, "jack");
        System.out.println(target);
    }
}

输出:
methodName=toString
methodName=update
User{age=1, name='jack'}
User{age=1, name='jack'}

ReflectASM执行效率比对

public class ReflectASMDemo {
    public static void main(String[] args) throws Exception {
        ReflectASMDemo test = new ReflectASMDemo();
        test.testJdkReflect();
        test.testReflectAsmName();
        test.testReflectAsmIndex();
    }

    /**
     * JDK反射调用方法
     * @throws Exception
     */
    public void testJdkReflect() throws Exception {
        User target = new User();
        long start = System.currentTimeMillis();
        Method method = target.getClass().getMethod("update", int.class, String.class);
        for (int i = 0; i < 100000000; i++) {
            method.invoke(target, 1, "jack");
        }
        long end = System.currentTimeMillis();
        System.out.println("timeout=" + (end - start));//418 450 426 430
    }

    /**
     * ReflectAsm反射调用方法
     * 用名称定位反射方法
     */
    public void testReflectAsmName() {
        User target = new User();
        MethodAccess access = MethodAccess.get(User.class);//生成字节码的方式创建UserMethodAccess
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            access.invoke(target, "update", 1, "jack");
        }
        long end = System.currentTimeMillis();
        System.out.println("timeout=" + (end - start));//88 64 66 50
    }

    /**
     * ReflectAsm反射调用方法
     * 用方法和字段的索引定位反射方法,性能高
     */
    public void testReflectAsmIndex() {
        User target = new User();
        MethodAccess access = MethodAccess.get(User.class);
        int index = access.getIndex("update", int.class, String.class);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            access.invoke(target, index, 1, "jack");
        }
        long end = System.currentTimeMillis();
        System.out.println("timeout=" + (end - start));//14 12 11 13
    }

    /**
     * ReflectAsm反射来set/get字段值
     */
    public void testFieldAccess() {
        User target = new User();
        FieldAccess fieldAccess = FieldAccess.get(target.getClass());
        fieldAccess.set(target, "age", 1);
        int age = (Integer)fieldAccess.get(target, "age");
        System.out.println(age);
    }

    /**
     * ReflectAsm反射来调用构造方法
     */
    public void testConstructorAccess() {
        ConstructorAccess<User> constructorAccess = ConstructorAccess.get(User.class);
        User userService = constructorAccess.newInstance();
        System.out.println(userService);
    }

    /**
     * 查找方法的索引
     */
    public void testIndex() {
        User target = new User();
        MethodAccess methodAccess = MethodAccess.get(target.getClass());
        int index = methodAccess.getIndex("update", int.class, String.class);
        System.out.println(index);
    }
}

class User {
    public int age;
    public String name;

    public void update(int age, String name) {

    }
}

结果发现,reflectASM效率明显高于Java反射获取,并且reflectASM提供通过方法名定位索引,然后通过索引调用方法,进一步提升方法调用效率。

通过reflectASM实现对象拷贝

public class BeanUtil {
    /**
     *  大小写可以忽略
     * 下划线 _ 被忽略
     * NULL值和空字符串不会覆盖新值
     *
     * @param source
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T copyPropertiesIgnoreCase(Object source, Object target) {
        Map<String, Field> sourceMap = CacheFieldMap.getFieldMap(source.getClass());
        CacheFieldMap.getFieldMap(target.getClass()).values().forEach((it) -> {
            Field field = sourceMap.get(it.getName().toLowerCase().replace("_", ""));
            if (field != null) {
                it.setAccessible(true);
                field.setAccessible(true);
                try {
                    //忽略null和空字符串
                    if(field.get(source)!=null)
                        it.set(target, field.get(source));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println(target.toString());
        return (T) target;
    }

    private static class CacheFieldMap {
        private static Map<String, Map<String, Field>> cacheMap = new HashMap<>();

        private static Map<String, Field> getFieldMap(Class clazz) {
            Map<String, Field> result = cacheMap.get(clazz.getName());
            if (result == null) {
                synchronized (CacheFieldMap.class) {
                    if (result == null) {
                        Map<String, Field> fieldMap = new HashMap<>();
                        for (Field field : clazz.getDeclaredFields()) {
                            fieldMap.put(field.getName().toLowerCase().replace("_", ""), field);
                        }
                        cacheMap.put(clazz.getName(), fieldMap);
                        result = cacheMap.get(clazz.getName());
                    }
                }
            }
            return result;
        }
    }
}

MapToObjectIgnoreCaseUtil:

package 对象拷贝;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapToObjectIgnoreCaseUtil {

    public static void main(String[] args) throws Exception {
        Map<Object, Object> map = new HashMap<>();

        map.put("fc_id", "TK");
        map.put("fc_merch_id", "123456");
        map.put("fc_merch_name", "中金");

        MerchInfo merchInfo = new MerchInfo();
        System.out.println(merchInfo);

        merchInfo = (MerchInfo) MapToObjectIgnoreCaseUtil.mapToObject(map, merchInfo.getClass());
        System.out.println(merchInfo);

    }
    public static Object mapToObject(Map<Object, Object> map, Class<?> beanClass) throws Exception {
        if (map == null)
            return null;

        Map<Object, Object> map2 = new HashMap<>();
        Set<Object> keySet = map.keySet();
        for(Object key : keySet){
            map2.put(key.toString().toLowerCase().replace("_", ""), map.get(key));
        }
        Object obj = beanClass.newInstance();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            int mod = field.getModifiers();
            if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                continue;
            }
            field.setAccessible(true);
            if (map2.containsKey(field.getName().toLowerCase().replace("_", ""))) {
                field.set(obj, map2.get(field.getName().toLowerCase().replace("_", "")));
            }
        }
        return obj;
    }
}

class MerchInfo{
    public String fcID;
    public String fcMerchId;
    public String fcMerchName;

    public String getFcID() {
        return fcID;
    }

    public void setFcID(String fcID) {
        this.fcID = fcID;
    }

    public String getFcMerchId() {
        return fcMerchId;
    }

    public void setFcMerchId(String fcMerchId) {
        this.fcMerchId = fcMerchId;
    }

    public String getFcMerchName() {
        return fcMerchName;
    }

    public void setFcMerchName(String fcMerchName) {
        this.fcMerchName = fcMerchName;
    }

    @Override
    public String toString() {
        return "MerchInfo{" +
                "fcID='" + fcID + '\'' +
                ", fcMerchId='" + fcMerchId + '\'' +
                ", fcMerchName='" + fcMerchName + '\'' +
                '}';
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值