2019-01-14 · Develop

类型转化异常-ClassCastException

在实际的项目中,在 SpringBoot 的 web 应用中使用 quartz 技术来实现一些定时任务的调度功能。
在功能里面需要将 JobDataMap 中的数据取出数据转化为自己定义的 JobEntity 对象,但是出现如下的报错信息:

org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213)
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: java.lang.ClassCastException: com.zuojl.entity.JobEntity cannot be cast to com.zuojl.entity.JobEntity
	at com.htph.pms.modules.sys.utils.ScheduleJob.executeInternal(ScheduleJob.java:33)
	at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75)
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
	... 1 common frames omitted

分析上面出现的信息发现,相同包下的对象 com.zuojl.entity.JobEntity 不能进行转化,对应的代码如下

@Slf4j
public class MyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobEntity scheduleJob = (JobEntity) context.getMergedJobDataMap().get("JOB_KEY");
        // TODO 
    }
}

期间在 Debug 的时候也发现两个类的路径都是一模一样,但就是不能进行转化。期初怀疑是 serialVersionUID 的原因,因为我们的对象对实现了 Serializable 接口。但后面发现这个并没有影响到转化的结果。
最后在查询了资料后猜测有可能是 ClassLoader 不同不能转化的原因。下面进行验证

log.info(context.getMergedJobDataMap().get("JOB_KEY").getClass().getClassLoader().toString());
log.info(JobEntity.class.getClassLoader().toString());
JobEntity scheduleJob = (JobEntity) context.getMergedJobDataMap().get("JOB_KEY");

返回如下的信息

sun.misc.Launcher$AppClassLoader@18b4aac2
org.springframework.boot.devtools.restart.classloader.RestartClassLoader@929d7f1

从上面的输出信息可以看出问题是因为 devtools 引起的 ClassLoader 不一致导致的无法强转的问题。知道了原因下面给出解决方案

方案一

第一种方案就是在非开发的环境中将 devtools 的功能禁用掉

spring.devtools.restart.enabled=false

方案二

如果想在开发环境也不出现上面的错误可以自己进行转化,避免强转可能出现的问题

JobEntity job = new JobEntity();
BeanUtils.copyProperties(context.getMergedJobDataMap().get("JOB_KEY"), job);

或者是使用 json 工具先转成 String 再转回来也行

方案三

上面都是临时的方案,想从源头解决问题还得回到 ClassLoader 的问题上来,参考资料上在 resources 下创建文件 META-INF/spring-devtools.properties 添加如下信息

restart.include.projectcommon=/quartz-2.3.0.jar

虽然使用 RestartClassLoader 来接管 quartz 包的 Loader 但是在启动的时候不能正常的注入 org.quartz.Scheduler 所以最后我还是采用了上面的方案二,如果你有更好的解决方案的话,欢迎评论

参考文档
SpringBoot使用devtools导致的类型转换异常
Springboot之devtools类加载问题研究