Quartz与Spring整合

发布于 3287 字 7 分钟 0 Java QuartzSpringJava

以下基本是在配置中遇到或考虑到的问题 末尾附代码

做之前先考虑好Quartz的Job,JobGroud,Trigger,TriggerGroup之间的关系

Job与Trigger之间是一对多的关系 Job与JobGroud,Trigger与TriggerGroup组合在一起是唯一的

执行任务无法注入Service接口

方式一:

通过Spring的ClassPathXmlApplicationContext获取配置文件来得到bean

protected Object getBean(String beanName) {
	return new ClassPathXmlApplicationContext("spring.xml", "spring-mybatis.xml").getBean(beanName);
}

调用:

// testService必须在bean中扫描有
TestService testService = this.getBean("testService");	

方式二:

import org.quartz.spi.TriggerFiredBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
 * 解决Spring quartz Job不能依赖注入 <br/>
 * Create Date: 3/20/18 10:09 PM <br/>
 * 
 * @author Peter Guo
 * @version 0.1
 **/
public class CustomJobFactory extends SpringBeanJobFactory {

    /**
     * slf4j日志记录
     */
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    /**
     * 覆盖super的createJobInstance方法,对其创建出来的类再进行autowire。
     * @param bundle
     * @return
     * @throws Exception
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        logger.debug("SpringBeanJobFactory 开始注入==============================================");
        //进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

在XML中配置,执行任务中直接调用注解即可

<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" autowire="no">
    <property name="jobFactory">
        <!-- 在job定时任务中调用service注解无效解决方案 -->
        <bean class="com.refill.base.job.CustomJobFactory"/>
    </property>
</bean>

Quartz如何交给Spring托管

Quartz的配置

##配置见:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering.html
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
#可为任何值,用在jdbc jobstrore中来唯一标识实例,但是在所有集群中必须相同
org.quartz.scheduler.instanceName=scheduler
#AUTO即可,基于主机名和时间戳来产生实例ID
#集群中的每一个实例都必须有一个唯一的"instance id",应该有相同的"scheduler instance name"
org.quartz.scheduler.instanceId=AUTO
#禁用quartz软件更新
org.quartz.scheduler.skipUpdateCheck:true
#============================================================================
# Configure ThreadPool 执行任务线程池配置
#============================================================================
#线程池类型,执行任务的线程
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 指定多少个线程被创建用来处理 Job
org.quartz.threadPool.threadCount=25
#设置工作者线程的优先级(最大值10,最小值1,常用值5)
org.quartz.threadPool.threadPriority=5
# 加载任务代码的ClassLoader是否从外部继承
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread:true
#============================================================================
# Configure JobStore  任务存储方式
#============================================================================
org.quartz.jobStore.misfireThreshold=60000
# 默认配置,数据保存到内存(调度程序信息是存储在被分配给JVM的内存里面,运行速度快)
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#可以设置两种属性,存储在内存的RAMJobStore和存储在数据库的JobStoreSupport
#(包括JobStoreTX和JobStoreCMT两种实现JobStoreCMT是依赖于容器来进行事务的管理,而JobStoreTX是自己管理事务)
#这里的属性为 JobStoreTX,将任务持久化到数据中。
#因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用Quartz 集群。
#这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。
#org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
# 使用一个驱动代理来操作 trigger 和 job 的数据存储
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#若要设置为true,则将JobDataMaps中的值当作string
org.quartz.jobStore.useProperties=true
# 数据库类型  与spring 结合不需要
#org.quartz.jobStore.dataSource=myDS
#数据库中quartz表的表名前缀   与spring 结合不需要
#org.quartz.jobStore.tablePrefix=QRTZ_
#告诉了Scheduler实例要它参与到一个集群当中。这一属性会贯穿于调度框架的始终,用于修改集群环境中操作的默认行为。
org.quartz.jobStore.isClustered=true
#属性定义了Scheduler实例检入到数据库中的频率(单位:毫秒)。默认值是 15000 (即15 秒)。
org.quartz.jobStore.clusterCheckinInterval=20000
#这是 JobStore 能处理的错过触发的 Trigger 的最大数量。
#处理太多(超过两打) 很快会导致数据库表被锁定够长的时间,这样就妨碍了触发别的(还未错过触发) trigger 执行的性能。
org.quartz.jobStore.maxMisfiresToHandleAtATime = 1
org.quartz.jobStore.txIsolationLevelSerializable = false

#============================================================================
# Configure Datasources 数据源配置,与spring结合不需要这个配置
#============================================================================
#org.quartz.dataSource.myDS.connectionProvider.class:com.refill.base.job.DruidConnectionProvider
#org.quartz.dataSource.myDS.driver=org.mariadb.jdbc.Driver
#org.quartz.dataSource.myDS.URL=jdbc:mariadb://localhost:3306/refill_quartz?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
#org.quartz.dataSource.myDS.user=root
#org.quartz.dataSource.myDS.password=guoph
#org.quartz.dataSource.myDS.maxConnections=20
#org.quartz.dataSource.myDS.validationQuery=select 0 from dual

#============================================================================
# Configure Plugins
#============================================================================
#当事件的JVM终止后,在调度器上也将此事件终止
#the shutdown-hook plugin catches the event of the JVM terminating, and calls shutdown on the scheduler.
org.quartz.plugin.shutdownHook.class: org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownHook.cleanShutdown: true
#记录trigger历史的日志插件
#org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingJobHistoryPlugin

Spring配置

<!-- 定时任务配置 start -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" autowire="no">
    <!-- 获取quartz配置文件 -->
    <property name="configLocation" value="classpath:spring-quartz.properties" />
    <property name="jobFactory">
        <!-- 在job定时任务中调用service注解无效解决方案 -->
        <bean class="com.refill.base.job.CustomJobFactory"/>
    </property>
    <!-- scheduler的名称 -->
    <property name="schedulerName" value="scheduler"/>
    <!--使用配置的数据源-->
    <property name="dataSource" ref="dataSource"/>
    <!--使用spring的事务管理-->
    <property name="transactionManager" ref="txManager"/>
    <!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了-->
    <property name="overwriteExistingJobs" value="true" />
    <!-- 必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动  -->
    <property name="startupDelay" value="5" />
    <!-- 设置自动启动  -->
    <property name="autoStartup" value="true" />
    <!-- 把spring上下 文以key/value的方式存放在了quartz的上下文中了 -->
    <property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
</bean>

注意!!!!!! 在类中一定要使用这个scheduler,否则无法托付给Spring管理

方式一: 在可以扫描到的类中,如在Service接口的实现类中直接添加Scheduler就可以了

@Autowired
private Scheduler scheduler;

方式二: 如果是在Spring中扫描不到的类中,那么在Spring添加bean进行引入即可

<!-- 配置scheduler -->
<bean class="com.refill.basic.bean.JobBeanUtil">
    <property name="scheduler" ref="scheduler"/>
</bean>
public class JobBeanUtil {

//    @Autowired
//    private Scheduler scheduler;

    //静态
    private static Scheduler scheduler;
    public void setScheduler(Scheduler scheduler) {
        JobBeanUtil.scheduler = scheduler;
    }
}

如何对Quartz进行管理

主要有中考虑,如果公司中使用的定时器数量较少,且短时间内不会有很多定时器任务的考虑,可以使用第一种,如果长远考虑,方便后期的管理和维护

短期

直接对quartz的表进行操作,可以进行基本的添删改查,但是,仅适用于定时器任务不复杂的情况,该方法会将所有的任务一次性全部查询出来 仅需要添加job名和执行类名,一级cron表达式,jobGroup和triggerGroup采取默认,triggerName使用jobName. 大约只需要3个类文件,一张bean做实体,一张工具类做与bean相关的方法(也可放到Service层),一个Controller提供给页面

GitHub quartz1

/**
 * 获取所有任务
 *
 * @return list
 * @throws SchedulerException
 */
public static List<JobBean> getAllJobs() throws SchedulerException {
    GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
    Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
    List<JobBean> jobList = new ArrayList<>();
    //一个任务可以有多个触发器
    for (JobKey jobKey : jobKeys) {
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        for (Trigger trigger : triggers) {
            jobList.add(getJobBean( jobKey, trigger.getKey()));
        }
    }
    return jobList;
}

长远

适用于定时任务复杂,数量较多. 如实现卖家自定义商品限时抢购,定义限时限价等等,这就意味这对jobGroup,trigger进行更多的使用,所以这里的思路是在数据库中建立三张表,一张job,一张trigger,一张group,方便后期拓展,如控制到用户,对job添加多个trigger.

将Quartz持久到数据库时,Spring无法管理Quartz事务

这里我到现在还不是很明白,因为我在Spring配置文件中SchedulerFactoryBean中已经将quartz托管给Spring了,但还是当我在service中一个方法内添加完quartz后再插入到另一张表报错时,quartz竟然没有回滚,后来没办法只能修改类的事务的传播行为为REQUIRES_NEW, 才可以正常回滚. ###方案一

/**
* xml中spring的事务管理器
*/
@Resource(name = "txManager")
private DataSourceTransactionManager transactionManager;

/**
 * 添加单个工作
 *
 * @param jobEntity 调度实体
 * @param paramMap 在execute中获取到的参数
 * @return ResultJsonBean
 */
@Override
public ResultJsonBean saveEntity(JobEntity jobEntity, Map<String, Object> paramMap){
    if (jobDao.getEntityByJobName(jobEntity.getJobName()) != null) {
        return new ResultJsonBean(null, false, "任务名称已经存在,不要重复添加!!!!");
    }
    if (jobDao.getEntityByTriggerName(jobEntity.getJobTriggerName()) != null) {
        return new ResultJsonBean(null, false, "触发器名称已经存在,不要重复添加!!!!");
    }
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
    TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
    try {
        jobEntity.setJobTriggerStatus(JobTriggerStatusEnum.OPEN.toInt());
        QuartzUtil.addJob(scheduler, jobEntity, paramMap);
        jobDao.saveEntity(jobEntity);
        transactionManager.commit(status);
        return new ResultJsonBean(null, jobEntity, true);
    } catch (Exception e) {
        QuartzUtil.shutdownJobs(scheduler);
        transactionManager.rollback(status);
        QuartzUtil.startJobs(scheduler);
        return new ResultJsonBean(null, false, "添加计划任务失败,程序异常,请连续管理员!!!");
    }
}

###方案二 使用Spring事务注解


/**
 * 添加单个工作
 *
 * @param jobEntity 调度实体
 * @param paramMap 在execute中获取到的参数
 * @return ResultJsonBean
 */
@Override
@Transactional(value = "txManager",propagation =Propagation.REQUIRES_NEW)
public ResultJsonBean saveEntity(JobEntity jobEntity, Map<String, Object> paramMap){
    if (jobDao.getEntityByJobName(jobEntity.getJobName()) != null) {
        return new ResultJsonBean(null, false, "任务名称已经存在,不要重复添加!!!!");
    }

    if (jobDao.getEntityByTriggerName(jobEntity.getJobTriggerName()) != null) {
        return new ResultJsonBean(null, false, "触发器名称已经存在,不要重复添加!!!!");
    }
    QuartzUtil.addJob(scheduler, jobEntity, paramMap);
    jobEntity.setJobTriggerStatus(JobTriggerStatusEnum.OPEN.toInt());
    jobDao.saveEntity(jobEntity);
    return new ResultJsonBean(null, jobEntity, true);
}