最近做项目,一个业务使用了多重classloader隔离,在fastjson转换toJson时,直接报类转换异常了。分析了一段时间,记录分析原因
涉及类自定义加载,这里项目结构如下
自定义classloader,打破双亲委派并相互切换
package com.feng.fastjson.demo;
import com.alibaba.fastjson.JSON;
import com.feng.demo.common.Cat;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class LoadClassMain {
public static void main(String[] args) {
loadByNewClassLoader();
}
public static void loadByNewClassLoader() {
try {
File file = new File("/Users/huahua/source_code/classloader-demo-fastjson/classloader-load-fastjson/src/main/resources/classloader-common-1.0-SNAPSHOT.jar");
URL[] urls = new URL[]{file.toURI().toURL()};
URLClassLoader urlClassLoader = new URLClassLoader(urls){
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//加锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//检查是否已加载
Class> c = findLoadedClass(name);
try {
if (c == null) {
//自己去找
c = findClass(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
c = ClassLoader.getSystemClassLoader().loadClass(name);
}
if (resolve) {
//解析class
resolveClass(c);
}
return c;
}
}
};
Class> c = urlClassLoader.loadClass("com.feng.demo.common.Cat");
Object o = c.newInstance();
Method setName = c.getDeclaredMethod("setName", new Class[]{String.class});
setName.invoke(o, "Tom");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(urlClassLoader);
System.out.println("urlClassLoader: " + JSON.toJSONString(o));
Thread.currentThread().setContextClassLoader(classLoader);
System.out.println("APPClassLoader: " + JSON.toJSONString(new Cat()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
classloader这个操纵还是很容易出现类加载,类转换的问题
根源是fastjson的缓存机制,JSON源码查看序列化过程
public static String toJSONString(Object object, SerializeFilter[] filters, SerializerFeature... features) {
return toJSONString(object, SerializeConfig.globalInstance, filters, null, DEFAULT_GENERATE_FEATURE, features);
}
这里有句代码静态常量
SerializeConfig.globalInstance
public final static SerializeConfig globalInstance = new SerializeConfig();
在创建这个bean的过程,非安卓平台,会开启ASM序列化能力
private boolean asm = !ASMUtils.IS_ANDROID;
public SerializeConfig(int tableSize, boolean fieldBase) {
this.fieldBased = fieldBase;
serializers = new IdentityHashMap(tableSize);
this.mixInSerializers = new IdentityHashMap>(16);
try {
if (asm) {
asmFactory = new ASMSerializerFactory();
}
} catch (Throwable eror) {
asm = false;
}
initSerializers();
}
然后在ASM工厂的创建时
public ASMClassLoader(){
super(getParentClassLoader());
}
public ASMClassLoader(ClassLoader parent){
super (parent);
}
static ClassLoader getParentClassLoader() {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
try {
contextClassLoader.loadClass(JSON.class.getName());
return contextClassLoader;
} catch (ClassNotFoundException e) {
// skip
}
}
return JSON.class.getClassLoader();
}
使用当前线程的classloader,可以看见是我们自定义的类加载器
继续跟踪核心,省略其他代码
if (asm && asmFactory.classLoader.isExternalClass(clazz) || clazz == Serializable.class || clazz == Object.class) { asm = false; }
会判断是否开启asm选项,ASM的ClassLoader会与需要序列化的类的classloader比较,如果ASM的Classloader或者ASM的parent的classloader是类的加载器,那么即可开启ASM
public boolean isExternalClass(Class> clazz) {
//解析的class的加载器
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader == null) {
return false;
}
//ASMClassLoader
ClassLoader current = this;
while (current != null) {
if (current == classLoader) {
return false;
}
current = current.getParent();
}
return true;
}
但是这里,如果自定义classloader,并且打破双亲委派,就会出现BUG,类转换异常,因为classloader是可以自由定义的。
fastjson序列化最终序列类,其实ASM的序列类也是Java序列类的另一只实现,通过ASM字节码生成技术,并通过map使用type缓存,提高效率。
当首次fastjson解析是自定义且非双亲委派加载的类序列化时,ASM就会把自己的ASMclassloader设置为自定义classloader的子classloader。那么当处理自定义classloader的父级classloader加载的类进行序列化时,ASM的classloader只会使用自己的加载类
当父级与自定义classloader同时加载一个类时,会类型不能转换。
所以
urlClassLoader: {"name":"Tom"}
java.lang.ClassCastException: com.feng.demo.common.Cat cannot be cast to com.feng.demo.common.Cat
at com.alibaba.fastjson.serializer.ASMSerializer_2_Cat.write(Unknown Source)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:769)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:707)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:672)
at com.feng.fastjson.demo.LoadClassMain.loadByNewClassLoader(LoadClassMain.java:61)
at com.feng.fastjson.demo.LoadClassMain.main(LoadClassMain.java:13)
fastjson确实比较快,不过最近很多安全漏洞,升级会对业务造成困扰。另外如果使用fastjson,最好对原理有一定了解,很多问题奇奇怪怪才能解决。比如笔者的问题,可以暂时关闭asm(不推荐),可以通过规范,比如父子classloader,不要重复加载相同类。