前面3篇文章介绍了quartz简单的使用以及quartz核心类的作用,这一篇文章主要介绍quartz在项目中的使用。
这篇文章是我之前在项目中的应用,有些业务相对比较复杂,大家看看就好。
这一篇对于quartz的一些类和接口的使用方法可能和上一篇不太一样,但是稍微理解一下那个类的作用是什么,相信大家就可以理解这么做的意义了。附上一篇链接:Quartz核心类详解
1.自定义注解JobUnit
我们通过自定义注解的方法,来完成job的一些信息的初始化,比如cron,job名称和分组
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JobUnit {
/**
* @return
*/
String jobName() default "";
String jobGroup() default "";
String jobDesc() default "";
String jobCorn() default "";
int misfireInstruction() default CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING;
/**
* 是否单例,单例情况下以代码配置为主,系统无法修改 jobName+jobGroup,防止生成多个同任务定时器
*
* @return
*/
boolean singleton() default true;
}
2.任务配置单元数据QuartzJobUnit
这个类主要是用来存储job对象的一些信息,方便在QuartzJobManage中使用
public class QuartzJobUnit implements Serializable {
private static final long serialVersionUID = 7669523527816564621L;
/**
* 任务名称
*/
private String jobName;
/**
* 任务分组
*/
private String jobGroup;
/**
* 任务表达式
*/
private String jobCorn;
/**
* 任务描述
*/
private String description;
/**
* 存活时间,单位秒
*/
private long surviveSecond;
/**
* 扩展单元数据
*/
private Map<String, Object> extraUnit;
/**
* 丢失补仓策略
*/
private int misfireInstruction = CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING;
//省略get、set
}
3.定义Job任务QuartzJob1
这里我们定义一个简单的job任务,名称为QuartzJob1,分组为QuartzJob,设置为每5秒执行一次
@JobUnit(jobName = "QuartzJob1", jobGroup = "QuartzJob", jobCorn = "*/5 * * * * ?", jobDesc = "Quartz学习")
public class QuartzJob1 implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobKey key = jobExecutionContext.getJobDetail().getKey();
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String jobMsg = jobDataMap.getString("jobMsg");
String triggerMsg = jobDataMap.getString("triggerMsg");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(jobMsg + "=====" + triggerMsg + sdf.format(new Date()));
}
}
4.定义调度监听器QuartzCustomSchedulerListener
用来在Scheduler 的生命周期中有关键事件发生时记录日志或者做一些其他事情。
/**
* 自定义定时任务调度监听器
* 监控任务情况
*
* @Author: yhx
* @Date: 2019/12/9 23:26 下午
*/
public class QuartzCustomSchedulerListener implements SchedulerListener {
private static final Logger LOG = LoggerFactory.getLogger(QuartzCustomSchedulerListener.class);
/**
* 任务被部署时被执行
*
* @param trigger
*/
@Override
public void jobScheduled(Trigger trigger) {
LOG.info("SchedulerListener监听器:任务 [{}] 被部署", trigger.toString());
}
/**
* 任务被卸载时被执行
*
* @param triggerKey
*/
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
LOG.info("SchedulerListener监听器:任务 [{}] 被卸载", triggerKey.toString());
}
/**
* 任务完成了它的使命,光荣退休时被执行
*
* @param trigger
*/
@Override
public void triggerFinalized(Trigger trigger) {
LOG.info("SchedulerListener监听器:任务 [{}] 结束", trigger.toString());
}
/**
* 一个触发器被暂停时被执行
*
* @param triggerKey
*/
@Override
public void triggerPaused(TriggerKey triggerKey) {
LOG.info("SchedulerListener监听器:任务 [{}] 被暂停", triggerKey.toString());
}
/**
* 所在组的全部触发器被停止时被执行
*
* @param triggerGroup
*/
@Override
public void triggersPaused(String triggerGroup) {
LOG.info("SchedulerListener监听器:任务组 [{}] 被暂停", triggerGroup);
}
/**
* 一个触发器被恢复时被执行
*
* @param triggerKey
*/
@Override
public void triggerResumed(TriggerKey triggerKey) {
LOG.info("SchedulerListener监听器:任务 [{}] 被恢复", triggerKey.toString());
}
/**
* 所在组的全部触发器被回复时被执行
*
* @param triggerGroup
*/
@Override
public void triggersResumed(String triggerGroup) {
LOG.info("SchedulerListener监听器:任务组 [{}] 被恢复", triggerGroup);
}
/**
* 一个JobDetail被动态添加进来
*
* @param jobDetail
*/
@Override
public void jobAdded(JobDetail jobDetail) {
}
/**
* 删除时被执行
*
* @param jobKey
*/
@Override
public void jobDeleted(JobKey jobKey) {
LOG.info("SchedulerListener监听器:任务 [{}] 被删除", jobKey.toString());
}
/**
* 暂停时被执行
*
* @param jobKey
*/
@Override
public void jobPaused(JobKey jobKey) {
LOG.info("SchedulerListener监听器:任务 [{}] 被暂停", jobKey.toString());
}
/**
* 一组任务被暂定时执行
*
* @param jobGroup
*/
@Override
public void jobsPaused(String jobGroup) {
LOG.info("SchedulerListener监听器:任务组 [{}] 被暂停", jobGroup);
}
/**
* 恢复时被执行
*
* @param jobKey
*/
@Override
public void jobResumed(JobKey jobKey) {
LOG.info("SchedulerListener监听器:任务 [{}] 被恢复", jobKey.toString());
}
/**
* 一组被恢复时执行
*
* @param triggerGroup
*/
@Override
public void jobsResumed(String triggerGroup) {
LOG.info("SchedulerListener监听器:任务组 [{}] 被恢复", triggerGroup);
}
/**
* 出现异常时执行
*
* @param jobGroup
* @param e
*/
@Override
public void schedulerError(String jobGroup, SchedulerException e) {
LOG.error("SchedulerListener监听器:jobGroup [{}] deal error: {}", jobGroup, getStackTraceMsg(e.getStackTrace()));
}
/**
* scheduler被设为standBy等候模式时被执行
*/
@Override
public void schedulerInStandbyMode() {
}
/**
* scheduler启动时被执行
*/
@Override
public void schedulerStarted() {
}
/**
* scheduler正在启动时被执行
*/
@Override
public void schedulerStarting() {
}
/**
* scheduler关闭时被执行
*/
@Override
public void schedulerShutdown() {
}
/**
* scheduler正在关闭时被执行
*/
@Override
public void schedulerShuttingdown() {
}
/**
* scheduler中所有数据包括jobs, triggers和calendars都被清空时被执行
*/
@Override
public void schedulingDataCleared() {
}
/**
* 日志栈输出
*
* @param stackTraceElements
* @return
*/
public String getStackTraceMsg(StackTraceElement[] stackTraceElements) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement stackTraceElem : stackTraceElements) {
sb.append(stackTraceElem.toString() + "\n");
}
return sb.toString();
}
}
5.定义任务监听器JobMonitorListener
由 Job 在其生命周期中产生的某些关键事件时被调用,可以进行日志记录或者一些操作。
public class JobMonitorListener implements JobListener {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
private static final Logger LOGGER = LoggerFactory.getLogger(JobMonitorListener.class);
@Override
public String getName() {
return "JobListenerName";
}
/**
* 定时任务开始执行
*
* @param context
*/
@Override
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().toString();
long startTime = System.currentTimeMillis();
TIME_THREADLOCAL.set(System.currentTimeMillis());
String executeTime = DateFormatUtils.format(new Date(startTime), "yyyy-MM-dd HH:mm:ss");
LOGGER.info("Job [{}] is going to start! BeginTime : [{}]", jobName, executeTime);
}
/**
* 定时任务被否决
*
* @param context
*/
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
String executeTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
String jobName = context.getJobDetail().getKey().toString();
LOGGER.warn("Job [{}] is vetoed, DateTime: {}", jobName, executeTime);
}
/**
* 定时任务执行完毕
*
* @param context
* @param jobException
*/
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
long endTime = System.currentTimeMillis();
long costTime = System.currentTimeMillis() - TIME_THREADLOCAL.get();
String jobName = context.getJobDetail().getKey().toString();
String executeTime = DateFormatUtils.format(new Date(endTime), "yyyy-MM-dd HH:mm:ss");
LOGGER.info("Job [{}] is finished! EndTime: [{}] , Cost time [{}] ms", jobName, executeTime, costTime);
if (jobException != null && !jobException.getMessage().equals("")) {
LOGGER.error("job [{}] is execute dateTime [{}] error: {}", jobName, executeTime, getStackTraceMsg(jobException.getStackTrace()));
}
}
/**
* 日志栈输出
*
* @param stackTraceElements
* @return
*/
public String getStackTraceMsg(StackTraceElement[] stackTraceElements) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement stackTraceElem : stackTraceElements) {
sb.append(stackTraceElem.toString() + "\n");
}
return sb.toString();
}
}
6.定义触发器监听器
public class QuartzTriggerListener implements TriggerListener {
@Override
public String getName() {
return "MyTriggerListener";
}
/**
* Trigger被激发 它关联的job即将被运行
*/
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("Trigger监听器:MyTriggerListener.triggerFired()");
}
/**
* Trigger被激发 它关联的job即将被运行,先执行(1),在执行(2) 如果返回TRUE 那么任务job会被终止
*/
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
System.out.println("Trigger监听器:MyTriggerListener.vetoJobExecution()");
return false;
}
/**
* 当Trigger错过被激发时执行,比如当前时间有很多触发器都需要执行,但是线程池中的有效线程都在工作,
* 那么有的触发器就有可能超时,错过这一轮的触发。
*/
@Override
public void triggerMisfired(Trigger trigger) {
System.out.println("Trigger监听器:MyTriggerListener.triggerMisfired()");
}
/**
* 任务完成时触发
*/
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context,
Trigger.CompletedExecutionInstruction triggerInstructionCode) {
System.out.println("Trigger监听器:MyTriggerListener.triggerComplete()");
}
}
7.项目启动时初始化Scheduler
项目启动时初始化调度器Scheduler,同时注册调度、任务和trigger监听器
@Configuration
public class QuartzConfiguration {
@Bean(name = "scheduler")
public Scheduler scheduler() throws Exception {
Scheduler scheduler = schedulerFactory.getScheduler();
// 注册监听
scheduler.getListenerManager().addSchedulerListener(new QuartzCustomSchedulerListener());
scheduler.getListenerManager().addJobListener(new JobMonitorListener());
scheduler.getListenerManager().addTriggerListener(new QuartzTriggerListener());
return scheduler;
}
}
8.QuartzJobManager管理类
在这个类中,我们根据添加了JobUnit注解的任务来定义任务实例的一些属性特征如名称,组;添加任务数据映射;构建表达式触发器;
最后将任务和触发器一起注册到调度器中。
/**
* @Author: yhx
* @Date: 2019/12/9 23:41 下午
*/
@Service
@AutoConfigureAfter(value = {QuartzConfiguration.class})
public class QuartzJobManager {
private static final Logger LOG = LoggerFactory.getLogger(QuartzJobManager.class);
@Qualifier(value = "scheduler")
@Autowired
private Scheduler scheduler;
/**
* 添加任务</br>
* 同一个任务只能添加一次,时间表达式不一样时会更新定时任务
*
* @param jobUnit
* @param jobClass
*/
public void addJob(QuartzJobUnit jobUnit, @SuppressWarnings("rawtypes") Class jobClass) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobUnit.getJobName(), jobUnit.getJobGroup());
if (!scheduler.checkExists(triggerKey)) {
// 创建JobDetail实例
JobDetail jobDetail = null;
// JobDetail 定义任务实例的一些属性特征
if (StringUtils.isNotBlank(jobUnit.getDescription())) {
jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobUnit.getJobName(), jobUnit.getJobGroup()).withDescription(jobUnit.getDescription()).build();
} else {
jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobUnit.getJobName(), jobUnit.getJobGroup()).build();
}
// JobDataMap 任务数据映射
jobDetail.getJobDataMap().put("scheduleJob", jobUnit);
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobUnit.getJobCorn());
CronTriggerImpl trigger = (CronTriggerImpl) TriggerBuilder.newTrigger().withIdentity(jobUnit.getJobName(), jobUnit.getJobGroup()).withSchedule(scheduleBuilder).build();
if (StringUtils.isNotBlank(jobUnit.getDescription())) {
trigger.setDescription(jobUnit.getDescription());
}
if (jobUnit.getSurviveSecond() > 0L) {
trigger.setEndTime(new Date(System.currentTimeMillis() + jobUnit.getSurviveSecond() * 1000));
}
trigger.setMisfireInstruction(jobUnit.getMisfireInstruction());
trigger.setJobDataMap(jobDetail.getJobDataMap());
// 注册job和调度器
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
LOG.info("add quartz job [{}] success!", triggerKey.toString());
} else {
CronTriggerImpl trigger = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
if (!trigger.getCronExpression().equalsIgnoreCase(jobUnit.getJobCorn()) || (StringUtils.isNotBlank(jobUnit.getDescription()) && !jobUnit.getDescription().equals(trigger.getDescription()))) {
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobUnit.getJobCorn());
trigger = (CronTriggerImpl) trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
if (jobUnit.getSurviveSecond() > 0L) {
trigger.setEndTime(new Date(System.currentTimeMillis() + jobUnit.getSurviveSecond() * 1000));
}
if (StringUtils.isNotBlank(jobUnit.getDescription())) {
trigger.setDescription(jobUnit.getDescription());
}
scheduler.rescheduleJob(triggerKey, trigger);
LOG.info("update quartz job [{}] success!", triggerKey.toString());
}
}
} catch (SchedulerException e) {
LOG.error("add quartz job error: {}", e);
}
}
/**
* 移除任务
*
* @param jobUnit
*/
public void removeJob(QuartzJobUnit jobUnit) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobUnit.getJobName(), jobUnit.getJobGroup());
scheduler.pauseTrigger(triggerKey);// 停止触发器
scheduler.unscheduleJob(triggerKey);// 移除触发器
JobKey jobKey = JobKey.jobKey(jobUnit.getJobName(), jobUnit.getJobGroup());
scheduler.deleteJob(jobKey);// 删除任务
} catch (SchedulerException e) {
//throw new RuntimeException(e);
}
}
/**
* 停止任务
*
* @param jobUnit
*/
public void pauseJob(QuartzJobUnit jobUnit) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobUnit.getJobName(), jobUnit.getJobGroup());
scheduler.pauseTrigger(triggerKey);// 停止触发器
} catch (SchedulerException e) {
LOG.error("暂定任务失败:", e);
}
}
/**
* 恢复任务
*
* @param jobUnit
*/
public void resumeJob(QuartzJobUnit jobUnit) {
try {
JobKey jobKey = JobKey.jobKey(jobUnit.getJobName(), jobUnit.getJobGroup());
scheduler.resumeJob(jobKey);// 停止触发器
} catch (SchedulerException e) {
//throw new RuntimeException(e);
}
}
/**
* 开始所有任务
*/
public void startJobs() {
try {
scheduler.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 停止所有任务
*/
public void shutdownJobs() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 立即执行任务
*
* @param jobUnit
*/
public void triggerJob(QuartzJobUnit jobUnit) {
try {
JobKey jobKey = JobKey.jobKey(jobUnit.getJobName(), jobUnit.getJobGroup());
scheduler.triggerJob(jobKey);
} catch (Exception e) {
}
}
}
9.启动类中注册定时任务
@SpringBootApplication
public class QuartzandbatchApplication implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzandbatchApplication.class);
private static final String QUARTZ_JOB_PKG = "com.yhx.quartzandbatch.quartz.QuartzFrame";
@Autowired
private QuartzJobManager quartzJobManager;
public static void main(String[] args) {
SpringApplication.run(QuartzandbatchApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
registerQuartzJob();
}
/**
* 注册定时任务
*
* @throws Exception
*/
private void registerQuartzJob() throws Exception {
LOGGER.info("start to register quartz job...");
final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(JobUnit.class));
for (final BeanDefinition resourceBean : scanner.findCandidateComponents(QUARTZ_JOB_PKG)) {
try {
final Class resourceClass = getClass().getClassLoader().loadClass(resourceBean.getBeanClassName());
if (null != resourceClass) {
JobUnit jobUnit = (JobUnit) resourceClass.getAnnotation(JobUnit.class);
String jobName = jobUnit.jobName();
if (StringUtils.isEmpty(jobName)) {
jobName = resourceClass.getSimpleName();
}
QuartzJobUnit quartzJobUnit = new QuartzJobUnit();
quartzJobUnit.setJobName(jobName);
quartzJobUnit.setJobGroup(jobUnit.jobGroup());
quartzJobUnit.setJobCorn(jobUnit.jobCorn());
quartzJobUnit.setMisfireInstruction(jobUnit.misfireInstruction());
if (!StringUtils.isEmpty(jobUnit.jobDesc())) {
quartzJobUnit.setDescription(jobUnit.jobDesc());
}
quartzJobManager.addJob(quartzJobUnit, resourceClass);
}
} catch (final ClassNotFoundException e) {
LOGGER.error("error to register quartz job bean [{}]", resourceBean.getBeanClassName(), e);
}
}
LOGGER.info("finished register quartz job...");
}
}
10.当我们运行后发现报错了:
这个因为job任务是交给quartz处理的,而job任务中引入的TestAutowired是spring管理的,他们是2个东西,所以引入的TestAutowired是空。具体原因见第四章
2019-12-10 00:55:00,106 ERROR (JobRunShell.java:211)- Job QuartzJob.QuartzJob1 threw an unhandled Exception:
java.lang.NullPointerException: null
at com.yhx.toali.Quartz.QuartzFrame.QuartzJob1.execute(QuartzJob1.java:33)
at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
2019-12-10 00:55:00,107 ERROR (QuartzCustomSchedulerListener.java:157)- jobGroup [Job (QuartzJob.QuartzJob1 threw an exception.] deal error: org.quartz.core.JobRunShell.run(JobRunShell.java:213)
org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
11.添加JobFactory
@Component(value = "quartzCustomAdaptableJobFactory")
public class QuartzCustomAdaptableJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//实例化对象
Object jobInstance = super.createJobInstance(bundle);
//进行注入,交给spring管理该bean
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
12.修改Scheduler的获取方式
@Configuration
@AutoConfigureAfter(value = {QuartzCustomAdaptableJobFactory.class})
public class QuartzConfiguration {
/*@Bean(name = "scheduler")
public Scheduler scheduler() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.getListenerManager().addSchedulerListener(new QuartzCustomSchedulerListener());
scheduler.getListenerManager().addJobListener(new JobMonitorListener());
scheduler.getListenerManager().addTriggerListener(new QuartzTriggerListener());
return scheduler;
}*/
@Bean(value = "schedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("quartzCustomAdaptableJobFactory") QuartzCustomAdaptableJobFactory quartzCustomAdaptableJobFactory) {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(quartzCustomAdaptableJobFactory);
return schedulerFactoryBean;
}
@Bean(name = "scheduler")
public Scheduler scheduler(@Qualifier("schedulerFactory") SchedulerFactoryBean schedulerFactory) throws Exception {
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.getListenerManager().addSchedulerListener(new QuartzCustomSchedulerListener());
scheduler.getListenerManager().addJobListener(new JobMonitorListener());
scheduler.getListenerManager().addTriggerListener(new QuartzTriggerListener());
return scheduler;
}
}
13.注释掉QuartzJobManager中的调度器的启动
// 注册job和调度器
scheduler.scheduleJob(jobDetail, trigger);
// scheduler.start();
14.执行结果:
红框主要是圈出来我主动打的一些日志,包括监听器的记录和job里面执行的内容,以及间隔时间是5秒钟