ClassCastException: XXX are in unnamed module of loader ‘app‘异常分析

 

项目场景:

SpringBoot 2.x + Spring5.3.14 测试使用Trigger实现任务调度


问题描述:

程序编译不报错, 启动SpringBoot后台报错,根据日志提示发现创建SchedulerFactoryBean的时候发生了ClassCastException类转换异常 

class org.quartz.impl.triggers.CronTriggerImpl cannot be cast to class [Lorg.quartz.Trigger; 

详细报错日志如下:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.scheduling.quartz.SchedulerFactoryBean]: Factory method 'getSchedulerFactoryBean' threw exception; nested exception is java.lang.ClassCastException: class org.quartz.impl.triggers.CronTriggerImpl cannot be cast to class [Lorg.quartz.Trigger; (org.quartz.impl.triggers.CronTriggerImpl and [Lorg.quartz.Trigger; are in unnamed module of loader 'app')
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.14.jar:5.3.14]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.14.jar:5.3.14]
	... 24 common frames omitted

Caused by: java.lang.ClassCastException: class org.quartz.impl.triggers.CronTriggerImpl cannot be cast to class [Lorg.quartz.Trigger; (org.quartz.impl.triggers.CronTriggerImpl and [Lorg.quartz.Trigger; are in unnamed module of loader 'app')
	at com.boot.basic.scheduled.AutoOrderProductConfig.getSchedulerFactoryBean(AutoOrderProductConfig.java:41) ~[classes/:na]
	at com.boot.basic.scheduled.AutoOrderProductConfig$$EnhancerBySpringCGLIB$$25770de7.CGLIB$getSchedulerFactoryBean$2(<generated>) ~[classes/:na]
	at com.boot.basic.scheduled.AutoOrderProductConfig$$EnhancerBySpringCGLIB$$25770de7$$FastClassBySpringCGLIB$$d39456c0.invoke(<generated>) ~[classes/:na]

报错位置的问题源码如下,@Configuration的配置类中配置了SchedulerFactoryBean ,并想其中注册Triggers, 实现调度, SpringUtil对象来自第三方包 hutool-extra ,它是一个很好用的工具类库,例如getBean方法可直接从Bean工厂中获取类实例

    @Bean("myScheduler")
    public SchedulerFactoryBean getSchedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setTriggers(SpringUtil.getBean("jobTrigger"));
        return schedulerFactoryBean;
    }

​


原因分析:

首先, 从程序上并没有看出问题所在

SpringUtil的到的Bean对象是 CronTriggerImpl,其实现了org.quartz.CronTrigger接口

package org.quartz.impl.triggers;
//略
public class CronTriggerImpl extends AbstractTrigger<CronTrigger> implements CronTrigger, CoreTrigger {

    //略

}

schedulerFactoryBean.setTriggers的参数是org.quartz.CronTrigger,

    public void setTriggers(Trigger... triggers) {
        this.triggers = Arrays.asList(triggers);
    }

所以将CronTrigger的实现类当做参数正常情况下是不会出现问题的,这正是Java的多态特性

然后使用javap -c AutoOrderProductConfig 命令查看类class的字节码信息,字节码信息如下

补充: Javap命令使用方式:链接:使用Javap命令查看class文件的字节码

 public org.springframework.scheduling.quartz.SchedulerFactoryBean getSchedulerFactoryBean();
   Code:
      0: new           #22                 // class org/springframework/scheduling/quartz/SchedulerFactoryBean
      3: dup
      4: invokespecial #23                 // Method org/springframework/scheduling/quartz/SchedulerFactoryBean."<init>":()V
      7: astore_1
      8: aload_1
      9: ldc           #12                 // String jobTrigger
     11: invokestatic  #19                 // Method cn/hutool/extra/spring/SpringUtil.getBean:(Ljava/lang/String;)Ljava/lang/Object;
     14: checkcast     #24                 // class "[Lorg/quartz/Trigger;"
     17: invokevirtual #25                 // Method org/springframework/scheduling/quartz/SchedulerFactoryBean.setTriggers:([Lorg/quartz/Trigger;)V
     20: aload_1
     21: areturn
  • 第11行:invokestatic指令调用静态方法实现获取bean
  • 第14行:checkcast 只用用于类转换检查
  • 第17行:invokevirtual 指调用对象SchedulerFactoryBean#setTriggers方法,并根据对象的实际类型做分派

查看SpringUtil#getBean方法,返回结果是个泛型 ,因此这里看出现异常ClassCastException的地方应该就是执行指令checkcast时出现,执行指令的时候将CronTriggerImpl对象检查是否可将其强转转换成接口CronTrigger,检查不通过所以报错

	public static <T> T getBean(String name) {
		return (T) getBeanFactory().getBean(name);
	}


解决方案:

    @Bean("myScheduler")
    public SchedulerFactoryBean getSchedulerFactoryBean() {
        CronTriggerImpl bean=SpringUtil.getBean("jobTrigger");
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setTriggers(bean);
        return schedulerFactoryBean;
    }

为避免checkcast报错,可将代码修改成一下方式SpringUtils放回结果转换成CronTriggerImpl 类,然后设置setTriggers的时候参数为CronTriggerImpl ,为做对比也分析一下修改后的字节码

 public org.springframework.scheduling.quartz.SchedulerFactoryBean getSchedulerFactoryBean();
   Code:
      0: ldc           #12                 // String jobTrigger
      2: invokestatic  #19                 // Method cn/hutool/extra/spring/SpringUtil.getBean:(Ljava/lang/String;)Ljava/lang/Object;
      5: checkcast     #22                 // class org/quartz/impl/triggers/CronTriggerImpl
      8: astore_1
      9: new           #23                 // class org/springframework/scheduling/quartz/SchedulerFactoryBean
     12: dup
     13: invokespecial #24                 // Method org/springframework/scheduling/quartz/SchedulerFactoryBean."<init>":()V
     16: astore_2
     17: aload_2
     18: iconst_1
     19: anewarray     #25                 // class org/quartz/Trigger
     22: dup
     23: iconst_0
     24: aload_1
     25: aastore
     26: invokevirtual #26                 // Method org/springframework/scheduling/quartz/SchedulerFactoryBean.setTriggers:([Lorg/quartz/Trigger;)V
     29: aload_2
     30: areturn
  • 第2行: invokestatic指令调用静态方法实现获取bean
  • 第5行:checkcast 指令泛型检查返回结果是否可以强转为CronTriggerImpl对象,此时是没有问题的
  • 第26行:invokevirtual 指调用对象SchedulerFactoryBean#setTriggers方法并根据对象的实际类型分派处理,此时参数为CronTriggerImpl,根据多态满足参数条件,此时就不会报错

       根据修改前和修改后的两段代码虽然很相似,但是会发现执行字节码指令是不一样的,主要的处理方式就是需要显示的方式将泛型强转,以避免转换检查失败。 因此将代码修改成以下方式也是可以的

    @Bean("myScheduler")
    public SchedulerFactoryBean getSchedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setTriggers((CronTriggerImpl)SpringUtil.getBean("jobTrigger"));
        return schedulerFactoryBean;
    }

    

  前一篇:JVM记一次java.lang.NoSuchMethodError问题排查

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
最近刚学习了异常,下面就写一篇有关异常的知识点,与各位同仁分享! 既然学习异常,必不可少的就是要学习异常处理机制。通过该机制是程序中的业务代码和异常处理代码分离,从而使代码更加优雅,是程序员更专心于业务代码的编写! 下面通过实例说明异常发生的情况 和 如何的处理异常! 在com.jbit.array包中定义Array类; package com.jbit.array; public class Array { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int []arr=new int[5]; for (int i = 0; i < 8; i++) { arr = i + 1; } System.out.println(arr); } } 上面的代码因为我们在写程序时不小心把数组的长度写成了8,而我们定义的长度只有5.很明显数组下标越界了。那我们该怎么来修改这个错误呢,我们在一期的时候就学习过try-catch 那么我们就用try-catch来解决这个异常! package com.jbit.array; public class Array { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int []arr=new int[5]; try { for (int i = 0; i < 8; i++) { //通过循环,定义长度,让下标越界 arr = i + 1; } }catch(ArrayIndexOutOfBoundsException ex){ //下标越界的异常 //System.out.println(ex.getMessage()); System.out.println("下标越界了!!!"); } catch (Exception e) { //Exception 异常的根类 // TODO: handle exception //System.out.println(e.getMessage()); System.out.println("您的程序出现了异常,暂时还查不出什么异常!"); } //System.out.println(arr); } } 这时候有的同学又要问了,我们学习的时候如果出现了异常,catch 里面的参数直接用的是Exception类,而为什么这里已经有一个Exception类了,为什么还会出现一个ArrayIndexOutOfBoundsException类,这个问题问的好,因为我们写这段代码的时候不小心让下标越界了,而ArrayIndexOutOfBoundsException类就是声明下标越界的。ArrayIndexOutOfBoundsException类是Exception类的子类。这和我们前面学习继承的时候的父类和子类一样。这里就是父子类关系! 我们在写异常处理的时候一定要先写子类,最后在写父类。要是不小心先写父类,后面的子类程序是发现不到的。 如果程序在try代码块中出现了异常,那么出现异常之后的代码就不会执行,就直接进入catch代码块!如果不出现异常,那么catch块的代码就不会执行!但我们在写程序的时候往往希望能够把代码执行完毕,那么我们就可以在catch后面加上finall代码块,用来输出我们预想得到的效果!在这里我们必须要明白无论会不会出现异常,finally代码块里面的代码都是必须执行的,finally块不执行的唯一情况是:在异常处理代码中执行System.exit(1),将退出java虚拟机! 最后我写点我们在学习和工作的时候常见的异常类型:(这些类都是Exception类的子类) Exception 异常层次结构的根类 ArithmeticException 算数错误情形,如以零作除数 ArrayIndexOutOfBoundsException 数组下标越界 NullPointerException 尝试访问Null对象成员 ClassNotFoundException 不能加载所需的类 InputMismatchException 欲得到的数据类型与实际输入的类型不匹配 IllegalArgumentException 方法接收到非法参数 ClassCastException 对象强制类型转换出错 NumberFormatException 数字格式转换异常,如把“abc”转换成数字

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

=PNZ=BeijingL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值