Quartz分布式模式与Spring集成、执行定时任务、防止任务并行、在任务中获取Spring管理的Bean

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);

扩展阅读

quartz 2.3.0 学习笔记

quartz2.3.0系列目录——带您由浅入深全面掌握quartz2.3.0

发布了212 篇原创文章 · 获赞 135 · 访问量 137万+

猜你喜欢

转载自blog.csdn.net/ystyaoshengting/article/details/103779750