Spring中使用Quartz
1.配置JobDetail
Spring提供了两种配置JobDetail的配置,官方示例如下:
-
方式一
<!-- JobDetail配置 1 -->
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
jobClass属性
通过jobClass配置一个Job实现类,该实现类需要实现Job接口。
直接实现Job接口
public class SimpleJob implements Job {
private static Logger _log = LoggerFactory.getLogger(SimpleJob.class);
/**
* Quartz requires a public empty constructor so that the
* scheduler can instantiate the class whenever it needs.
*/
public SimpleJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
// This job simply prints out its job name and the
// date and time that it is running
JobKey jobKey = context.getJobDetail().getKey();
_log.info("SimpleJob says: " + jobKey + " executing at " + new Date());
}
}
继承QuartzJobBean方式,该QuartzJobBean实现了Job接口(Spring推荐直接继承QuartzJobBean类)
源码如下,QuartzJobBean只是做了一层封装,然后将JobDataMap中配置的属性设置到Job的Bean中
public abstract class QuartzJobBean implements Job {
/**
* This implementation applies the passed-in job data map as bean property
* values, and delegates to {@code executeInternal} afterwards.
* @see #executeInternal
*/
@Override
public final void execute(JobExecutionContext context) throws JobExecutionException {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(context.getScheduler().getContext());
pvs.addPropertyValues(context.getMergedJobDataMap());
bw.setPropertyValues(pvs, true);
}
catch (SchedulerException ex) {
throw new JobExecutionException(ex);
}
executeInternal(context);
}
/**
* Execute the actual job. The job data map will already have been
* applied as bean property values by execute. The contract is
* exactly the same as for the standard Quartz execute method.
* @see #execute
*/
protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
//在executeInternal中进行实际业务逻辑
}
}
jobDataAsMap属性(只有继承QuartzJobBean的方式实现Job才可以被直接赋值给job的字段,否则需要自己获取并赋值)
在jobDataAsMap配置了一个map,有一个
<entry key="timeout" value="5"/>
可以通过这种方式直接向Job的属性中传递值,如上例中的的timeout属性
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
然后在executeInternal方法中就可以直接使用timeout的值了,如下:
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println(timeout);
}
-
方式二
<!-- JobDetail配置 2 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
<property name="concurrent" value="false"/>
</bean>
JobDetail配置 2
官方介绍如下:Often you just need to invoke a method on a specific object. Using the MethodInvokingJobDetailFactoryBean you can do exactly this。按照官方的意思,就是说简单情况下,如果你的定时任务只是需要调用某个Spring对象的某个方法,可以用这个配置来简单配置下,concurrent设置为false可以防止任务并发执行。这种配置在简单的情况下可用,但是功能不强,用处不大。
2.配置Trigger
Quartz中常用的Trigger只有两种,官方示例如下:
<!-- trigger 1 -->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<!-- trigger 2 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
- SimpleTriggerFactoryBean为每个一段时间执行一次的计划任务
源码、类级别的说明及属性如下
一个用于创建Quartz SimpleTrigger实例的Spring FactoryBean,它支持bean风格的触发器配置用法。
SimpleTrigger实现类本身已经是JavaBean,但是缺少合理的默认值。此类使用Spring Bean名称作为作业名称,使用Quartz默认组(“ DEFAULT”)作为作业组,将当前时间作为开始时间,并使用不确定的重复次数(如果未指定)
该类还将使用给定JobDetail的作业名称和组来注册触发器。 这允许SchedulerFactoryBean自动为相应的JobDetail注册触发器,而不是分别注册JobDetail。
public class SimpleTriggerFactoryBean implements FactoryBean<SimpleTrigger>, BeanNameAware, InitializingBean {
//指定trigger的名称
private String name;
//指定trigger的组
private String group;
//设置trigger关联的JobDetail
private JobDetail jobDetail;
//设置trigger的JobDataMap,与JobDetail数据图中的对象相反,这些对象仅可用于此trigger。
private JobDataMap jobDataMap = new JobDataMap();
//设置trigger的开始时间
private Date startTime;
//设置延迟的毫秒数,启动延迟会添加到当前系统时间(bean启动时),以控制触发器的启动时间。
private long startDelay;
//指定此触发器的执行时间之间的间隔,单位为毫秒。
private long repeatInterval;
//指定该触发器应触发的次数,-1表示无穷次。
private int repeatCount = -1;
//指定此触发器的优先级。
private int priority;
//为此触发器指定一个失火指令
private int misfireInstruction;
private String description;
private String beanName;
private SimpleTrigger simpleTrigger;
- cronTrigger是用cron表达式来执行定时任务
源码及属性如下
public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNameAware, InitializingBean {
private String name;
private String group;
private JobDetail jobDetail;
private JobDataMap jobDataMap = new JobDataMap();
private Date startTime;
private long startDelay = 0;
//cron表达式
private String cronExpression;
//时区
private TimeZone timeZone;
private String calendarName;
private int priority;
private int misfireInstruction;
private String description;
private String beanName;
private CronTrigger cronTrigger;
3.配置scheduler
Quartz中自带的scheduler有两种,其中一种是做测试用的,也就是说只有一个是实际开发用的,所以Spring相应的只推荐一种配置,示例如下:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
SchedulerFactoryBean部分源码如下及类级别的注释说明
- FactoryBean创建并配置Quartz Scheduler,将其生命周期作为Spring应用程序上下文的一部分进行管理,并将Scheduler作为bean引用公开给依赖项注入。
- 允许注册JobDetails,Calendars和Triggers,在初始化时自动启动调度程序,并在销毁时将其关闭。在仅需要在启动时静态注册作业的情况下,无需在应用程序代码中访问Scheduler实例本身。
- 为了在运行时动态注册作业,请使用对此SchedulerFactoryBean的Bean引用来直接访问Quartz Scheduler。 这使您可以创建新的作业和触发器,还可以控制和监视整个Scheduler。
- 请注意,与使用在重复执行之间共享的TimerTask实例的Timer相比,Quartz为每个执行实例化一个新的Job。 只是JobDetail描述符是共享的。
- 当使用持久性作业时,强烈建议在Spring管理的(或普通JTA)事务中对Scheduler执行所有操作,否则数据库锁定将无法正常工作甚至可能中断
public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
......
//设置要通过SchedulerFactory创建的Scheduler的名称。如果未指定,则将Bean名称用作默认的Scheduler名称。
private String schedulerName;
//quartz.properties配置文件路径
private Resource configLocation;
private Properties quartzProperties;
private Executor taskExecutor;
//
private DataSource dataSource;
private DataSource nonTransactionalDataSource;
private Map<String, ?> schedulerContextMap;
private ApplicationContext applicationContext;
private String applicationContextSchedulerContextKey;
private JobFactory jobFactory;
private boolean jobFactorySet = false;
private boolean autoStartup = true;
private int startupDelay = 0;
private int phase = Integer.MAX_VALUE;
private boolean exposeSchedulerInRepository = false;
private boolean waitForJobsToCompleteOnShutdown = false;
private Scheduler scheduler;
......
SchedulerFactoryBean继承的父类SchedulerAccessor 部分源码如下
public abstract class SchedulerAccessor implements ResourceLoaderAware {
protected final Log logger = LogFactory.getLog(getClass());
private boolean overwriteExistingJobs = false;
private String[] jobSchedulingDataLocations;
private List<JobDetail> jobDetails;
private Map<String, Calendar> calendars;
private List<Trigger> triggers;
private SchedulerListener[] schedulerListeners;
private JobListener[] globalJobListeners;
private TriggerListener[] globalTriggerListeners;
private PlatformTransactionManager transactionManager;
protected ResourceLoader resourceLoader;
防止Quartz任务并行
1.针对MethodInvokingJobDetailFactoryBean类
按照Spring的官方文档,只介绍说MethodInvokingJobDetailFactoryBean(请参看教程前面相关部分)有一个concurrent属性可以用来控制任务是否可以并行。
MethodInvokingJobDetailFactoryBean类的部分源码如下图,可以看到里面有一个concurrent属性,因此可以通过配置该属性来控制是否并发。
由于MethodInvokingJobDetailFactoryBean功能太弱,实际开发中用不多。
2.针对JobDetailFactoryBean类
concurrent属性是MethodInvokingJobDetailFactoryBean独有的,JobDetailFactoryBean是没有这个属性的,所以是无法给JobDetailFactoryBean配置concurrent属性的。
通过@DisallowConcurrentExecution注解防止JobDetailFactoryBean任务并行,如下:
package example;
//只需要在Job类上加上这个注解即可
@DisallowConcurrentExecution
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
在任务中获取Spring管理Bean
我们在上面的JobDetailFactoryBean类源码中,发现了这个类实现了ApplicationContextAware这个接口,关于这个类可以参考我的另一篇博客ApplicationContextAware使用理解
我们通过ContextLoader类的getCurrentWebApplicationContext()方法获取spring的WebApplicationContext,然后再通过相应获取Bean的方法获取Bean。
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
userCorporateAttachService = webApplicationContext.getBean(xxxxx.class);