项目场景:
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问题排查
转载请注明:ClassCastException: XXX are in unnamed module of loader ‘app‘异常分析 | 胖虎的工具箱-编程导航