任务调度 Quartz

任务调度 Quartz

任务调度

任务调度需求

1、定义触发规则,基于时刻、时间间隔、表达式
2、定义任务
3、方便配置;配置文件/配置中心
4、任务串行;A-B-C
5、任务并发,互不干扰
6、调度器,启动、中断、停止任务
7、集成Spring

任务调度工具对比

层次 举例 特点
操作系统 Linux crontab Windows 计划任务 只能执行简单脚本或者命令
数据库 MySQL、Oracle 可以操作数据。不能执行Java 代码
工具 Kettle 可以操作数据,执行脚本。没有集中配置
开发语言 JDK Timer、ScheduledThreadPool Timer:单线程 JDK1.5 之后:ScheduledThreadPool(Cache、Fiexed、Single):没有集中配置,日程管理不够灵活
容器 Spring Task、@Scheduled 不支持集群
分布式框架 XXL-JOB,Elastic-Job

Quartz

api

依赖

<dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz</artifactId>
	<version>2.3.0</version>
</dependency>

默认配置文件

quartz.properties
包自带的,如果我们没有创建该配置文件,就用系统自带的

创建Job

public class MyJob implements Job
{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException
    {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println(format.format(date));
    }
}

Job 包装成JobDetail

JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.usingJobData("name","2673")
.usingJobData("moon",5.21F)
.build();

创建触发器Trigger

Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(2)
                        .repeatForever())

创建调度器 Scheduler

调度器一定都是单例
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();

JobDetail

是由Job包装而来

Trigger

JobDetail 和Trigger 比例是1比N
Trigger有4种子接口
子接口 描述 特点
SimpleTrigger 简单触发器 固定时刻或时间间隔,毫秒
CalendarIntervalTrigger 基于日历的触发器 比简单触发器更多时间单位,支持非固定时间的触发,例如一年可365/366,一个月可能28/29/30/31
DailyTimeIntervalTrigger 基于日期的触发器 每天的某个时间段
CronTrigger 基于Cron 表达式的触发器

Calendar 的排除规则

名称 用法
BaseCalendar 为高级的Calendar 实现了基本的功能,实现了org.quartz.Calendar 接口
AnnualCalendar 排除年中一天或多天
CronCalendar 日历的这种实现排除了由给定的CronExpression 表达的时间集合。例如,您可以使用此日历使用表达式“* * 0-7,18-23?* *”每天排除所有营业时间(上午8 点至下午5 点)。如果CronTrigger 具有给定的cron 表达式并且与具有相同表达式的CronCalendar 相关联,则日历将排除触发器包含的所有时间,并且它们将彼此抵消。
DailyCalendar 您可以使用此日历来排除营业时间(上午8 点- 5 点)每天。每个DailyCalendar 仅允许指定单个时间范围,并且该时间范围可能不会跨越每日边界(即,您不能指定从上午8 点至凌晨5 点的时间范围)。如果属性invertTimeRange 为false(默认),则时间范围定义触发器不允许触发的时间范围。如果invertTimeRange 为true,则时间范围被反转- 也就是排除在定义的时间范围之外的所有时间。
HolidayCalendar 特别的用于从Trigger 中排除节假日
MonthlyCalendar 排除月份中的指定数天,例如,可用于排除每月的最后一天
WeeklyCalendar 排除星期中的任意周几,例如,可用于排除周末,默认周六和周日

Scheduler 调度器

由 StdSchedulerFactory 产生的,是单例
scheduler的方法主要有三类:
1、操作调度器本身,start、shutdown
2、操作trigger pauseTriggers()、resumeTrigger()
3、操作job ;scheduleJob()、unscheduleJob()、rescheduleJob()

Listener

针对job、调度器、触发器的三种监听

JobListener

方法 作用
getName() 返回JobListener 的名称
jobToBeExecuted() Scheduler 在JobDetail 将要被执行时调用这个方法
jobExecutionVetoed() Scheduler 在JobDetail 即将被执行,但又被TriggerListener 否决了时调用这个方法
jobWasExecuted() Scheduler 在JobDetail 被执行之后调用这个方法

TriggerListener

方法 作用
getName() 返回监听器的名称
triggerFired() Trigger 被触发,Job 上的execute() 方法将要被执行时,Scheduler 就调用这个方法
vetoJobExecution() 在Trigger 触发后, Job 将要被执行时由Scheduler 调用这个方法。TriggerListener 给了一个选择去否决Job 的执行。假如这个方法返回true,这个Job 将不会为此次Trigger 触发而得到执行
triggerMisfired() Trigger 错过触发时调用
triggerComplete() Trigger 被触发并且完成了Job 的执行时,Scheduler 调用这个方法

SchedulerListener

方法太多

Spring 集成Quartz

把job、Trigger、scheduler定义成Bean

Job

<bean name="myJob1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
	<property name="name" value="my_job_1"/>
	<property name="group" value="my_group"/>
	<property name="jobClass" value="com.gupaoedu.quartz.MyJob1"/>
	<property name="durability" value="true"/>
</bean>

Trigger

<bean name="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="name" value="my_trigger_1"/>
<property name="group" value="my_group"/>
<property name="jobDetail" ref="myJob1"/>
<property name="startDelay" value="1000"/>
<property name="repeatInterval" value="5000"/>
<property name="repeatCount" value="2"/>
</bean>
Scheduler
<bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="triggers">
	<list>
	<ref bean="simpleTrigger"/>
	<ref bean="cronTrigger"/>
	</list>
	</property>
</bean>

Test

public class QuartzTest {

    private static Scheduler scheduler;

    public static void main(String[] args) throws SchedulerException {
        // 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring_quartz.xml");

        // 从容器中获取调度器
        scheduler = (StdScheduler) ac.getBean("scheduler");

        // 启动调度器
        scheduler.start();
    }

}

注解方式配置

@Configuration
public class QuartzConfig
{
    @Bean
    public JobDetail printTimeJobDetail()
    {
        return JobBuilder.newJob(MyJob3.class).withIdentity("gupaoJob").usingJobData("gupao", "职位更好的你").storeDurably().build();
    }

    @Bean
    public Trigger printTimeJobTrigger()
    {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        return TriggerBuilder.newTrigger().forJob(printTimeJobDetail()).withIdentity("quartzTaskService").withSchedule(cronScheduleBuilder).build();
    }

    @Bean
    public SchedulerFactoryBean printTimeJobScheduler()
    {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setStartupDelay(1);
        return bean;
    }
}

测试

ApplicationContext context = new AnnotationConfigApplicationContext(QuartzConfig.class);

        JobDetail jobDetail = (JobDetail) context.getBean(JobDetail.class);

        Trigger trigger = (Trigger) context.getBean(Trigger.class);

        scheduler = (Scheduler) context.getBean(Scheduler.class);

        scheduler.scheduleJob(jobDetail, trigger);

        scheduler.start();
SchedulerFactoryBean 实例化是内部会创建一个scheduler 对象。

Springboot集成quartz

任务配置管理

表JobDetail

任务调度 Quartz

数据操作与任务调度

public class SchedulerUtil {
	private static Logger logger = LoggerFactory.getLogger(SchedulerUtil.class);

	/**
	 * 新增定时任务
	 * @param jobClassName 类路径
	 * @param jobName 任务名称
	 * @param jobGroupName 组别
	 * @param cronExpression Cron表达式
	 * @param jobDataMap 需要传递的参数
	 * @throws Exception
	 */
	public static void addJob(String jobClassName,String jobName, String jobGroupName, String cronExpression,String jobDataMap) throws Exception {
		// 通过SchedulerFactory获取一个调度器实例
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		// 启动调度器
		scheduler.start();
		// 构建job信息
		JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
				.withIdentity(jobName, jobGroupName).build();
		// JobDataMap用于传递任务运行时的参数,比如定时发送邮件,可以用json形式存储收件人等等信息
		if (StringUtils.isNotEmpty(jobDataMap)) {
			JSONObject jb = JSONObject.parseObject(jobDataMap);
			Map<String, Object> dataMap =(Map<String, Object>) jb.get("data");
			for (Map.Entry<String, Object> m:dataMap.entrySet()) {
				jobDetail.getJobDataMap().put(m.getKey(),m.getValue());
			}
		}
		// 表达式调度构建器(即任务执行的时间)
		CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
		// 按新的cronExpression表达式构建一个新的trigger
		CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
				.withSchedule(scheduleBuilder).startNow().build();
		try {
			scheduler.scheduleJob(jobDetail, trigger);
		} catch (SchedulerException e) {
			logger.info("创建定时任务失败" + e);
			throw new Exception("创建定时任务失败");
		}
	}
	
	/**
	 * 停用一个定时任务
	 * @param jobName 任务名称
	 * @param jobGroupName 组别
	 * @throws Exception
	 */
	public static void jobPause(String jobName, String jobGroupName) throws Exception {
		// 通过SchedulerFactory获取一个调度器实例
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));
	}
	
	/**
	 * 启用一个定时任务
	 * @param jobName 任务名称
	 * @param jobGroupName 组别
	 * @throws Exception
	 */
	public static void jobresume(String jobName, String jobGroupName) throws Exception {
		// 通过SchedulerFactory获取一个调度器实例
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		scheduler.resumeJob(JobKey.jobKey(jobName, jobGroupName));
	}
	
	/**
	 * 删除一个定时任务
	 * @param jobName 任务名称
	 * @param jobGroupName 组别
	 * @throws Exception
	 */
	public static void jobdelete(String jobName, String jobGroupName) throws Exception {
		// 通过SchedulerFactory获取一个调度器实例
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName));
		scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName));
		scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
	}
	
	/**
	 * 更新定时任务表达式
	 * @param jobName 任务名称
	 * @param jobGroupName 组别
	 * @param cronExpression Cron表达式
	 * @throws Exception
	 */
	public static void jobReschedule(String jobName, String jobGroupName, String cronExpression) throws Exception {
		try {
			SchedulerFactory schedulerFactory = new StdSchedulerFactory();
			Scheduler scheduler = schedulerFactory.getScheduler();
			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
			// 表达式调度构建器
			CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
			CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
			// 按新的cronExpression表达式重新构建trigger
			trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).startNow().build();
			// 按新的trigger重新设置job执行
			scheduler.rescheduleJob(triggerKey, trigger);
		} catch (SchedulerException e) {
			System.out.println("更新定时任务失败" + e);
			throw new Exception("更新定时任务失败");
		}
	}
	
	/**
	 * 检查Job是否存在
	 * @throws Exception
	 */
	public static Boolean isResume(String jobName, String jobGroupName) throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
		Boolean state = scheduler.checkExists(triggerKey);
	     
		return state;
	}

	/**
	 * 暂停所有任务
	 * @throws Exception
	 */
	public static void pauseAlljob() throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		scheduler.pauseAll();
	}

	/**
	 * 唤醒所有任务
	 * @throws Exception
	 */
	public static void resumeAlljob() throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler sched = sf.getScheduler();
		sched.resumeAll();
	}

	/**
	 * 获取Job实例
	 * @param classname
	 * @return
	 * @throws Exception
	 */
	public static BaseJob getClass(String classname) throws Exception {
		try {
			Class<?> c = Class.forName(classname);
			return (BaseJob) c.newInstance();
		} catch (Exception e) {
			throw new Exception("类["+classname+"]不存在!");
		}
		
	}

}

前端界面

任务调度 Quartz
任务调度 Quartz

容器启动与Service 注入

任务定义在数据库,容器启动如何读取任务

利用CommandLineRunner接口,实现项目启动后读取job构建定时任务。
@Component
public class InitStartSchedule implements CommandLineRunner
{
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ISysJobService sysJobService;
    @Autowired
    private MyJobFactory myJobFactory;

    @Override
    public void run(String... args) throws Exception
    {
        /**
         * 用于程序启动时加载定时任务,并执行已启动的定时任务(只会执行一次,在程序启动完执行)
         */

        //查询job状态为启用的
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("jobStatus", "1");
        List<SysJob> jobList = sysJobService.querySysJobList(map);
        if (null == jobList || jobList.size() == 0)
        {
            logger.info("系统启动,没有需要执行的任务... ...");
        }
        // 通过SchedulerFactory获取一个调度器实例
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler scheduler = sf.getScheduler();
        // 如果不设置JobFactory,Service注入到Job会报空指针
        scheduler.setJobFactory(myJobFactory);
        // 启动调度器
        scheduler.start();

        for (SysJob sysJob : jobList)
        {
            String jobClassName = sysJob.getJobName();
            String jobGroupName = sysJob.getJobGroup();
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(getClass(sysJob.getJobClassPath()).getClass()).withIdentity(jobClassName, jobGroupName).build();
            if (StringUtils.isNotEmpty(sysJob.getJobDataMap()))
            {
                JSONObject jb = JSONObject.parseObject(sysJob.getJobDataMap());
                Map<String, Object> dataMap = (Map<String, Object>) jb.get("data");
                for (Map.Entry<String, Object> m : dataMap.entrySet())
                {
                    jobDetail.getJobDataMap().put(m.getKey(), m.getValue());
                }
            }
            //表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(sysJob.getJobCron());
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName).withSchedule(scheduleBuilder).startNow().build();
            // 任务不存在的时候才添加
            if (!scheduler.checkExists(jobDetail.getKey()))
            {
                try
                {
                    scheduler.scheduleJob(jobDetail, trigger);
                } catch (SchedulerException e)
                {
                    logger.info("\n创建定时任务失败" + e);
                    throw new Exception("创建定时任务失败");
                }
            }
        }
    }

    public static BaseJob getClass(String classname) throws Exception
    {
        Class<?> c = Class.forName(classname);
        return (BaseJob) c.newInstance();
    }
}

Service 类注入到Job 中

Job实例是quartz管理的,Job中的service是spring管理的。Job中依赖service会空指针
利用 AutowireCapableBeanFactory 把 job实例注册到spring中
@Component
public class MyJobFactory extends AdaptableJobFactory
{
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception
    {
        //调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        capableBeanFactory.autowireBean(jobInstance);

        return jobInstance;
    }
}

指定Scheduler 的JobFactory 为自定义的JobFactory
任务调度 Quartz
这里有个问题,貌似定时任务执行时都会创建一个Job实例。

Quartz 调度原理

获取调度器实例

绑定JobDetail 和Trigger

启动调度器

源码总结

集群原理

任务为什么重复执行

上一篇:使用QueryRunner类实现更新


下一篇:JavaSE 55 异常机制 → 捕获和抛出异常