以下基本是在配置中遇到或考虑到的问题 末尾附代码
做之前先考虑好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);
}