bat 调用class文件_拯救写框架的程序员!用字节码替代反射,实现任意函数调用...
技术类别: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文件_拯救写框架的程序员!用字节码替代反射,实现任意函数调用...相关推荐
- python文件调用python文件_自己写的python文件如何相互调用
自己写的python文件如何相互调用?Python中的模块库十分常用,对于常用的模块可以自己动手自定义,但是如何进行调用呢? 模块相互调用 同级目录调用时的两种方法import module prin ...
- bat 调用class文件_【Java视频教程】day42-??什么是Class???
使用反射破坏单例 预防措施: 使用反射破坏单例https://www.zhihu.com/video/1087688627387117568 获取和修改成员变量(Field) 获取成员变量的方法: 获 ...
- bat 调用class文件_[Golang实现JVM第五篇]静态方法调用的实现
一直以来又长又臭的调用链简直就是Java语言的标志性特色,方法调用可谓是Java世界里表达一切逻辑的基石.现在我们终于具备了实现它的基础. JVM中的5条方法调用指令 在JVM中触发方法调用的指令有5 ...
- float right不生效_【工具篇】程序员不愿意写 PPT 是姿势不对?
| 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...
- 写给Java程序员的Kotlin介绍信
Kotlin已出现一段时间,很多同学都听过甚至写过一些demo.在我入门时候总有一种盲人摸象不识大体的感觉,如果恰好你也有这种感觉,那么就一起探讨下面的问题吧: What is Kotlin ...
- 当 ChatGPT 比你更会写代码,程序员还能干什么?
作者 | 何苗 出品 | CSDN(ID:CSDNnews) 大模型的火热引爆了 AI 编程领域的全面革新,人们开始思考如何借助 AI 提高编程效率的同时,也在思考未来需要怎样的"新程序员& ...
- python绘制函数图像opengl3d_写给 python 程序员的 OpenGL 教程
原标题:写给 python 程序员的 OpenGL 教程 作者:牧马人 (本文来自作者投稿) 1预备知识 OpenGL 是 Open Graphics Library 的简写,意为"开放式图 ...
- 不要困在自己建造的盒子里――写给.net程序员
不要困在自己建造的盒子里――写给.net程序员 2011年02月26日 此文章的主旨是希望过于专注.NET程序员在做好工作.写好.NET程序的同时,能分拨出一点时间接触一下.NET之外的东西(例如10 ...
- 写代码犹如写文章: “大师级程序员把系统当故事来讲,而不是当做程序来写” | 如何架构设计复杂业务系统? 如何写复杂业务代码?
写代码犹如写文章: "大师级程序员把系统当故事来讲,而不是当做程序来写" | 如何架构设计复杂业务系统? 如何写复杂业务代码? Kotlin 开发者社区 "大师级程序员把 ...
最新文章
- window下Java的环境变量的配置
- 《Effective C#》读书笔记——条目10:使用可选参数减少方法重载的数量C#语言习惯...
- IDEA2020安装
- 【每日算法Day 81】面试经典题:关于丑数,你真的理解为什么这么算吗?
- Qt配置使用VS2010进行开发
- java jdk使用教程_java初学者实践教程2-jdk的使用
- css字体库免费下载使用(带网址)
- 解决:Windows打开文件时选择打开方式-》选择其他应用-》时出现无效应用。
- 米勒-拉宾(MillerRabbin)素性测试算法模板
- Python 实现王者荣耀自动刷金币
- php jwt payload,php实现JWT(json web token)鉴权实例详解
- html5+css3笔记整理
- 刷题笔记 | 朋友圈、岛屿的最大面积、岛屿数量
- 智能交通 路侧智能感知 应用层数据格式
- >>技术开发:轻量级BI工具Superset
- Error:UserServiceImpl不是抽象的, 并且未覆盖UserService中的抽象方法
- Binding的学习与使用
- 分布式消息中间件 MetaQ 作者庄晓丹专访
- 一、解决Linux开启CentOS虚拟机就蓝屏问题
- HFSS学习笔记—11.环形定向耦合器分析
热门文章
- Ubuntu18.04编译pulseaudio14.x(八)
- linux缓存机制buffer/cache/swap
- Android报错:No resource found that matches the given name 'Theme.AppCompat.Light.NoActionBar'
- Linux修改的文件“修改时间”
- Mac使用systrace/monitor/adb等工具
- Android Audio System 架构初探(好文)
- android之保存Bitmap到文件
- webgl之helloworld
- tensorflow之成品模型
- VALSE学习(十六): Visual Question Generation and Answering-视觉问题生成和视觉问题