java应用反射的时候,性能往往是java程序员担心的地方,那么在大量运用反射的时候,性能的微弱提升,对这个系统而言都是如旱地逢甘霖。

 

下面用代码简单验证一下反射性能都损耗在哪里。

 

package littlehow.reflect.demo;

/**
 * WorkBean 作为反射性能测试的bean
 *
 * @author littlehow
 * @time 2016-06-13 14:27
 */
public class WorkBean {
    /**
     * 工作人名称
     */
    private String userName;
    /**
     * 公司名称
     */
    private String companyName;
    /**
     * 工作年限
     */
    private int workAge;
    /**
     * 薪资待遇
     */
    private double salary;

    /**
     * 无参构造
     */
    public WorkBean() {
        super();
    }

    /**
     * 姓名
     * @param userName
     */
    public WorkBean(String userName) {
        this.userName = userName;
    }

    /**
     * 姓名和公司名称的构造
     * @param userName
     * @param companyName
     */
    public WorkBean(String userName, String companyName) {
        this(userName);
        this.companyName = companyName;
    }

    /**
     * 工作
     */
    public void workAt(boolean flag) {
        System.out.println(this.userName+"在"+(flag?"上午":"下午")+"上午工作的时候一直在打瞌睡!");
    }

    /**
     * 工作
     * @param flag
     * @param message
     */
    public void workAt(boolean flag, String message) {
        System.out.println(this.userName+"在"+(flag?"上午":"下午")+message);
    }

    /**
     * 工作
     */
    public void workAt() {
        System.out.println(this.userName+"工作了好久了");
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public int getWorkAge() {
        return workAge;
    }

    public void setWorkAge(int workAge) {
        this.workAge = workAge;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return this.userName + "在" + this.companyName + "工作了" + this.workAge + "年,现在的薪水是" + this.salary;
    }
}

 

 

然后是测试反射的demo

 

package littlehow.reflect.demo;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ReflectDemo  反射性能消耗在哪里
 *
 * @author littlehow
 * @time 2016-06-13 14:41
 */
public class ReflectDemo {
    /**
     * 测试class
     */
    static Class<?> clazz = WorkBean.class;
    /**
     * 公用构造方法缓存
     */
    private static final HashMap<String, Constructor<?>> constructors = new HashMap<String, Constructor<?>>();
    /**
     * 公用method缓存
     */
    private static final ConcurrentHashMap<String, MethodCache> methods = new ConcurrentHashMap<String, MethodCache>();

    /**
     * 不经过优化的构造方法性能测试
     */
    @Test
    public void constructorNormalTest() {
        long start = System.currentTimeMillis();
        try {
            Object obj = null;
            for(int i = 0; i < 1000000; i ++) {
                Constructor<?> cons1 = clazz.getConstructor();
                obj = cons1.newInstance();
                Constructor<?> cons2 = clazz.getConstructor(String.class);
                obj = cons2.newInstance("littlehow");
                Constructor<?> cons3 = clazz.getConstructor(String.class, String.class);
                obj = cons3.newInstance("littlehow", "future company");
            }
            System.out.println(obj);//littlehow在future company工作了0年,现在的薪水是0.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均840毫秒左右
    }

    /**
     * 对单一的进行缓存,速度最快,但功用性不够好
     * 相对于
     * @see constructorNormalTest()方法,性能提升10倍
     * 可见反射中对构造方法的获取非一个非常耗时的操作
     */
    @Test
    public void constrctorBufferOne() {
        long start = System.currentTimeMillis();
        try {
            Constructor<?> cons1 = clazz.getConstructor();
            Constructor<?> cons2 = clazz.getConstructor(String.class);
            Constructor<?> cons3 = clazz.getConstructor(String.class, String.class);
            Object obj = null;
            for(int i = 0; i < 1000000; i ++) {
                obj = cons1.newInstance();
                obj = cons2.newInstance("littlehow");
                obj = cons3.newInstance("littlehow", "future company");
            }
            System.out.println(obj);//littlehow在future company工作了0年,现在的薪水是0.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均80毫秒左右
    }

    /**
     * key值的计算耗时也比较大,可以再进行优化
     * @param clazz
     * @param arguClass
     * @return
     * @throws NoSuchMethodException
     */
    private static Constructor<?> getConstrucor(Class<?> clazz, Class<?> ...arguClass) throws NoSuchMethodException {
        String key = clazz.getName();
        for (Class<?> argu : arguClass) {
            key = key + argu.getName();//这里会有编译优化
        }
        Constructor<?> constructor = constructors.get(key);
        if (constructor == null) {
            constructor = clazz.getConstructor(arguClass);
            constructors.put(key, constructor);
        }
        return constructor;
    }

    /**
     * 公用缓存,在性能和开发维护中这种处理,在性能要求不是超高的
     * 系统中可以选用该方式进行构造方法的缓存,提升开发速度
     * 效率相对第一个测试性能也提升了一倍多
     */
    @Test
    public void constructorBufferAll() {
        long start = System.currentTimeMillis();
        try {
            Constructor<?> cons1 = clazz.getConstructor();
            Constructor<?> cons2 = clazz.getConstructor(String.class);
            Constructor<?> cons3 = clazz.getConstructor(String.class, String.class);
            Object obj = null;
            for(int i = 0; i < 1000000; i ++) {
                obj = getConstrucor(clazz).newInstance();
                obj = getConstrucor(clazz, String.class).newInstance("littlehow");
                obj = getConstrucor(clazz, String.class, String.class).newInstance("littlehow", "future company");
            }
            System.out.println(obj);//littlehow在future company工作了0年,现在的薪水是0.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均330毫秒左右
    }

    /**
     * 用class直接newInstance
     * 直接用class的newInstance方式性能非常高,
     * 但是局限于newInstance只能调用无参构造方法;
     * 因为newInstance调用的是其内部的cacheConstructor
     * 所以实例化的时候非常快
     * 所以如果是无参构造方法的话,就不需要单独获取constructor,
     * 因为性能相差无几
     */
    @Test
    public void clazzNewInstance() {
        long start = System.currentTimeMillis();
        try {
            Object obj = null;
            for(int i = 0; i < 1000000; i ++) {
                obj = clazz.newInstance();
            }
            System.out.println(obj);//null在null工作了0年,现在的薪水是0.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均20毫秒左右
    }

    /**
     * 方法反射示例,方法每次都去反射中寻找
     */
    @Test
    public void methodNormalTest() {
        long start = System.currentTimeMillis();
        try {
            Object obj = getConstrucor(clazz, String.class, String.class).newInstance("littlehow", "old company");
            for (int i = 0; i < 1000000; i ++) {
                Method m = clazz.getDeclaredMethod("setWorkAge", int.class);
                m.invoke(obj, i);
                m = clazz.getDeclaredMethod("setSalary", double.class);
                m.invoke(obj, 3500.0);
            }
            System.out.println(obj);//littlehow在old company工作了999999年,现在的薪水是3500.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均850毫秒左右
    }

    /**
     * 单独缓存方法,大规模反射应用不易管理
     * 相对非缓存,性能提高了14倍
     */
    @Test
    public void methodBufferOne() {
        long start = System.currentTimeMillis();
        try {
            Method m1 = clazz.getDeclaredMethod("setWorkAge", int.class);
            Method m2 = clazz.getDeclaredMethod("setSalary", double.class);
            Object obj = getConstrucor(clazz, String.class, String.class).newInstance("littlehow", "old company");
            for (int i = 0; i < 1000000; i ++) {
                m1.invoke(obj, i);
                m2.invoke(obj, 3500.0);
            }
            System.out.println(obj);//littlehow在old company工作了999999年,现在的薪水是3500.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均56毫秒左右
    }

    /**
     * 获取方法,这里只是最初略的写了个获取的方法
     * @param clazz
     * @param argus
     * @return
     */
    private static Method getMethod(Class<?> clazz, String name, Class<?> ...argus) throws NoSuchMethodException {
        MethodCache mc = methods.get(clazz.getName());
        if (mc == null) {
            mc = new MethodCache(clazz);
            methods.put(clazz.getName(), mc);
        }
        return mc.getMethod(name, argus);
    }

    /**
     * 进一步缓存,牺牲内存提升速度,速度提升明显
     */
    static class MethodCache {
        private Class<?> clazz;
        /**
         * 缓存
         */
        private Map<String, Method> methods = new HashMap<String, Method>();
        public MethodCache(Class<?> clazz) {
            this.clazz = clazz;
            Method[] methodArr = clazz.getDeclaredMethods();
            List<String> remove = new ArrayList<String>();
            //私有的method不缓存,static方法不缓存
            for (Method m : methodArr) {
                if (!m.isAccessible() && ((1 & m.getModifiers()) > 0) && ((8 & m.getModifiers()) == 0)) {
                    Method old = methods.put(m.getName(), m);
                    //如果old不为空,表示方法多了重载,需要处理特殊处理
                    if (old != null) {
                        put(old);
                        put(m);
                        remove.add(m.getName());
                    }
                }
            }
            for (String s : remove) {
                methods.remove(s);
            }
        }

        /**
         * 按照参数进行put
         * @param method
         */
        private void put(Method method) {
            Type[] types = method.getParameterTypes();
            String key = method.getName() + types.length;
            for (Type type : types) {
                String str = type.toString();
                if (str.startsWith("class ")) {
                    str = str.substring(6);
                }
                key += str;
            }
            methods.put(key, method);
        }

        /**
         * 获取方法
         * @param name
         * @param argus
         * @return
         * @throws NoSuchMethodException
         */
        public Method getMethod(String name, Class<?> ...argus)  throws NoSuchMethodException{
            Method m = methods.get(name);
            if (m == null) {
                name = name + argus.length;
                for(Class<?> argu : argus) {
                    name = name + argu.getName();
                }
                m = methods.get(name);
            }
            return m;
        }
    }

    /**
     * 因为在大规模反射应用中,
     * @see methodBufferOne()方法并不容易扩展
     * 特别是自己写的动态代理进行aop切入时,
     * 对一些方法进行置换处理时,就需要有个管理的缓存
     */
    @Test
    public void methodBufferAll() {
        long start = System.currentTimeMillis();
        try {
            Object obj = getConstrucor(clazz, String.class, String.class).newInstance("littlehow", "old company");
            //测试是否能获取不同的重载方法
            getMethod(clazz, "workAt").invoke(obj);//littlehow工作了好久了
            getMethod(clazz, "workAt", boolean.class).invoke(obj, false);//littlehow在下午上午工作的时候一直在打瞌睡!
            getMethod(clazz, "workAt", boolean.class, String.class).invoke(obj, true, "一直努力解决问题");//littlehow在上午一直努力解决问题
            for (int i = 0; i < 1000000; i ++) {
                getMethod(clazz, "setWorkAge", int.class).invoke(obj, i);
                getMethod(clazz, "setSalary", double.class).invoke(obj, 3500.0);
            }
            System.out.println(obj);//littlehow在old company工作了999999年,现在的薪水是3500.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("use time : " + (end - start) + "ms");//平均130毫秒左右
    }
}

 

 

 

经过测试,可以发现,反射主要性能损耗是在对方法或变量的获取时,我们可以从这些方面入手,力求做到性能的极致。

因为我这里只是简单的demo,只是验证反射在哪些地方比较耗时,所以在处理性能优化方面没做进一步的改进。

而且从测试上也可以看出,反射其实没有想想中那么损耗性能,所以运用反射,速度上还是可以接受的。