由Quartz框架引发的“A cannot cast to A”的问题


起因与问题描述

向新开的项目中添加定时任务,部署集群,添加了热加载(springboot-dev-tools),发现在转型时候出现了A cannot cast to A”的问题。自己怎么可能不认识自己???排查走起!!!


排查

类确实是同一类,问题会出现在哪里呢?我们可以想到,类加载器不同会导致“我不是我”的问题,所以打印debug走起!
真的是类加载器不同!能注意到,我们想要的ScheduleJobEntity类,类加载器是RestartClassLoader,这一切发生在【添加热加载(springboot-dev-tools)】之后才出现的。这时候需要复习一下springboot-dev-tools与类加载器机制的知识了。

知识回顾

类加载器机制

  1. 有两个术语,一个叫“定义类加载器”,一个叫“初始类加载器”。比如有如下的类加载器结构:

    • bootstrap

      • ExtClassloader
      • AppClassloader
    • -自定义clsloadr1
    • -自定义clsloadr2
      如果用“自定义clsloadr1”加载java.lang.String类,那么根据双亲委派最终bootstrap会加载此类,那么bootstrap类就叫做该类的“定义类加载器”,而包括bootstrap的所有得到该类class实例的类加载器都叫做“初始类加载器”。
  2. 所说的“命名空间”,是指jvm为每个类加载器维护的一个“表”,这个表记录了所有以此类加载器为“初始类加载器”(而不是定义类加载器,所以一个类可以存在于很多的命名空间中)加载的类的列表,所以,题目中的问题就可以解释了:
    CLTest是AppClassloader加载的,String是通过加载CLTest的类加载器也就是AppClassloader进行加载,但最终委派到bootstrap加载的(当然,String类其实早已经被加载过了,这里只是举个例子)。所以,对于String类来说,bootstrap是“定义类加载器”,AppClassloader是“初始类加载器”。根据刚才所说,String类在AppClassloader的命名空间中(同时也在bootstrap,ExtClassloader的命名空间中,因为bootstrap,ExtClassloader也是String的初始类加载器),所以CLTest可以随便访问String类。这样就可以解释“处在不同命名空间的类,不能直接互相访问”这句话了。
  3. 一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例。
  4. 那么由不同类加载器实例(比如-自定义clsloadr1,-自定义clsloadr2)所加载的classpath下和ext下的类,也就是由我们自定义的类加载器委派给AppClassloader和ExtClassloader加载的类,在内存中是同一个类吗?所有继承ClassLoader并且没有重写getSystemClassLoader方法的类加载器,通过getSystemClassLoader方法得到的AppClassloader都是同一个AppClassloader实例,类似单例模式。在ClassLoader类中getSystemClassLoader方法调用私有的initSystemClassLoader方法获得AppClassloader实例,在initSystemClassLoader中:
    sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
...
scl = l.getClassLoader();

AppClassloader是sun.misc.Launcher类的内部类,Launcher类在new自己的时候生成AppClassloader实例并且放在自己的私有变量loader里:

loader = AppClassLoader.getAppClassLoader(extclassloader);

值得一提的是sun.misc.Launcher类使用了一种类似单例模式的方法,即既提供了单例模式的接口getLauncher()又把构造函数设成了public的。但是在ClassLoader中是通过单件模式取得的Launcher 实例的,所以我们写的每个类加载器得到的AppClassloader都是同一个AppClassloader类实例。
这样的话得到一个结论,就是所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在方法区都是同一个类,堆中的Class实例也是同一个。

springboot-dev-tools

可以参考<a href = "https://blog.csdn.net/isea533/article/details/70495714">Spring DevTools 介绍一文,其中最关键的一句话就是

重启功能是通过使用两个类加载器实现的。 对于大多数应用程序,此方法运行良好,但有时可能会导致类加载问题。
默认情况下,IDE中的任何打开的项目都会使用“restart”类加载器加载,任何常规.jar文件将使用“base”类加载器加载。

解决问题

在项目 /resource/META-INF目录下(如果没有就创建一个)创建
spring-devtools.properties文件 加入下面代码:

restart.include.mapper=/mapper-[\w-\.]+jar
restart.include.pagehelper=/pagehelper-[\w-\.]+jar
restart.include.shiro=/shiro-[\w-\.]+jar

这里其实是
添加 jar 包到 restart 类加载器中 = 后面是具体的 jar 包名称, 或正则表达式

 

JVM的深入理解:由一次Quartz的定时任务引发的“A cannot cast to A”的问题的更多相关文章

  1. Quartz.net 定时任务之Cron表达式

    一.cron表达式简单介绍和下载 1.在上一篇博客"Quartz.net 定时任务之简单任务"中,我简单介绍了quartz的使用,而这篇博客我将介绍cron的具体使用(不足之处望大 ...

  2. 使用Quartz创建定时任务

    项目开发中经常需要定时循环执行某些任务 比如定时发送报表,定时发送邮件,亦或者定时清理缓存,定时更新数据等等 有些时候可以简单地利用Windows Server的计划任务执行程序 Linux也有相应的 ...

  3. springboot整合Quartz实现定时任务

    1.maven依赖: <!--quartz--> <dependency> <groupId>org.quartz-scheduler</groupId> ...

  4. Spring+Quartz 实现定时任务的配置方法

    Spring+Quartz 实现定时任务的配置方法 整体介绍 一.Quartz介绍 在企业应用中,我们经常会碰到时间任务调度的需求,比如每天凌晨生成前天报表,每小时生成一次汇总数据等等.Quartz是 ...

  5. Spring整合quartz2.2.3总结,quartz动态定时任务,Quartz定时任务集群配置

    Spring整合quartz2.2.3总结,quartz动态定时任务,Quartz定时任务集群配置 >>>>>>>>>>>>&g ...

  6. 【spring-boot】 springboot整合quartz实现定时任务

    在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的. spring支持多种定时任务的实现.我们来介绍下使用spring的定时器和使用quartz定时器 1.我们使用s ...

  7. Spring Boot集成quartz实现定时任务并支持切换任务数据源

    org.quartz实现定时任务并自定义切换任务数据源 在工作中经常会需要使用到定时任务处理各种周期性的任务,org.quartz是处理此类定时任务的一个优秀框架.随着项目一点点推进,此时我们并不满足 ...

  8. SpringBoot集成Quartz实现定时任务

    1 需求 在我的前后端分离的实验室管理项目中,有一个功能是学生状态统计.我的设计是按天统计每种状态的比例.为了便于计算,在每天0点,系统需要将学生的状态重置,并插入一条数据作为一天的开始状态.另外,考 ...

  9. Quartz:定时任务工具类

    Quartz:定时任务工具类 Quartz工具类 Quartz工具类 import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; ...

随机推荐

  1. bitmap以及异或运算法

    一 有40亿个整数,再给一个新的整数,需要判断新的整数是否在1亿个整数中. 此处需要用到bitmap方法,每个整数用一个bit表示,1表示存在,0表示不存在.因此一个4字节的int=32个bit也就是 ...

  2. 映美FP-530K+打印发票的各种经验

    本人虽然写了很多lodop博文,但是程序开发一般都是用虚拟打印机测试,实际打印机打印中还可能存在各种问题,毕竟理论都不如实践能获得更多的打印经验.当个人使用过的打印机不多,只有映美FP-530K+,用 ...

  3. Apache软件基金会核心项目Tomcat的那些事

    Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选.今天就来为大家介绍一下关于T ...

  4. 超实用的 JavaScript 代码片段( ES6+ 编写)

    Array 数组 Array concatenation (数组拼接) 使用 Array.concat() ,通过在 args 中附加任何数组 和/或 值来拼接一个数组. const ArrayCon ...

  5. 在Linux下,使用Xmanager图形化显示

    linux在安装Oracle时,需要图形化展示,本博客将使用xclock显示钟表进行测试 首先需要下载Xmanager网址:http://www.xshellcn.com/ 安装之后 打开之后,点击t ...

  6. Word样式教程

    目录 写在前面 样式可以解决什么问题? 本文适合于 快速入门 一切皆样式 样式与格式的关系 如何修改样式 建立新的样式 样式的匹配和更新 根据样式更新所选段落 根据所选段落更新样式 小结 进一步了解 ...

  7. Pycharm下直接升级库所遇到的'main'问题

    Pycharm下直接升级库所遇到的pip模块中无'main'问题 Author : Benjamin142857 Date : 8/19/2018 对于Pycharm中直接升级库,只需在 \(Sett ...

  8. 【转载】Windows安装Redis并添加本地自启动服务

    概况 在windows本地搭建redis缓存,添加到本地计算机的服务中,保证每次开机自动启动服务. 第一步:下载redis(我的是计算机win10,64位) https://github.com/Mi ...

  9. 记redis一次Could not get a resource from the pool 异常的解决过程

    最近有个项目中的redis每天都会报 "Could not get a resource from the pool"的错误,而这套代码在另一地方部署又没有问题.一直找不到错误原因 ...

  10. Base64图片编码的使用

    一.base64编码介绍 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,Base64编码可用于在HTTP环境下传递较长的标识信息.采用Base64编码具有不可读性,即所编码的数据 ...