在SpringBoot中使用 org.apache.commons.lang.SerializationUtils.clone
方法时,发现克隆出来的类强转对应类时发生类型不一致的错误,经过检测发现两个看似相同的类的类加载器不一致
java.lang.ClassCastException: com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint cannot be cast to com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint
既然发现类加载器不一致,那么需要找到类反序列化时的类加载器是如何指定得
深入SerializationUtils.clone
方法时发现内部是通过jdk的反序列化类ObjectInputStream
将字节码转为对象得
发现返回对象是由cons
创建的,cons
是一个Constructor
,那么需要判断cons
是在哪里生成的,从而推断出类加载器的生成依据,而cons
存在于ObjectStreamClass
,进入ObjectStreamClass desc = readClassDesc(false);
判断cons
何时赋值
发现执行完desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
这段代码时,cons
有值,进入initNonProxy
方法中
localDescs
是一个全局静态变量,所以需要知道在什么地方添加值的
打断点debug发现在序列化的时候会new ObjectStreamClass(cl)
并放在localDescs
里,所以进入new ObjectStreamClass(cl)
的方法里,找cons
的来源
发现cons
是由Class cl
生成,也就是说cons
的类加载器信息是由Class cl
的类加载器决定的,反向查找cl
的加载
发现cl
的类加载信息由latestUserDefinedLoader()
,查阅资料发现,latestUserDefinedLoader()
会根据栈帧信息查找第一个非根类加载器或扩展类加载器,而SerializationUtils
属于ApplicationClassLoader
加载的范围,所以SerializationUtils.clone(point)
返回的对象是由ApplicationClassLoader
加载
将SerializationUtils.clone
中的方法复制到项目中
public class TestClassLoaderController {
private static PrePoint point = new PrePoint();
@GetMapping("/testClassLoader")
public AjaxResult testClassLoader() {
PrePoint deserialize = (PrePoint) cloneObject(point);
return null;
}
private static Object cloneObject(PrePoint point) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
serialize(point, baos);
byte[] bytes = baos.toByteArray();
return deserialize(bytes);
}
public static Object deserialize(byte[] objectData) {
if (objectData == null) {
throw new IllegalArgumentException("The byte[] must not be null");
}
ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
return deserialize(bais);
}
public static Object deserialize(InputStream inputStream) {
if (inputStream == null) {
throw new IllegalArgumentException("The InputStream must not be null");
}
ObjectInputStream in = null;
try {
// stream closed in the finally
in = new ObjectInputStream(inputStream);
return in.readObject();
} catch (ClassNotFoundException ex) {
throw new SerializationException(ex);
} catch (IOException ex) {
throw new SerializationException(ex);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
// ignore close exception
}
}
}
public static void serialize(Serializable obj, OutputStream outputStream) {
if (outputStream == null) {
throw new IllegalArgumentException("The OutputStream must not be null");
}
ObjectOutputStream out = null;
try {
// stream closed in the finally
out = new ObjectOutputStream(outputStream);
out.writeObject(obj);
} catch (IOException ex) {
throw new SerializationException(ex);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException ex) {
// ignore close exception
}
}
}
}
关闭热加载
spring:
devtools:
restart:
enabled: false
或者
@SpringBootApplication
public class SSMPApplication {
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled","false");
SpringApplication.run(SSMPApplication.class);
}
}
移除热加载的jar包
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>