bat 调用class文件_拯救写框架的程序员!用字节码替代反射,实现任意函数调用...

作者 | 阿里巴巴文娱 高级开发工程师 兰摧 ddf79f0054acfa90457f25da7992d546.png

技术类别:JAVA,后端技术,中间件开发,框架开发

技术亮点:字节码实现类似反射的功能,速度接近JAVA原生的调用

一、背景

我们在写一些框架或者中间件时,往往需要调用用户自定义的方法,最常用的做法就是定义接口,指定某个方法。但这样方法就写死了,不够灵活,如果有多个参数变化的情况下,我们需要定义多个方法,使用者也要实现多个方法,十分不便。

更多时候,使用框架的人希望框架能像spring的@RequestMapping注解一样,标注的方法会框架识别到并且调用。

识别不难,可以模仿一个spring扫包或者直接依托spring扫包即可,重点在调用上。

但问题在于,框架如果按照传统的做法只能通过反射形式来调用被识别的方法,这就大大影响了程序的效率。虽然高版本的jdk对反射已经做了不少优化,但毕竟还是慢。

那么,能否有一种方式,在灵活性上接近反射,在效率上接近原生呢?答案就是字节码。

二、原理和思路

假设我们有这么一个类。

1.     public static class A{  

2.             public void test() {  

3.                 StringBuilder buf = new StringBuilder(100);  

4.                 for(int i =0;i<100;i++) {  

5.                     buf.append(i);  

6.                 }  

7.             }  

8.     }  

最直接的方式当然是通过 new A().test()调用,不过我们也可以写这么一个类来间接调用。

1.     public class CallerWrap$A$Test{     

2.         public Object call(A target){  

3.             target.test();  

4.             return null;  

5.         }       

6.     } 

正常情况下,这个类是写在.java文件中,这样就需要编译,而我们可以用字节码在运行期间直接写在内存中,然后从内存中将这个类取出,创建并调用call方法来间接实现调用类A的test方法。

同样,想要调用类B、类C,甚至任意类的任意方法,只要用字节码在内存中生成相应的CallerWrap$B$MethodName,CallerWrap$C$MethodName……CallerWrap$N$MethodName即可。如此就可以实现调用任意类的任意方法了。

三、核心代码实现

1.  引入cglib

1.        

2.            cglib    

3.            cglib    

4.            3.2.9   

5.       

2.  实现CallerWrap基类,模板类

1.     public abstract class CallerWrap {  

2.         // 转换成cligb形式的type,注意是cglib的type  

3.         private static final Type CALL_WRAP = TypeUtils.parseType(CallerWrap.class.getName());  

4.       

5.         // cglib所需要的keyfactory,cglib在创建对象时的标识  

6.         private static final CallerKey KEY_FACTORY = (CallerKey) KeyFactory.create(CallerKey.class);  

7.       

8.         static interface CallerKey {  

9.             // key的创建申明,注意这里最好使用基本类型以及string,太复杂的类型会有问题  

10.           public Object newInstance(String source, String methodName, String types);  

11.       }  

12.     

13.       /** 创建动态类所需要的方法申明 **/  

14.       public static CallerWrap create(Class> source, String methodName, Class>[] types) {  

15.           // 调用Generator生产者创建动态类,该类需要自己实现,最好是CallerWrap的静态内部类,方便调用所需  

16.           Generator gen = new Generator(source, methodName, types);  

17.           return gen.create();  

18.       }  

19.       /** 

20.        * 调用申明 

21.        */  

22.       public abstract Object call(Object obj, Object[] args) throws Throwable;  

23.   }  

3.  实现Generator

1.     // 生产者,最好作为CallerWrap的内部类,  

2.     private static class Generator extends AbstractClassGenerator {  

3.                 private static final Map, Class>> primary2Wrap = new HashMap<>();  

4.                 private static final Map, MethodInfo> primaryValue = new HashMap<>();  

5.                 private static final Source SOURCE = new Source(CallerWrap.class.getName());  

6.                 static {  

7.                     primary2Wrap.put(byte.class, Byte.class);  

8.                     primary2Wrap.put(short.class, Short.class);  

9.                     primary2Wrap.put(char.class, Character.class);  

10.                   primary2Wrap.put(int.class, Integer.class);  

11.                   primary2Wrap.put(long.class, Long.class);  

12.                   primary2Wrap.put(float.class, Float.class);  

13.                   primary2Wrap.put(double.class, Double.class);  

14.                   primary2Wrap.put(boolean.class, Boolean.class);  

15.                   primary2Wrap.put(void.class, Void.class);  

16.                

17.                   primaryValue.put(byte.class, toPrimary(Byte.class, "byteValue"));  

18.                   primaryValue.put(short.class, toPrimary(Short.class, "shortValue"));  

19.                   primaryValue.put(char.class, toPrimary(Character.class, "charValue"));  

20.                   primaryValue.put(int.class, toPrimary(Integer.class, "intValue"));  

21.                   primaryValue.put(long.class, toPrimary(Long.class, "longValue"));  

22.                   primaryValue.put(float.class, toPrimary(Float.class, "floatValue"));  

23.                   primaryValue.put(double.class, toPrimary(Double.class, "doubleValue"));  

24.                   primaryValue.put(boolean.class, toPrimary(Boolean.class, "booleanValue"));  

25.               }  

26.               private Class> source;  

27.               private String methodName;  

28.               private Class>[] types;  

29.     

30.               public Generator(Class> source, String methodName, Class>[] types) {  

31.                   super(SOURCE);  

32.                   this.source = source;  

33.                   this.methodName = methodName;  

34.                   this.types = types;  

35.                   setNamePrefix(source.getName());  

36.               }  

37.               //使用key创建CallerWrap对象  

38.               public CallerWrap create() {  

39.                   Object key = KEY_FACTORY.newInstance(source.getName(), methodName, Arrays.toString(types));  

40.                   return (CallerWrap) super.create(key);  

41.               }  

42.               //核心方法,用字节码生成call方法  

43.               public void generateClass(ClassVisitor v) {  

44.                   ClassEmitter ce = new ClassEmitter(v);  

45.                   ce.begin_class(Constants.V1_2, Constants.ACC_PUBLIC, getClassName(), CALL_WRAP, null,  

46.                           Constants.SOURCE_FILE);  

47.                   // 构造器  

48.                   EmitUtils.null_constructor(ce);  

49.                   // 创建call方法  

50.                   CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC,  

51.                           new Signature("call", Constants.TYPE_OBJECT,  

52.                                   new Type[] { Constants.TYPE_OBJECT, Constants.TYPE_OBJECT_ARRAY }),  

53.                           new Type[] { Constants.TYPE_THROWABLE });  

54.     

55.                   // 方法体  

56.     

57.                   // 加载需要调用的对象,并强制转换  

58.                   e.load_arg(0);  

59.                   e.checkcast(Type.getType(source));  

60.                   // 加载参数数组,并逐个强制转换  

61.                   if (types != null) {  

62.                       int index = 0;  

63.                       for (Class> type : types) {  

64.                           //加载参数,参数是数组的形式传入的。  

65.                           e.load_arg(1);  

66.                           //从数组中获取第n个参数,也就是实际调用时第n个参数  

67.                           e.aaload(index++);  

68.                           if (type.isPrimitive()) {  

69.                               Class> wrapType = primary2Wrap.get(type);  

70.                               e.checkcast(Type.getType(wrapType));  

71.                               e.invoke(primaryValue.get(type));  

72.                           } else {  

73.                               e.checkcast(Type.getType(type));  

74.                           }  

75.                       }  

76.                   }  

77.                   // 找到方法  

78.                   Method method = getMethod(source, methodName, types);  

79.                   // 用字节码 调用方法  

80.                   e.invoke(ReflectUtils.getMethodInfo(method));  

81.                   //如果是void,返回null,否则返回值  

82.                   Class> returnType = method.getReturnType();  

83.                   if (returnType.equals(void.class) || returnType.equals(Void.class)) {  

84.                       e.aconst_null();  

85.                   }  

86.     

87.                   // 返回值  

88.                   e.return_value();  

89.     

90.                   // 结束方法和类  

91.                   e.end_method();  

92.                   ce.end_class();  

93.               }  

94.     

95.               private static Method getMethod(Class> type, String methodName, Class>[] types) {  

96.                   try{  

97.                       Method method = type.getDeclaredMethod(methodName, types);  

98.                         if(!method.isAccessible()) {  

99.                           method.setAccessible(true);  

100.                    }  

101.                    return method;  

102.                }catch(NoSuchMethodException e){  

103.                    Class> superClass = type.getSuperclass();  

104.                    if(superClass != null){  

105.                        return getMethod(superClass, methodName, types);  

106.                    }  

107.                    throw new RuntimeException(e);  

108.                }catch(Exception e){  

109.                    throw new RuntimeException(e);  

110.                }  

111.            }  

112.            private static MethodInfo toPrimary(Class> cls, String methodName) {  

113.                return ReflectUtils.getMethodInfo(getMethod(cls, methodName, null));  

114.            }  

115.  

116.            protected Object firstInstance(Class type) {  

117.                return ReflectUtils.newInstance(type);  

118.            }  

119.  

120.            protected Object nextInstance(Object instance) {  

121.                return instance;  

122.            }  

123.                    protected ClassLoader getDefaultClassLoader() {  

124.                return source.getClassLoader();  

125.            }  

126.  

127.            protected ProtectionDomain getProtectionDomain() {  

128.                return ReflectUtils.getProtectionDomain(source);  

129.            }  

130.} 

除create方法和generateClass,其他方法的写法都比较固定,最好不要修改。create是根据key作为坐标来创建代理对象,而generateClass则是真正逻辑的实现。这里e.xxxx样子的方法,都是字节码的调用,童鞋们可以搜索下字节码相关的用法,这里就不一一阐述了。不过顺便提一下学习字节码的关键词语:一曰入栈,二曰出栈,其方法的调用,变量的加载等等,无非就是一个入栈,出栈的过程。

4.  封装调用类,方便调用,并确保只创建一次

cglib在创建时消耗的时间比反射还长,所以务必确保只创建一次。同时为了方便调用,在外层封装一个Caller类。

1.     public class Caller{  

2.         private static final Map cache = new ConcurrentHashMap<>();  

3.         private static class Key{  

4.             Class> cls;  

5.             String methodName;  

6.             Class>[] types;  

7.             @Override  

8.             public int hashCode() {  

9.                 int hash = 0;  

10.               hash += methodName.hashCode();  

11.               if(types!=null) {  

12.                   for (Class> type : types) {  

13.                       hash += type.hashCode();  

14.                   }  

15.               }  

16.               return hash;  

17.           }  

18.           @Override  

19.           public boolean equals(Object obj) {  

20.               Key other = (Key) obj;  

21.               return cls.equals(other.cls) && methodName.equals(other.methodName) && eq(types, other.types);  

22.           }  

23.           private boolean eq(Class>[] types1, Class>[] types2) {  

24.               if(types1 == null) {  

25.                   if(types2 == null||types2.length == 0) {  

26.                       return true;  

27.                   }  

28.                   return false;  

29.               }  

30.               if(types2 == null) {  

31.                   if(types1.length  == 0) {  

32.                       return true;  

33.                   }  

34.                   return false;  

35.               }  

36.               return Arrays.equals(types1, types2);  

37.           }  

38.             

39.       }  

40.       /** 

41.        * 调用某个对象的方法 

42.        */  

43.       public static Object call(Object obj,String methodName,  

44.               Class>[] types,Object[] args) throws Throwable {  

45.           Class> cls = obj instanceof Class?(Class>)obj:obj.getClass();     

46.           Key key = new Key();  

47.           key.cls = cls;  

48.           key.methodName = methodName;  

49.           key.types = types;  

50.           CallerWrap caller = cache.get(key);  

51.           if(caller == null) {  

52.               caller = CallerWrap.create(cls, methodName, types);  

53.               cache.put(key, caller);  

54.           }  

55.           return caller.call(obj, args);  

56.       }  

57.     

58.   }  

写在最后

实际上,jdk的反射已经使用MethodAccessor进行了优化,但总体的时间消耗还是稍稍慢些,主要耗费在方法的access检查和getMethod获取方法上,除了字节码外,同样激进的优化可参考jdk1.7的MethodHandle方法句柄。

efb56aee75ef93b8ea1c94efb108f38f.png

加入阿里文娱技术交流群

1、添加“文娱技术小助手”微信

2、注明您的手机号/公司/职位

3、小助手会拉您进群

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值