前言:前段時間在設計公司基於netty的易用框架時,不少地方都用到了反射機制。反射的性能一直是你們有目共睹的詬病,相比於直接調用速度上差了不少。可是在不少地方,做爲未知通用判斷的時候,不得不調用反射類型來保障代碼的複用性和框架的擴展性。因此咱們只能想辦法優化反射,而不能抵制反射,那麼優化方案,這裏給你們推薦了ReflectASM。html
1、性能對比
咱們先經過簡單的代碼來看看,各類調用方式之間的性能差距。java
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
public static void main(String[] args) throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"spring-common.xml"}); new InitMothods().initApplicationContext(ac); long now; HttpRouteClassAndMethod route = InitMothods.getTaskHandler("GET:/login/getSession"); Map map = new HashMap(); //-----------------------最粗暴的直接調用 now = System.currentTimeMillis(); for(int i = 0; i<5000000; ++i){ new LoginController().getSession(map); } System.out.println("get耗時"+(System.currentTimeMillis() - now) + "ms); //---------------------常規的invoke now = System.currentTimeMillis(); for(int i = 0; i<5000000; ++i){ Class<?> c = Class.forName("com.business.controller.LoginController"); Method m = c.getMethod("getSession",Map.class); m.invoke(SpringApplicationContextHolder.getSpringBeanForClass(route.getClazz()), map); } System.out.println("標準反射耗時"+(System.currentTimeMillis() - now) + "ms); //---------------------緩存class的invoke now = System.currentTimeMillis(); for(int i = 0; i<5000000; ++i){ try { route.getMethod().invoke(SpringApplicationContextHolder.getSpringBeanForClass(route.getClazz()), new Object[]{map}); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } } System.out.println("緩存反射耗時"+(System.currentTimeMillis() - now) + "ms秒); //---------------------reflectasm的invoke MethodAccess ma = MethodAccess.get(route.getClazz()); int index = ma.getIndex("getSession"); now = System.currentTimeMillis(); for(int i = 0; i<5000000; ++i){ ma.invoke(SpringApplicationContextHolder.getSpringBeanForClass(route.getClazz()), index, map); } System.out.println("reflectasm反射耗時"+(System.currentTimeMillis() - now) + "ms); }
每種方式執行500W次運行結果以下:spring
- get耗時21ms
- 標準反射耗時5397ms
- 緩存反射耗時315ms
- reflectasm反射耗時275ms
(時間長度請忽略,由於每一個人的代碼業務不一致,主要看體現的差距,屢次運行效果基本一致。)緩存
結論:方法直接調用屬於最快的方法,其次是java最基本的反射,而反射中又分是否緩存class兩種,由結果得出其實反射中很大一部分時間是在查找class,實際invoke效率仍是不錯的。而reflectasm反射效率要在java傳統的反射之上快了接近1/3.
2、reflectasm原理解析。
ReflectASM是一個很小的java類庫,主要是經過asm生產類來實現java反射。他的主要代碼仍是get方法,可是因爲get方法源碼比較多,就不在博客中貼出來了,你們能夠本身點進去看。這裏咱們給出實現invoke的抽象方法。app
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) package com.johhny.ra; import com.esotericsoftware.reflectasm.MethodAccess; // Referenced classes of package com.johhny.ra: // User public class UserMethodAccess extends MethodAccess { public UserMethodAccess() { } /** * 這個方法是主要是實現了MethodAccess 的抽象方法,來實現反射的功能 * @param obj 須要反射的對象 * @param i class.getDeclaredMethods 對應方法的index * @param 參數對象集合 * @return */ public transient Object invoke(Object obj, int i, Object aobj[]) { User user = (User)obj; switch(i) { case 0: // '\0' return user.getName(); case 1: // '\001' return Integer.valueOf(user.getId()); case 2: // '\002' user.setName((String)aobj[0]); return null; case 3: // '\003' user.setId(((Integer)aobj[0]).intValue()); return null; } throw new IllegalArgumentException((new StringBuilder("Method not found: ")).append(i).toString()); } }
由代碼能夠看出來,實際上ReflectASM就是把類的各個方法緩存起來,而後經過case選擇,直接調用,所以速度會快上不少。可是它的get方法一樣會消耗很大的時間,所以就算是使用ReflectASM的朋友也記得請在啓動的時候就初始化get方法計入緩存。框架