![ddf79f0054acfa90457f25da7992d546.png](https://img-blog.csdnimg.cn/img_convert/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方法句柄。
加入阿里文娱技术交流群
1、添加“文娱技术小助手”微信
2、注明您的手机号/公司/职位
3、小助手会拉您进群