一、异常重现
1、测试条件
1.1、quartz版本:1.8.6
1.2、spring版本:4.2.6.RELEASE
2、定义接口
public interface ServiceI {
void getMsg();
}
3、定义任务实现类
@Service("serviceImpl")
public class ServiceImpl implements ServiceI {
@Override
public void getMsg() {
System.out.println("hello world! i'm invoking!");
}
}
4、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
default-lazy-init="false">
<!-- 开启注解支持,并扫描指定包下的注解 -->
<context:annotation-config />
<context:component-scan base-package="com.chhliu.myself.quartz.spring.impl"></context:component-scan>
<!-- 配置需要定时执行的任务类以及方法 -->
<bean id="doJob"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 指定任务类 -->
<property name="targetObject" ref="serviceImpl" />
<!-- 指定任务执行的方法 -->
<property name="targetMethod" value="getMsg" />
</bean>
<!-- 配置触发器 -->
<bean id="jobTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="doJob" />
<!-- 每10秒运行一次 -->
<property name="cronExpression" value="0/10 * * * * ?" /><!-- 每隔10秒钟触发一次 -->
</bean>
<!-- 触发定时任务 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="jobTrigger" /><!-- 此处可以配置多个触发器 -->
</list>
</property>
</bean>
</beans>
5、测试结果
Caused by: java.lang.ClassNotFoundException: org.quartz.impl.JobDetailImpl
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
... 21 more
发现出现了这个异常。
6、异常原因分析
spring4.1.x版本后,MethodInvokingJobDetailFactoryBean类发生了改变,在初始化JobDetail的时候,需要new JobDetailImpl类,代码如下:
public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
prepare();
// Use specific name if given, else fall back to bean name.
String name = (this.name != null ? this.name : this.beanName);
// Consider the concurrent flag to choose between stateful and stateless job.
Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
// Build JobDetail instance.
JobDetailImpl jdi = new JobDetailImpl();// 此处需要JobDetailImpl类,但是quartz-1.8.6版本中又没有这个类,因此抛异常
jdi.setName(name);
jdi.setGroup(this.group);
jdi.setJobClass((Class) jobClass);
jdi.setDurability(true);
jdi.getJobDataMap().put("methodInvoker", this);
this.jobDetail = jdi;
postProcessJobDetail(this.jobDetail);
}
spring3.2.x版本中的初始化代码如下:
static {
try {// 兼容quartz2.x版本
jobDetailImplClass = Class.forName("org.quartz.impl.JobDetailImpl");
}
catch (ClassNotFoundException ex) {
jobDetailImplClass = null;
}
try {// 兼容quartz1.x版本
Class jobExecutionContextClass =
QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext");
setResultMethod = jobExecutionContextClass.getMethod("setResult", Object.class);
}
catch (Exception ex) {
throw new IllegalStateException("Incompatible Quartz API: " + ex);
}
}
public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
prepare();
// Use specific name if given, else fall back to bean name.
String name = (this.name != null ? this.name : this.beanName);
// Consider the concurrent flag to choose between stateful and stateless job.
Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
// Build JobDetail instance.
if (jobDetailImplClass != null) {
// Using Quartz 2.0 JobDetailImpl class...
// 如果是quartz2.x版本,注意此处的instantiate的类型,是Class<>类型,这是因为在Quartz2.x版本后,JobDetail由类变成了接口
this.jobDetail = (JobDetail) BeanUtils.instantiate(jobDetailImplClass);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this.jobDetail);
bw.setPropertyValue("name", name);
bw.setPropertyValue("group", this.group);
bw.setPropertyValue("jobClass", jobClass);
bw.setPropertyValue("durability", true);
((JobDataMap) bw.getPropertyValue("jobDataMap")).put("methodInvoker", this);
}
else {
// Using Quartz 1.x JobDetail class...
this.jobDetail = new JobDetail(name, this.group, jobClass);
this.jobDetail.setVolatility(true);
this.jobDetail.setDurability(true);
this.jobDetail.getJobDataMap().put("methodInvoker", this);
}
// Register job listener names.
if (this.jobListenerNames != null) {
for (String jobListenerName : this.jobListenerNames) {
if (jobDetailImplClass != null) {
throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " +
"manually register a Matcher against the Quartz ListenerManager instead");
}
this.jobDetail.addJobListener(jobListenerName);
}
}
postProcessJobDetail(this.jobDetail);
}
总结如下:
导致这个异常的根本原因是因为使用的quartz版本和spring版本不兼容造成的,在二者集成的时候,需要注意对应的版本。
其实在spring4.1.x的源码中已经有对应的说明了,原文如下:
Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
7、异常修复
7.1、quartz版本:2.2.0
7.2、spring版本:4.2.6.RELEASE
7.3、正确结果
hello world! i'm invoking!
2017-01-04 14:00:10 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job DEFAULT.doJob
hello world! i'm invoking!
2017-01-04 14:00:10 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
2017-01-04 14:00:20 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job DEFAULT.doJob
hello world! i'm invoking!
2017-01-04 14:00:20 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
2017-01-04 14:00:30 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-5] DEBUG org.quartz.core.JobRunShell - Calling execute on job DEFAULT.doJob
2017-01-04 14:00:30 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
hello world! i'm invoking!