赞
踩
之前与定时任务相关的一个文章记录:
springboot使用@Scheduled作定时任务详细用法
Quartz是一个完全由java编写的功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中,小到独立应用程序,大到大型的电子商务系统。Quartz可以用来创建执行数十,数百乃至数万个作业的简单或复杂的计划;作业的任务被定义为标准的Java组件,它可以执行几乎任何你可能编程的任务。而且Quartz Scheduler包含许多企业级功能,例如支持JTA事务和集群。
了解一下Quartz中涉及到的几个类概念:
SchedulerFactory:调度器工厂。这是一个接口,用于调度器的创建和管理。示例中使用的是Quartz中的默认实现。
Scheduler:任务调度器。它表示一个Quartz的独立运行容器,里面注册了多个触发器(Trigger)和任务实例(JobDetail)。两者分别通过各自的组(group)和名称(name)作为容器中定位某一对象的唯一依据,因此组和名称必须唯一(触发器和任务实例的组和名称可以相同,因为对象类型不同)。
Job:是一个接口,只有一个方法void execute(JobExecutionContext context)
,开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。
JobDetail:Job实例。Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
Trigger
:触发器,描述触发Job执行的时间触发规则。
simpleSchedule()
生成的SimpleTrigger),并指定唯一标识(Identity)组(示例中的“group1”)和名称(示例中的“myTrigger”)start()
方法开启任务调度。关于CommandLineRunner:平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中
这个类的run方法String… args是应用启动的时候可以传进来的参数,有两种方式可以传参
一种是命令行的方式传参,所以这个接口叫CommandLineRunner
另一种方法是通过IntelliJ IDEA配置参数
命令行传参
首先将应用打成jar包,然后运行如下命令行,这里传入三个参数
java -jar MyProject.jar my name is
或者在idea中配置:
代码:
import com.itheima.pinda.entity.ScheduleJobEntity; import com.itheima.pinda.mapper.ScheduleJobMapper; import com.itheima.pinda.utils.ScheduleUtils; import lombok.extern.slf4j.Slf4j; import org.quartz.CronTrigger; import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.List; /** * 在项目启动时初始化定时任务 */ @Component @Slf4j public class DispatchCommandLineRunner implements CommandLineRunner { @Autowired private Scheduler scheduler; @Autowired private ScheduleJobMapper scheduleJobMapper; @Override public void run(String... args) throws Exception { log.info("开始进行定时任务初始化..."); //查询定时任务表schedule_job中所有的数据 List<ScheduleJobEntity> list = scheduleJobMapper.selectList(null);//查询数据库 for (ScheduleJobEntity scheduleJobEntity : list) { //获得触发器对象,调用ScheduleUtils中的方法 CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJobEntity.getId()); if(cronTrigger == null){ //触发器对象为空,说明对应的定时任务还没有创建 ScheduleUtils.createScheduleJob(scheduler,scheduleJobEntity); }else{ //触发器对象不为空,说明对应的定时任务已经存在了,此时只需要更新 ScheduleUtils.updateScheduleJob(scheduler,scheduleJobEntity); } } } }
上面自定义的CommandLineRunner类中调用了工具类的方法来真正运行任务,需要写一个定时任务工具类,方便通过jobid得到触发器key和jobkey、获得表达式触发器
在这里创建、更新定时任务、立即执行某个特定任务
/** * 定时任务工具类 * * @author */ public class ScheduleUtils { private final static String JOB_NAME = "TASK_"; /** * 任务调度参数key */ public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY"; /** * 获取触发器key */ public static TriggerKey getTriggerKey(String jobId) { return TriggerKey.triggerKey(JOB_NAME + jobId); } /** * 获取jobKey */ public static JobKey getJobKey(String jobId) { return JobKey.jobKey(JOB_NAME + jobId); } /** * 获取表达式触发器 */ public static CronTrigger getCronTrigger(Scheduler scheduler, String jobId) { try { return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId)); } catch (SchedulerException e) { throw new PdException("getCronTrigger ERROR", e); } } /** * 创建定时任务 */ public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { //构建job信息 JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getId())).build(); //表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()) .withMisfireHandlingInstructionDoNothing(); //按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getId())).withSchedule(scheduleBuilder).build(); //放入参数,运行时的方法可以获取 jobDetail.getJobDataMap().put(JOB_PARAM_KEY, scheduleJob); scheduler.scheduleJob(jobDetail, trigger); //暂停任务 if (scheduleJob.getStatus() == ScheduleStatus.PAUSE.getValue()) { pauseJob(scheduler, scheduleJob.getId()); } } catch (SchedulerException e) { throw new PdException("CREATE ERROR", e); } } /** * 更新定时任务 */ public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { TriggerKey triggerKey = getTriggerKey(scheduleJob.getId()); //表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()) .withMisfireHandlingInstructionDoNothing(); CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getId()); //按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); //参数 trigger.getJobDataMap().put(JOB_PARAM_KEY, scheduleJob); scheduler.rescheduleJob(triggerKey, trigger); //暂停任务 if (scheduleJob.getStatus() == ScheduleStatus.PAUSE.getValue()) { pauseJob(scheduler, scheduleJob.getId()); } } catch (SchedulerException e) { throw new PdException("UPDATE ERROR", e); } } /** * 立即执行任务 */ public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { //参数 JobDataMap dataMap = new JobDataMap(); dataMap.put(JOB_PARAM_KEY, scheduleJob); scheduler.triggerJob(getJobKey(scheduleJob.getId()), dataMap); } catch (SchedulerException e) { throw new PdException("RUN ERROR", e); } } /** * 暂停任务 */ public static void pauseJob(Scheduler scheduler, String jobId) { try { scheduler.pauseJob(getJobKey(jobId)); } catch (SchedulerException e) { throw new PdException("PAUSE ERROR", e); } } /** * 恢复任务 */ public static void resumeJob(Scheduler scheduler, String jobId) { try { scheduler.resumeJob(getJobKey(jobId)); } catch (SchedulerException e) { throw new PdException("RESUME ERROR", e); } } /** * 删除定时任务 */ public static void deleteScheduleJob(Scheduler scheduler, String jobId) { try { scheduler.deleteJob(getJobKey(jobId)); } catch (SchedulerException e) { throw new PdException("DELETE ERROR", e); } } }
@Service
public class ScheduleJobServiceImpl extends ServiceImpl<ScheduleJobMapper, ScheduleJobEntity> implements IScheduleJobService {
@Autowired
private Scheduler scheduler;
// 在service层中调用ScheduleUtils.run的方法来立即执行任务
@Override
@Transactional(rollbackFor = Exception.class)
public void run(String[] ids) {
for (String id : ids) {
ScheduleUtils.run(scheduler, baseMapper.selectById(id));
}
}
}
在controller中调用service的方法
@PutMapping("/run/{id}")
@ApiOperation("立即执行")
public Result run(@PathVariable String id) {
scheduleJobService.run(new String[]{id});
return Result.ok();
}
【QuartzJobBean】:
Quartz Job 接口的简单实现,应用传入的 JobDataMap 和 SchedulerContext 作为 bean 属性值。 这是合适的,因为每次执行都会创建一个新的 Job 实例。 JobDataMap 条目将使用相同的键覆盖 SchedulerContext 条目。
例如,假设 JobDataMap 包含一个值为“5”的键“myParam”:然后,Job 实现可以公开一个 int 类型的 bean 属性“myParam”来接收这样的值,即方法“setMyParam(int)” . 这也适用于复杂类型,如业务对象等。
在上面的 ScheduleUtils中使用JobBuilder.newJob(ScheduleJob.class)构建了job信息,需要拓展QuartzJobBean自定义一个定时任务类
ScheduleJob 继承自QuartzJobBean,程序会进入executeInternal来执行定时任务
/** * 定时任务类,进行智能调度操作 */ public class ScheduleJob extends QuartzJobBean { private Logger logger = LoggerFactory.getLogger(getClass()); @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { ScheduleJobEntity scheduleJob = (ScheduleJobEntity) context.getMergedJobDataMap(). get(ScheduleUtils.JOB_PARAM_KEY); System.out.println(new Date() + "定时任务开始执行...,定时任务id:" + scheduleJob.getId()); //记录定时任务相关的日志信息 //封装日志对象 ScheduleJobLogEntity logEntity = new ScheduleJobLogEntity(); logEntity.setId(IdUtils.get()); logEntity.setJobId(scheduleJob.getId()); logEntity.setBeanName(scheduleJob.getBeanName()); logEntity.setParams(scheduleJob.getParams()); logEntity.setCreateDate(new Date()); long startTime = System.currentTimeMillis(); try{ //通过反射调用目标对象,在目标对象中封装智能调度核心逻辑 logger.info("定时任务准备执行,任务id为:{}",scheduleJob.getId()); //获得目标对象 Object target = SpringContextUtils.getBean(scheduleJob.getBeanName()); //获得目标方法对象 Method method = target.getClass().getDeclaredMethod("run", String.class, String.class, String.class, String.class); //通过反射调用目标对象的方法 method.invoke(target,scheduleJob.getBusinessId(),scheduleJob.getParams(),scheduleJob.getId(),logEntity.getId()); logEntity.setStatus(1);//成功 }catch (Exception ex){ logEntity.setStatus(0);//失败 logEntity.setError(ExceptionUtils.getErrorStackTrace(ex)); logger.error("定时任务执行失败,任务id为:{}",scheduleJob.getId()); }finally { int times = (int) (System.currentTimeMillis() - startTime); logEntity.setTimes(times);//耗时 IScheduleJobLogService scheduleJobLogService = SpringContextUtils.getBean(IScheduleJobLogService.class); scheduleJobLogService.save(logEntity); } } }
编写智能调度组件DispatchTask,封装的是智能调度的核心逻辑,此类中需要提供run方法,在前面的ScheduleJob定时任务中通过反射调用此run方法来完成智能调度。
/** * 智能调度组件 */ @Slf4j @Component("dispatchTask") public class DispatchTask{ /** * 智能调度 * @param businessId * @param params * @param jobId * @param logId */ public void run(String businessId, String params, String jobId, String logId) { log.info("智能调度正在执行,参数为:{},{},{},{}", businessId, params, jobId, logId); } }
@Data @EqualsAndHashCode(callSuper = false) @TableName("schedule_job") public class ScheduleJobEntity implements Serializable { /** * id */ @TableId(value = "id", type = IdType.INPUT) private String id; /** * 创建者 */ @TableField(fill = FieldFill.INSERT) private Long creator; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private Date createDate; /** * spring bean名称 */ private String beanName; /** * 参数 */ private String params; /** * cron表达式 */ private String cronExpression; /** * 任务状态 0:暂停 1:正常 */ private Integer status; /** * 备注 */ private String remark; /** * 业务id 机构id */ private String businessId; /** * 更新者 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Long updater; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateDate; }
/**
* 定时任务 Mapper 接口
*/
@Component
@Mapper
public interface ScheduleJobMapper extends BaseMapper<ScheduleJobEntity> {
}
定时任务在配置成功后启动时会查询数据库,在控制台输出类似如下的相应内容:(会查询数据库,执行用户定义的定时任务)
具体时间设定可参考
“0/10 * * * * ?” 每10秒触发
“0 0 12 * * ?” 每天中午12点触发
“0 15 10 ? * *” 每天上午10:15触发
“0 15 10 * * ?” 每天上午10:15触发
"0 15 10 * * ? " 每天上午10:15触发
“0 15 10 * * ? 2005” 2005年的每天上午10:15触发
“0 * 14 * * ?” 在每天下午2点到下午2:59期间的每1分钟触发
“0 0/5 14 * * ?” 在每天下午2点到下午2:55期间的每5分钟触发
“0 0/5 14,18 * * ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
“0 0-5 14 * * ?” 在每天下午2点到下午2:05期间的每1分钟触发
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
“0 15 10 15 * ?” 每月15日上午10:15触发
“0 15 10 L * ?” 每月最后一日的上午10:15触发
“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发
每隔5秒执行一次:/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。