这篇文章我们将讲解 ASM 在 cglib 和 fastjson 上的实际使用案例。
cglib 的简单应用
如果说 ASM 是字节码改写事实上的标准,那么可以说 cglib 则是动态代理事实上的标准。 cglib 是一个强大的、高性能的代码生成库,被大量框架使用
- Spring:为基于代理的 AOP 框架提供方法拦截
- MyBatis:用来生成 Mapper 接口的动态代理实现类
- Hibernate:用来生成持久化相关的类
- Guice、EasyMock、jMock 等
在实现内部,cglib 库使用了 ASM 字节码操作框架来转化字节码,产生新类,帮助开发者屏蔽了很多字节码相关的内部细节,不用再去关心类文件格式、指令集等
有这样一个 Person 类,想在 doJob 调用前和调用后分别记录一些日志
public class Person {
public void doJob(String jobName) {
System.out.println("who is this class: " + getClass());
System.out.println("doing job: " + jobName);
}
}
我们可以使用 JDK 动态代理来实现,不过介于 JDK 动态代理有个明显的缺点(需要目标对象实现一个或多个接口),在这里重点介绍 cglib 的实现方案。
一个典型的实现方案是实现一个 net.sf.cglib.proxy.MethodInterceptor
接口,用来拦截方法调用。这个接口只有一个方法:public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;
这个方法的第一个参数 obj 是代理对象,第二个参数 method 是拦截的方法,第三个参数是方法的参数,第四个参数 proxy 用来调用父类的方法。MethodInterceptor 作为一个桥梁连接了目标对象和代理对象
cglib 代理的核心是 net.sf.cglib.proxy.Enhancer
类,它用于创建一个 cglib 代理。这个类有一个静态方法public static Object create(Class type, Callback callback)
,该方法的第一个参数 type 指明要代理的对象类型,第二个参数 callback 是要拦截的具体实现,一般都会传入一个 MethodInterceptor 的实现
public static void main(String[] _args) {
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println(">>>>>before intercept");
Object o = methodProxy.invokeSuper(obj, args);
System.out.println(">>>>>end intercept");
return o;
}
};
Person person = (Person) Enhancer.create(Person.class, interceptor);
person.doJob("coding");
}
运行上面的代码输出:
>>>>>before intercept
who is this class: class Person?EnhancerByCGLIB?a1da8fe5
doing job: coding
>>>>>end intercept
可以用设置系统变量让 cglib 输出生成的文件System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/path/to/cglib-debug-location");
核心类是 Person?EnhancerByCGLIB?a1da8fe5.class
,这个类的反编译以后的代码如下
public class Person?EnhancerByCGLIB?a1da8fe5 extends Person implements Factory {
public final void doJob(String jobName) {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
methodInterceptor.intercept(this, CGLIB$doJob$0$Method, new Object[]{jobName}, CGLIB$doJob$0$Proxy);
}
}
可以看到 cglib 生成了一个 Person 的子类,实现了 doJob 方法,此方法会调用 MethodInterceptor 的 intercept 函数,这个函数先输出 ">>>>>before intercept" 然后调用父类(也即真正的 Person 类)的 doJob 的方法,最后输出 ">>>>>end intercept"
FastJson
fastjson 是目前 java 语言中最快的 json 库,比自称最快的 jackson 速度要快。fastjson 库内置 ASM,基于 objectweb asm 3.3 改造,只保留必要的部分不到 2000 行代码,通过 ASM 自动生成序列号、反序列化字节码,减少反射开销,理论上可以提高 20% 的性能。 如果不用反射,一个 json 反序列化要怎么样来做呢?下面写了一个最简单粗暴的例子,来反序列化下面的 json 字符串
{
"id": "A10001",
"name": "Arthur.Zhang",
"score": 100
}
对应 Java bean
public static class MyBean {
public String id;
public String name;
public Integer score;
}
假定不考虑嵌套,特殊字符的情况,不做语法解析的情况下,可以这么来写
public static void main(String[] args) {
String json = "{ \"id\": \"A10001\", \"name\": \"Arthur.Zhang\", \"score\": 100 }";
// 去掉头尾的 {}
String str = json.substring(1, json.length() - 1);
// 用 "," 分割字符串
String[] fieldStrArray = str.split(",");
MyBean bean = new MyBean();
for (String item : fieldStrArray) {
// 分隔 key value
String[] parts = item.split(":");
String key = parts[0].replaceAll("\"", "").trim();
String value = parts[1].replaceAll("\"", "").trim();
// 通过反射获取字段对应的 field
Field field = MyBean.class.getDeclaredField(key);
// 根据字段类型通过反射设置字段的值
if (field.getType() == String.class) {
field.set(bean, value);
} else if (field.getType() == Integer.class) {
field.set(bean, Integer.valueOf(value));
}
}
System.out.println(bean);
}
可以看到获取获取字段 field、设置字段值都需要通过反射的方式。那么 fastjson 是怎么解决反射低效的问题的呢? 通过调试的方式,把 fastjson 生成的字节码写入到文件中。针对 MyBean,fastjson 使用 ASM 为它生成了一个反序列化的类,里面硬编码了处理序列化需要用到的所有可能场景,不再需要任何反射相关的代码。结合创新的 sort field fast match 算法,速度更上一层楼。下面是通过阅读字节码精简以后的 Java 代码。
public class FastjsonASMDeserializer_1_MyBean extends JavaBeanDeserializer {
public char[] id_asm_prefix__ = "\"id\":".toCharArray();
public char[] name_asm_prefix__ = "\"name\":".toCharArray();
public char[] score_asm_prefix__ = "\"score\":".toCharArray();
@Override
public Object deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
JSONLexerBase lexer = (JSONLexerBase) parser.lexer;
MyTest.MyBean localMyBean = new MyTest.MyBean();
String id = lexer.scanFieldString(this.id_asm_prefix__);
if (lexer.matchStat > 0) {
localMyBean.id = id;
}
String name = lexer.scanFieldString(this.name_asm_prefix__);
if (lexer.matchStat > 0) {
localMyBean.name = name;
}
Integer score = lexer.scanFieldInt(this.score_asm_prefix__);
if (lexer.matchStat > 0) {
localMyBean.score = score;
}
return localMyBean;
}
}
通过上面的两个例子,我们可以看到 ASM 字节码技术在底层库上的强大。可能每天写业务代码不会需要使用这些底层的优化,但是当我们想造一个轮子,想读懂开源代码背后的核心时,都不得不深入的去学习了解这部分知识,很难,但很值得。
小结
这篇文章我们主要讲解了 ASM 字节码改写技术在 cglib 和 fastjson 上的应用,一起来回顾一下要点:
- 第一,cglib 使用 ASM 生成了目标代理类的一个子类,在子类中扩展父类方法,达到代理的功能,因此要求代理的类不能是 final 的。
- 第二,fastjson 使用 ASM 生成了实例 Bean 反序列化类,彻底去掉了反射的开销,使性能更上一层楼。
本文由mdnice多平台发布
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。