Problem analysis
When using the org.apache.commons.lang.SerializationUtils.clone
method in SpringBoot, it was found that a type inconsistency error occurred when the cloned class was forced to the corresponding class. After inspection, it was found that two seemingly identical Class loaders for classes are inconsistent
Scene
Error message
java.lang.ClassCastException: com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint cannot be cast to com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint
Detection information
Solution analysis
Now that the class loader is found to be inconsistent, you need to find out how the class loader is specified when the class is deserialized.
When delving into the SerializationUtils.clone
method, we found that the bytecode is converted into an object through jdk’s deserialization class ObjectInputStream
It is found that the returned object is created by cons
, and cons
is a Constructor
, then you need to determine where cons
is generated , thus inferring the basis for generating the class loader, and cons
exists in ObjectStreamClass
, enter ObjectStreamClass desc = readClassDesc(false);
to determine < When does code>cons assign a value?
It is found that when desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
is executed, cons
has a value and enters initNonProxy
In method
It was found that it ultimately pointed to a map of Caches.localDescs
localDescs
is a global static variable, so you need to know where to add the value
Breakpoint debugging found that new ObjectStreamClass(cl)
will be placed in localDescs
during serialization, so enter new ObjectStreamClass(cl)
In the method, find the source of cons
It is found that cons
is generated by Class> cl
, which means that the class loader information of cons
is generated by Class> Determined by the class loader of cl
, reverse search for the loading of cl
It is found that the class loading information of cl
is provided by latestUserDefinedLoader()
. After consulting the data, it is found that latestUserDefinedLoader()
will find the first non-root based on the stack frame information. Class loader or extended class loader, and SerializationUtils
belongs to the scope loaded by ApplicationClassLoader
, so the object returned by SerializationUtils.clone(point)
is ApplicationClassLoader
loads
Solution
Option 1 (recommended)
Copy the methods in SerializationUtils.clone
to the project
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 } } } }
Option 2
Turn off hot reloading
spring: devtools: restart: enabled: false
or
@SpringBootApplication public class SSMPApplication {<!-- --> public static void main(String[] args) {<!-- --> System.setProperty("spring.devtools.restart.enabled","false"); SpringApplication.run(SSMPApplication.class); } }
Option three
Remove hot-loaded jar packages
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- This needs to be true for hot deployment to be effective --> </dependency>