spring-boot-devtools导致com.alibaba.fastjson.JSONException: write javaBean error, fastjson ver

为什么会出现这个问题呢,首先这个跟java类加载的双亲委派机制有关系,双亲机制还有一个作用就是隔离,不同类加载器加载的类之间是不能进行转换的,FastJSON也不例外,看下面两张图:

spring-boot-devtools导致com.alibaba.fastjson.JSONException: write javaBean error, fastjson ver

                                                       图1 fastjson序列化时

spring-boot-devtools导致com.alibaba.fastjson.JSONException: write javaBean error, fastjson ver

                    图2 controller中方法调用时,即调用fastjson.toJSONString()之前

从上面两张图可以看出,图一个类在fastjson中是被AppclassLoader加载的,但是在controller中传入的UserDetailsDto类却是被RestartClassLoader加载的,最终导致了序列化失败!

其实不只涉及到json序列化问题,强制类型转换也会因RestartClassLoader出现下面的问题:

java.lang.ClassCastException: jtl3d.dto.UserDetailsDto cannot be cast to jtl3d.dto.UserDetailsDto

这种报错看到时会一脸懵逼(看似同样的类为什么不能强制转换),其实是因为两个类不是一个类加载器加载导致的

 

解决方案:

1、去掉spring-boot-devtools

2、如果需要反序列化的类有默认构造方法的话可以使用jackson处理--不推荐

3、还有一种就是按照spring官网上排序不需要使用spring-boot-devtools里面类加载器加载--不推荐,有这个功夫不如直接去掉spring-boot-devtools

 

PS:

1、序列化失败原因可能有多种,但今天这种情况笔者是第一次见,在spring官网找到了下面的一段话:简单说就是不变的类一般都要appclassloader加载,开发应用中的类由spring-boot-devtools里的restart 类加载器加载,而Fastjson也是第三方jar包,孤儿也使用appclassloader加载

 

Restart vs Reload

The restart technology provided by Spring Boot works by using two classloaders. Classes that do not change (for example, those from third-party jars) are loaded into a base classloader. Classes that you are actively developing are loaded into a restart classloader. When the application is restarted, the restart classloader is thrown away and a new one is created. This approach means that application restarts are typically much faster than “cold starts”, since the base classloader is already available and populated.

If you find that restarts are not quick enough for your applications or you encounter classloading issues, you could consider reloading technologies such as JRebelfrom ZeroTurnaround. These work by rewriting classes as they are loaded to make them more amenable to reloading.

 

参考文章:

1、https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-spring-boot-restart-vs-reload

20.2.6 Customizing the Restart Classloader

As described earlier in the Restart vs Reload section, restart functionality is implemented by using two classloaders. For most applications, this approach works well. However, it can sometimes cause classloading issues.

By default, any open project in your IDE is loaded with the “restart” classloader, and any regular .jar file is loaded with the “base” classloader. If you work on a multi-module project, and not every module is imported into your IDE, you may need to customize things. To do so, you can create a META-INF/spring-devtools.properties file.

The spring-devtools.properties file can contain properties prefixed with restart.exclude and restart.include. The include elements are items that should be pulled up into the “restart” classloader, and the exclude elements are items that should be pushed down into the “base” classloader. The value of the property is a regex pattern that is applied to the classpath, as shown in the following example:

restart.exclude.companycommonlibs=/mycorp-common-[\\w-]+\.jar
restart.include.projectcommon=/mycorp-myproj-[\\w-]+\.jar