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

2年前 (2022) 程序员胖胖胖虎阿
415 0 0

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问题排查

相关文章

暂无评论

暂无评论...