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

技术类别: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方法句柄。

加入阿里文娱技术交流群

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

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

3、小助手会拉您进群

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

  1. python文件调用python文件_自己写的python文件如何相互调用

    自己写的python文件如何相互调用?Python中的模块库十分常用,对于常用的模块可以自己动手自定义,但是如何进行调用呢? 模块相互调用 同级目录调用时的两种方法import module prin ...

  2. bat 调用class文件_【Java视频教程】day42-??什么是Class???

    使用反射破坏单例 预防措施: 使用反射破坏单例https://www.zhihu.com/video/1087688627387117568 获取和修改成员变量(Field) 获取成员变量的方法: 获 ...

  3. bat 调用class文件_[Golang实现JVM第五篇]静态方法调用的实现

    一直以来又长又臭的调用链简直就是Java语言的标志性特色,方法调用可谓是Java世界里表达一切逻辑的基石.现在我们终于具备了实现它的基础. JVM中的5条方法调用指令 在JVM中触发方法调用的指令有5 ...

  4. float right不生效_【工具篇】程序员不愿意写 PPT 是姿势不对?

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...

  5. 写给Java程序员的Kotlin介绍信

    Kotlin已出现一段时间,很多同学都听过甚至写过一些demo.在我入门时候总有一种盲人摸象不识大体的感觉,如果恰好你也有这种感觉,那么就一起探讨下面的问题吧:      What is Kotlin ...

  6. 当 ChatGPT 比你更会写代码,程序员还能干什么?

    作者 | 何苗 出品 | CSDN(ID:CSDNnews) 大模型的火热引爆了 AI 编程领域的全面革新,人们开始思考如何借助 AI 提高编程效率的同时,也在思考未来需要怎样的"新程序员& ...

  7. python绘制函数图像opengl3d_写给 python 程序员的 OpenGL 教程

    原标题:写给 python 程序员的 OpenGL 教程 作者:牧马人 (本文来自作者投稿) 1预备知识 OpenGL 是 Open Graphics Library 的简写,意为"开放式图 ...

  8. 不要困在自己建造的盒子里――写给.net程序员

    不要困在自己建造的盒子里――写给.net程序员 2011年02月26日 此文章的主旨是希望过于专注.NET程序员在做好工作.写好.NET程序的同时,能分拨出一点时间接触一下.NET之外的东西(例如10 ...

  9. 写代码犹如写文章: “大师级程序员把系统当故事来讲,而不是当做程序来写” | 如何架构设计复杂业务系统? 如何写复杂业务代码?

    写代码犹如写文章: "大师级程序员把系统当故事来讲,而不是当做程序来写" | 如何架构设计复杂业务系统? 如何写复杂业务代码? Kotlin 开发者社区 "大师级程序员把 ...

最新文章

  1. window下Java的环境变量的配置
  2. 《Effective C#》读书笔记——条目10:使用可选参数减少方法重载的数量C#语言习惯...
  3. IDEA2020安装
  4. 【每日算法Day 81】面试经典题:关于丑数,你真的理解为什么这么算吗?
  5. Qt配置使用VS2010进行开发
  6. java jdk使用教程_java初学者实践教程2-jdk的使用
  7. css字体库免费下载使用(带网址)
  8. 解决:Windows打开文件时选择打开方式-》选择其他应用-》时出现无效应用。
  9. 米勒-拉宾(MillerRabbin)素性测试算法模板
  10. Python 实现王者荣耀自动刷金币
  11. php jwt payload,php实现JWT(json web token)鉴权实例详解
  12. html5+css3笔记整理
  13. 刷题笔记 | 朋友圈、岛屿的最大面积、岛屿数量
  14. 智能交通 路侧智能感知 应用层数据格式
  15. >>技术开发:轻量级BI工具Superset
  16. Error:UserServiceImpl不是抽象的, 并且未覆盖UserService中的抽象方法
  17. Binding的学习与使用
  18. 分布式消息中间件 MetaQ 作者庄晓丹专访
  19. 一、解决Linux开启CentOS虚拟机就蓝屏问题
  20. HFSS学习笔记—11.环形定向耦合器分析

热门文章

  1. Ubuntu18.04编译pulseaudio14.x(八)
  2. linux缓存机制buffer/cache/swap
  3. Android报错:No resource found that matches the given name 'Theme.AppCompat.Light.NoActionBar'
  4. Linux修改的文件“修改时间”
  5. Mac使用systrace/monitor/adb等工具
  6. Android Audio System 架构初探(好文)
  7. android之保存Bitmap到文件
  8. webgl之helloworld
  9. tensorflow之成品模型
  10. VALSE学习(十六): Visual Question Generation and Answering-视觉问题生成和视觉问题