本篇主要介绍Spring 定时任务框架 的几种使用方式,以及动态定时任务
文章目录
- 简单使用方式
- 下面进行扩展出spring提供的方式,包括自定义动态任务
- 总结
简单使用方式
- 首先开启注解
@SpringBootApplication
@EnableScheduling
public class Springbootdemo1Application {
public static void main(String[] args) {
SpringApplication.run(Springbootdemo1Application.class, args);
}
}
- 其次方法上使用定时任务注解为@Scheduled(*)
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
@Scheduled(fixedRate = 5000)
public void reportFixedRate() {
log.info("reportFixedRate The time is now {}", dateFormat.format(new Date()));
}
@Scheduled(cron = "0/15 * * * * ?")
public void reportCron() {
log.info(" reportCron The time is now {}", dateFormat.format(new Date()));
}
}
支持设置的调度规则
内部可以设置的调度规则有 cron zone fixedDelay fixedDelayString fixedRate
initialDelay initialDelayString
依据测试打印的日志 fixedRate是循环每隔5秒执行任务,cron表达式语法语法 支持的是每隔15秒进行打印日志 ,上面是简单的分析打印的日志,使用最简单的方式实现定时任务,开发相当的轻松;
cron表达式详解语法(注:[年]不是必须的域,可以省略[年],则一共6个域 )
[秒] [分] [小时] [日] [月] [周] [年]
每隔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 * * ?
cron表达式使用占位符
cron属性接收的cron表达式支持占位符。"${time.cron}"
* 表示所有值。 例如:在分的字段上设置 *,表示每一分钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为”?” 具体设置为 0 0 0 10 * ?
- 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。
, 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。 在日字段上设置’1/3’所示每月1号开始,每隔三天触发一次 */y,它等同于 0/y。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示“本月最后一个星期五”
W (work)表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)。
# 序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六.注意如果指定”#5”,正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;小提示:’L’和 ‘W’可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。
fixedDelay
上一次执行完毕时间点之后多长时间再执行 以秒为单位
fixedDelayString
fixedDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符
fixedRate
上一次开始执行时间点之后多长时间再执行
fixedRateString
第一次延迟多长时间后再执行
下面进行扩展出spring提供的方式,包括自定义动态任务
1.在方法上使用定时任务注解为@Scheduled(*)
2.Xml配置方式
<task:scheduled ref="taskJob" method="job1" cron="0 0 5 * * ?"/> </task:scheduled-tasks>
3.实现SchedulingConfigurer接口,并持有ScheduledTaskRegistrar进行动态注册任务
下面方法包括整个自定义动态注册任务的流程
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
this.registrar = scheduledTaskRegistrar;
// 自定义任务调度器
TaskScheduler taskScheduler = getTaskScheduler();
scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
try {
// 解析缓存
CacheUtils.parseProcessConf(xmlPath);
} catch (Exception e) {
LOGGER.error("parseProcessConf Exception : {}", e.getMessage(), e);
}
// 遍历所有的任务进行执行
CacheUtils.SCHEDULED_MAP.keySet().forEach(key -> {
Scheduled scheduled = CacheUtils.SCHEDULED_MAP.get(key);
if (scheduled != null && scheduled.isScheduledTask()) {// 判断是否有任务
List<TaskDomain> listTask = scheduled.getTaskList();// 并拿到对应的任务
if (listTask != null && listTask.size() > 0) {
try {
Method method = scheduled.getTargetClass().getDeclaredMethod(scheduled.getMethod(), key,
String.class);
listTask.stream().forEach(task -> {// 添加任务进去
processScheduled(scheduled, method, task, scheduled.getTargetClass());
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
我把任务调度的核心线程数设置为10 ,表示同一时间可以同时运行10个任务
/*
* 自定义任务调度器
*/
@Bean(destroyMethod = "shutdown")
public ThreadPoolTaskScheduler getTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
自己定义了一个 ruannable任务执行器
/**
* 创建 任务 runnable
*
* @param target
* @param method
* @return
*/
protected Runnable createRunnable(Object target, Method method, Object task) {
Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
return new ScheduledMethodRunnable(target, invocableMethod, task);
}
解析任务并对添加入注册器中
/**
* 解析任务并对添加入注册器中
*
* @param scheduled
* @param method
* @param taskList
* @param bean
*/
protected void processScheduled(Scheduled scheduled, Method method, Object task, Object bean) {
try {
Runnable runnable = createRunnable(bean, method, task);
boolean processedSchedule = false;
String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// Determine initial delay
long initialDelay = -1;
String initialDelayString = scheduled.getInitialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = parseDelayAsLong(initialDelayString);
} catch (RuntimeException ex) {
throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString
+ "\" - cannot parse into long");
}
}
}
String cron = scheduled.getCron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.getZone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!cron.equals(scheduled.getCron_disabled())) {
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
} else {
timeZone = TimeZone.getDefault();
}
tasks.add(this.registrar
.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
}
// At this point we don't need to differentiate between initial delay set or not
// anymore
if (initialDelay < 0) {
initialDelay = 0;
}
// Check fixed delay
long fixedDelay = -1;
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(
this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.getFixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
if (StringUtils.hasLength(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = parseDelayAsLong(fixedDelayString);
} catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
tasks.add(this.registrar
.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
}
// 固定时间
long fixedRate = -1;
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.getFixedRateString();
if (StringUtils.hasText(fixedRateString)) {
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
if (StringUtils.hasLength(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = parseDelayAsLong(fixedRateString);
} catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
tasks.add(
this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
}
// 最后注册计划任务 同一个bean默认新建任务初始化大小为16
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
} catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
实现DisposableBean接口,管理 任务销毁
/**
* 容器销毁该bean时会调用该方法进行销毁
*/
@Override
public void destroy() {
synchronized (this.scheduledTasks) {
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
for (Set<ScheduledTask> tasks : allTasks) {
for (ScheduledTask task : tasks) {
task.cancel();
}
}
this.scheduledTasks.clear();
}
}
解析自己的任务,我是自定义xml的进行解析
public class CacheUtils {
private final static Logger LOGGER = LoggerFactory.getLogger(ScheduleTaskConfigurer.class);
public static final Map<Class<?>, Scheduled> SCHEDULED_MAP = new HashMap<>();
// 遍历xml文件
public static void parseProcessConf(String xmlPath) throws Exception {
LOGGER.info("parseProcessConf xmlPath : {} ", xmlPath);
// 获取xml文件列表
File xmlFile = new File(xmlPath);
//
SAXReader reader = new SAXReader();
Document document = reader.read(xmlFile);
Element root = document.getRootElement();
// 解析task任务列表
Element task = root.element("task");
if (task != null) {
List<TaskDomain> taskList = parseXml(task);
if (taskList == null || taskList.size() == 0) {
LOGGER.error(" taskList is null");
} else {
putSheduled(task, taskList);
}
}
}
private static void parseXml(File xmlFile) throws Exception {
}
private static List<TaskDomain> parseXml(Element task) {
// TODO Auto-generated method stub
return null;
}
/**
* 解析任务执行规则
*
* @param key 任务类型
* @param element 解析节点
*/
public static void putSheduled(Element element, List<TaskDomain> taskList) {
Scheduled scheduled = new Scheduled();
scheduled.setCron(element.attributeValue("cron"));
scheduled.setFixedDelayString(element.attributeValue("fixedDelayString"));
scheduled.setFixedRateString(element.attributeValue("fixedRateString"));
scheduled.setMethod(element.attributeValue("method"));
// scheduled.setTargetClass(element.attributeValue("targetClass"));
scheduled.setTaskList(taskList);
CacheUtils.SCHEDULED_MAP.put(taskList.get(0).getClass(), scheduled);
}
}
包括下面的这是spring提供的一些例子供我们参考使用的
/**
*启用Spring的计划任务执行功能,类似于
*Spring的{@code<task:*>}XML命名空间中的功能。待使用
*在@{@link Configuration}类上,如下所示:
*<pre class=“code”>
* @Configuration
* @EnableScheduling
*公共类AppConfig{
*// various @;Bean definitions
*}</pre>
*这允许在任何Spring管理的数据库上检测@{@link Scheduled}注释
*容器中的注解。例如,给定一个类{@code MyTask}
* package com.myco.tasks;
*public class MyTask {
* @Scheduled(fixedRate=1000)
*public void work() {
*//任务执行逻辑
* }
*}</pre>
*以下配置将确保调用{@code MyTask.work()}
*每1000毫秒一次
* @Configuration
* @EnableScheduling
* public class AppConfig {
* @Bean
* public MyTask task() {
* return new MyTask();
* }
* }</pre>
*或者,如果用{@code@Component}注释了{@code@MyTask},则
*以下配置将确保其{@code@Scheduled}方法
*按所需间隔调用:
*<pre class=“code”>
* @Configuration
* @ComponentScan(basePackages="com.myco.tasks")
* public class AppConfig {
* }</pre>
*用{@code@Scheduled}注释的方法甚至可以直接在
*{@code@Configuration}类:
* <pre class="code">
* @Configuration
* @EnableScheduling
* public class AppConfig {
* @Scheduled(fixedRate=1000)
* public void work() {
* // task execution logic
* }
* }</pre>
*在上述所有场景中,都使用默认的单线程任务执行器。
*当需要更多的控制时,{@code@Configuration}类可以实现
*{@link SchedulingConfigurer}。这允许访问底层
*{@link scheduledtaskregistar}实例。例如,以下示例
*演示如何自定义用于执行调度的{@link Executor}
*任务:
*<pre class=“code”>
* @Configuration
* @EnableScheduling
* public class AppConfig implements SchedulingConfigurer {
* @Override
* public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
* taskRegistrar.setScheduler(taskExecutor());
* }
*
* @Bean(destroyMethod="shutdown")
* public Executor taskExecutor() {
* return Executors.newScheduledThreadPool(100);
* }
* }</pre>
*
*注意上面的例子中{@code@Bean(destroyMethod=“shutdown”)}的用法。这个
*确保任务执行器在Spring应用程序启动时正确关闭
*上下文本身是封闭的。
*实现{@code SchedulingConfigurer}还允许细粒度的
*通过{@code scheduledtaskregistar}控制任务注册。
*例如,下面配置特定bean的执行
*每个自定义{@code Trigger}实现的方法:
*<pre class=“code”>
@Configuration
*@EnableScheduling
* public class AppConfig implements SchedulingConfigurer {
* @Override
* public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
* taskRegistrar.setScheduler(taskScheduler());
* taskRegistrar.addTriggerTask(
* new Runnable() {
* public void run() {
* myTask().work();
* }
* },
* new CustomTrigger()
* );
* }
* @Bean(destroyMethod="shutdown")
* public Executor taskScheduler() {
* return Executors.newScheduledThreadPool(42);
* }
* @Bean
* public MyTask myTask() {
* return new MyTask();
* }
* }</pre>
*作为参考,上面的示例可以与下面的Spring XML进行比较
* 配置:
*<pre class=“code”>
* {@code
* <beans>
* <task:annotation-driven scheduler="taskScheduler"/>
* <task:scheduler id="taskScheduler" pool-size="42"/>
* <task:scheduled ref="myTask" method="work" fixed-rate="1000"/>
* <bean id="myTask" class="com.foo.MyTask"/>
* </beans>
* }</pre>
*除了在XML中使用固定利率时段外,这些示例是等效的
*而不是定制的<em>{@code Trigger}</em>实现;这是因为
*{@code task:}命名空间{@code scheduled}无法轻松公开此类支持。这是
*但是有一个例子说明了基于代码的方法如何允许最大的可配置性
*通过直接访问实际组件。<p>
* @author Chris Beams
* @since 3.1
* @see Scheduled
* @see SchedulingConfiguration
* @see SchedulingConfigurer
* @see ScheduledTaskRegistrar
* @see Trigger
* @see ScheduledAnnotationBeanPostProcessor
*/
总结
spring定时任务器是sping-context中提供,所提供的三种方式也相当强大,但是不支持分布式定时任务,这个就需要用到xx-job这些框架,这里就不在这里叙述