fastjson classloader问题处理与原因分析

前言

最近做项目,一个业务使用了多重classloader隔离,在fastjson转换toJson时,直接报类转换异常了。分析了一段时间,记录分析原因

1. demo模拟

涉及类自定义加载,这里项目结构如下

fastjson classloader问题处理与原因分析_第1张图片

自定义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();
        }

    }
}

运行结果如下:

fastjson classloader问题处理与原因分析_第2张图片

classloader这个操纵还是很容易出现类加载,类转换的问题

2. 原因分析

根源是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,可以看见是我们自定义的类加载器

fastjson classloader问题处理与原因分析_第3张图片

继续跟踪核心,省略其他代码

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 classloader问题处理与原因分析_第4张图片

fastjson序列化最终序列类,其实ASM的序列类也是Java序列类的另一只实现,通过ASM字节码生成技术,并通过map使用type缓存,提高效率。

当首次fastjson解析是自定义且非双亲委派加载的类序列化时,ASM就会把自己的ASMclassloader设置为自定义classloader的子classloader。那么当处理自定义classloader的父级classloader加载的类进行序列化时,ASM的classloader只会使用自己的加载类

fastjson classloader问题处理与原因分析_第5张图片

当父级与自定义classloader同时加载一个类时,会类型不能转换。

fastjson classloader问题处理与原因分析_第6张图片

所以

fastjson classloader问题处理与原因分析_第7张图片

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,不要重复加载相同类。

 

 

你可能感兴趣的:(架构设计,json)