burning 2016-01-30
这个博客介绍quartz集成spring,之前用的是spring3,既然现在spring4已经出了很久了,再学习的话当然用的是4,我这里使用的是最新的spring4.2.4.RELEASE,创建的maven项目的pom如下,其中spring中关于quartz的包都在spring-context-support这个包中。
<properties> <spring.version>4.2.4.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <exclusions> <exclusion> <!-- 不懂为啥依赖C3P0 --> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> </exclusion> </exclusions> <version>2.2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> </dependencies>
spring和quartz结合一共就五个主要的类
1、JobDetailFactoryBean
这个是创建jobDetail的。这个类很简单,就是使用了代理模式,将quartz正真的jobDetail设置作为一个属性,并通过其他的属性配置生成一个quartz的jobDetail,实现类是JobDetailImpl,
有一个关键的属性是:jobClass,这个表示job的类,可以通过反射来生成一个实例,生成的job实例的JobDataMap可以获得applicationContext的引用,引用是通过applicationContextJobDataKey来从JobDataMap中获取的。
但是对这个job类是有点限制的,必须继承自QuartzJobBean抽象类(这个类是实现了quartz的Job接口),然后执行的任务都会在executeInternal(JobExecutionContext context)方法中定义。
这个类允许在这个bean中获得applicationContext,通过这个属性applicationContext。其他的配置都很简单
所有的关键代码都在这个类的afterPropertiesSet方法中,顺便复习一下spring的知识,如果一个bean实现了InitailizingBean,那么这个bean在创建完成后会执行他的afterPropertiesSet方法,在spring容器关闭时调用destroy方法。
@Override @SuppressWarnings("unchecked") public void afterPropertiesSet() { if (this.name == null) { this.name = this.beanName;//如果没有指定name,则将这个bean的id或者name属性赋给他, } if (this.group == null) { this.group = Scheduler.DEFAULT_GROUP; } if (this.applicationContextJobDataKey != null) { if (this.applicationContext == null) { throw new IllegalStateException( "JobDetailBean needs to be set up in an ApplicationContext " + "to be able to handle an 'applicationContextJobDataKey'"); } getJobDataMap().put(this.applicationContextJobDataKey, this.applicationContext); } JobDetailImpl jdi = new JobDetailImpl(); jdi.setName(this.name); jdi.setGroup(this.group); jdi.setJobClass((Class) this.jobClass); jdi.setJobDataMap(this.jobDataMap); jdi.setDurability(this.durability); jdi.setRequestsRecovery(this.requestsRecovery); jdi.setDescription(this.description); this.jobDetail = jdi; }
我的测试中在spring的xml中的配置,和源码如下:
<!-- 创建jobDetail, --> <bean id="hellojob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="task.HelloJob"></property> <property name="name" value="hellojob"/> <property name="group" value="default" /> <property name="applicationContextJobDataKey" value="applicationContext" /> </bean>
我的Java代码
public class HelloJob extends QuartzJobBean{ ApplicationContext applicationContext = null; public ApplicationContext getApplicationContext() { return applicationContext; } public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { Object o = getApplicationContext().getBean("hellojob"); System.out.println(o == null); } }
2、MethodInvokingJobDetailFactoryBean
这个类也是产生jobDetail的bean,但是不同于上面那个需要继承自某个类或者是实现某个接口,这个类可以指定作为job的类和要调用的方法,然后通过反射的形式定时地执行这个方法,这也和名字MethodInvoking想匹配。
我在看这个类的源码时颇费了一番功夫,因为源码很长,而且有一个关键的类我没有吓到源码,不知道为何maven不给我下这个类的源码,不过幸运的是最终我将这个类的源码拿下,现在记录一下。
javadoc中关键记录:
1、这个类集成了methodInveoker的属性,比如setTargetObject,setTargetMethod,也可以设置target beanname(spring的applicationContext中的beanname)来替代targetObject,这样就避免了每一次都去创建一个新的实例。方法可以是静态方法或者是非静态方法。
2、支持并发的执行和非并发的执行,通过设置concurrent的boolean属性值,默认情况下是支持并发的(关于并发性我们在之前的quartz中已经提到了,我说的情况是每一次执行完成的时间大于间隔执行的时间)。通过这个类创建的jobDetail都是持久的,也就是即使这个工作执行完成后以后再也不会再执行了也不会被丢弃,而是存在jobStore中。关于这个的设置在源码中得到体现,下面我会贴出源码的。
类的属性和方法,很简单的属性我就放过了。
1、targetBeanName 当要执行的方法所在的类的实例在spring的applicationContext中时 ,指定这个bean的name(或者id),这个可以替代targetObject,这个不用每一次在执行时都创建一个实例,但是如果同时指定了targetObject或者是targetClass,则这个配置不会起作用。源码中的英文如下
/**
* Set the name of the target bean in the Spring BeanFactory.
* <p>This is an alternative to specifying {@link #setTargetObject "targetObject"},
* allowing for non-singleton beans to be invoked. Note that specified
* "targetObject" and {@link #setTargetClass "targetClass"} values will
* override the corresponding effect of this "targetBeanName" setting
* (i.e. statically pre-define the bean type or even the bean object).
*/
2、afterPropertiesSet 这个方法会在bean初始化完成后执行
里面显示调用了prepare方法,这个方法在其父类里面,是找到对应的方法和对象,不用管。
里面建立了JobDeatilImpl,需要注意的有两个,一个是jobClass,先把源码贴出来
// 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(); jdi.setName(name); jdi.setGroup(this.group); jdi.setJobClass((Class) jobClass); jdi.setDurability(true); jdi.getJobDataMap().put("methodInvoker", this); this.jobDetail = jdi;
当concurrent是true时,jobClass是methodInvokingJob,不是时是StateFulMethodInvoklingJob,其实这两个类是一样的,之不贵后者在类上面加了两个注解,就是我们之前提及的注解:@PersistJobDataAfterExecution @DisallowConcurrentExecution,表示不能并发执行,并且对Map的操作会影响JobDetail的map。这量个jobClass作为被调用时的job调用的方法都是MethodInvokingJo的executeInternal方法,因为StateFulMethodInvokingJob类是继承的MethodInvokingJob,这个方法的源码如下:
@Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { try { context.setResult(this.methodInvoker.invoke()); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof JobExecutionException) { // -> JobExecutionException, to be logged at info level by Quartz throw (JobExecutionException) ex.getTargetException(); } else { // -> "unhandled exception", to be logged at error level by Quartz throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException()); } } catch (Exception ex) { // -> "unhandled exception", to be logged at error level by Quartz throw new JobMethodInvocationFailedException(this.methodInvoker, ex); } }
在这个源码中,他是调用的methodInvoker.invoke方法,我们先不管这个方法是如何调用的,先来看一下什么是methodInvoker,在MethodInvokingJob中,根本就没有实例化MethodInvoker,怎么能调用呢?当时我也被难住了,直到我看到上面的在创建JobDetailImpl时,有这么一行代码
jdi.getJobDataMap().put("methodInvoker", this)
我突然想起来,原来他是通过调用时穿进去的methodInvoker实例(因为每一次调用都会建立一个job对象,并且执行某些setter方法,这个在之前已经说过),this就是代表的MethodInvokingJobDetailFactoryBean,他是MethodInvoker的子类,随意最终调用的就是这个MethodInvokingJobDetailFactoryBean实例的invoke方法,他并没有复写,所以使用的还是MethodInvoker中的invoke方法,这个方法执行的之前prepare方法中设定的targetObject targetMethod,使用的方式是反射。
至此我们有已经完全看懂了这个类的源码,写的非常的巧妙,但是同时很耗费时间。
我测试的spring的xml的配置和java代码
<bean id="hellojob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="methodInvokingJob"/> <property name="targetMethod" value="kaishigan"/> </bean> <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <property name="name" value="simpleTrigger"/> <property name="group" value="default"></property> <property name="repeatCount" value="3"></property> <property name="repeatInterval" value="2000"></property> <property name="startDelay" value="4000"></property> <property name="jobDetail" ref="hellojob"/> </bean> <bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="simpleTrigger"/> </list> </property> <property name="startupDelay" value="10"/><!-- 等待10秒之后才执行,让容器启动成功。单位是秒 --> </bean>
public class MethodInvokingJob { public void kaishigan(){ System.out.println("xxxx"); } }
上面的代码是我已经测试好的,但是在写完这么多之后我还是有疑问,在上面的xlm中,simpleTrigger这个bean的属性jobDetail我们指向的是hellojob这个bean,也就是methodInvokeingJobDetailFactoryBean 这个实例,但是methodInvokeingJobDetailFactoryBean类和jobDetail没有任何关系啊,谁都不是谁的子类,他怎么能够引用呢?如果有大神看到这里,请给我留言或者是加我qq,1902442871或者1308567317.
3、SimpleTriggerFactoryBean
这个类似于我们在quartz中创建SimpleTrigger的类,他创建的就是一个SimpleTriggerImpl类,在这个类的afterpropertiesSet方法中存在。
这个类的javadoc中是这么说的:SimpleTrigger已经是一个javabean的对象了,但是他缺乏合理的默认值,这个类(SimpleTriggerFactoryBean)提供了很多的缺省值:beanname是这个trigger的缺省的name值,quartz默认的group是缺省的group值,当前时间是开始时间,循环次数为无线次。
在这个类的afterPropertiesSet方法中,可以发现如果在trigger中设置了jobDetail的话,会将其放置到trigger的jobDataMap中,key为jobDetail
代码如下:
@Override public void afterPropertiesSet() { if (this.name == null) { this.name = this.beanName; } if (this.group == null) { this.group = Scheduler.DEFAULT_GROUP; } if (this.jobDetail != null) { this.jobDataMap.put("jobDetail", this.jobDetail); } if (this.startDelay > 0 || this.startTime == null) { this.startTime = new Date(System.currentTimeMillis() + this.startDelay); } SimpleTriggerImpl sti = new SimpleTriggerImpl(); sti.setName(this.name); sti.setGroup(this.group); sti.setJobKey(this.jobDetail.getKey()); sti.setJobDataMap(this.jobDataMap); sti.setStartTime(this.startTime); sti.setRepeatInterval(this.repeatInterval); sti.setRepeatCount(this.repeatCount); sti.setPriority(this.priority); sti.setMisfireInstruction(this.misfireInstruction); sti.setDescription(this.description); this.simpleTrigger = sti; }
这个类也很简单,类似于JobDetailFactoryBean,他比quartz原声的simpleTrigger多了些功能,比如可以获得spring的applicationcontext。
4、CronTriggerFactoryBean
这个类提供我们之前的CronTrigger的功能,他的配置和上面的simpleTrigger一样,这里直接略过,把我的xml配置和代码贴出来:
public class MethodInvokingJob { public void kaishigan(){ System.out.println("xxxx"); } }
<bean id="methodInvokingJob" class="task.MethodInvokingJob"/> <bean id="hellojob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="methodInvokingJob"/> <property name="targetMethod" value="kaishigan"/> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="cronExpression" value="*/2 * * * * ? *"/> <property name="jobDetail" ref="hellojob"/> </bean> <bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger"/> </list> </property> <property name="startupDelay" value="10"/><!-- 等待10秒之后才执行,让容器启动成功。单位是秒 --> </bean>
5、SchedulerFactoryBean
这个是spring给我们提供的类,提供的是quartz中的SchedulerFactory的功能。下面对这个类的源码做些记录:
1、这个类中含有SchedulerFactory属性,就是quartz中真正的SchedulerFactory,但是允许我们配置实现类,配置这个类的schedulerFactoryClass属性指定实现类的类型,默认是StdSchedulerFactory.class,配置configLocation属性指定配置文件的位置,如果不是StdSchedulerFactory的话,必须写这个,配置是直接配置quartzProperties这个属性。1
2、schedulerContextMap 这个属性用来保存一些属性,可以在job执行时获取。
3、applicationContextSchedulerContextKey 可以配置spring的applicationcontext在这个scheduler中的schedulerContext中可以获得,获得时的键就是这里指定的key,关于这点的内容可以在这个方法:populateSchedulerContext中仔细查看。
4、autoStartup 调度器是不是立刻启动,默认是true.
5、phase 我没明白这个属性的意思,javadoc如下:/**
* Specify the phase in which this scheduler should be started and
* stopped. The startup order proceeds from lowest to highest, and
* the shutdown order is the reverse of that. By default this value
* is Integer.MAX_VALUE meaning that this scheduler starts as late
* as possible and stops as soon as possible.
*/
6、startupDelay 这个属性很重要,默认是0.表示只要这个SchedulerFactoryBean一创建,就要执行(当然前提是autostart是true),最好设置一些值,比如10秒以等待整个application启动完成,除非我们有特殊情况。单位是秒,不是毫秒,在这个类的这个方法中有提到:startScheduler
7、waitForJobsToCompleteOnShutdown 在停止spring的applicationcontext时要不要等待任务执行完成,默认是false。
8、applicationContext 用来指向spring的applicationContext。
9、这个方法比较值得一看:start方法,如果指定的startDelay合理的话,操作的明细如下:
protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException { if (startupDelay <= 0) { logger.info("Starting Quartz Scheduler now"); scheduler.start(); } else { if (logger.isInfoEnabled()) { logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + "] in " + startupDelay + " seconds"); } Thread schedulerThread = new Thread() { //新开启一个线程,异步处理 @Override public void run() { try { Thread.sleep(startupDelay * 1000);//可以看出,startupDelay的单位是秒。 } catch (InterruptedException ex) { // simply proceed } if (logger.isInfoEnabled()) { logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds"); } try { scheduler.start(); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler after delay", ex); } } }; schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]"); schedulerThread.setDaemon(true); schedulerThread.start(); } }