spring 定时任务
This commit is contained in:
parent
b88fee21f8
commit
8b139b7a66
@ -49,6 +49,7 @@
|
||||
- [Spring 自定义属性解析器](/docs/Spring/clazz/Spring-Custom-attribute-resolver.md)
|
||||
- [Spring 排序工具](/docs/Spring/clazz/Spring-OrderUtils.md)
|
||||
- [Spring-import注解](/docs/Spring/clazz/Spring-Import.md)
|
||||
- [Spring-定时任务](/docs/Spring/clazz/Spring-Scheduling.md)
|
||||
### Spring5 新特性
|
||||
- [Spring5-spring.components解析](/docs/Spring/Spring5新特性/Spring-spring-components.md)
|
||||
|
||||
|
302
docs/Spring/clazz/Spring-Scheduling.md
Normal file
302
docs/Spring/clazz/Spring-Scheduling.md
Normal file
@ -0,0 +1,302 @@
|
||||
# Spring 定时任务
|
||||
- Author: [HuiFer](https://github.com/huifer)
|
||||
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
|
||||
|
||||
## EnableScheduling
|
||||
- 首先关注的类为启动定时任务的注解`@EnableScheduling`
|
||||
```java
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(SchedulingConfiguration.class)
|
||||
@Documented
|
||||
public @interface EnableScheduling {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## SchedulingConfiguration
|
||||
- 注册定时任务相关信息
|
||||
```java
|
||||
@Configuration
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class SchedulingConfiguration {
|
||||
|
||||
/**
|
||||
* 开启定时任务
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
|
||||
// 注册 ScheduledAnnotationBeanPostProcessor
|
||||
return new ScheduledAnnotationBeanPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## ScheduledAnnotationBeanPostProcessor
|
||||
- 关注application事件,以及spring生命周期相关的接口实现
|
||||
```java
|
||||
/**
|
||||
* application 事件
|
||||
* @param event the event to respond to
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (event.getApplicationContext() == this.applicationContext) {
|
||||
// Running in an ApplicationContext -> register tasks this late...
|
||||
// giving other ContextRefreshedEvent listeners a chance to perform
|
||||
// their work at the same time (e.g. Spring Batch's job registration).
|
||||
// 注册定时任务
|
||||
finishRegistration();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
|
||||
bean instanceof ScheduledExecutorService) {
|
||||
// Ignore AOP infrastructure such as scoped proxies.
|
||||
return bean;
|
||||
}
|
||||
|
||||
// 当前类
|
||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
|
||||
if (!this.nonAnnotatedClasses.contains(targetClass)) {
|
||||
// 方法扫描,存在 Scheduled、Schedules 注解的全部扫描
|
||||
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
|
||||
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
|
||||
|
||||
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
|
||||
method, Scheduled.class, Schedules.class);
|
||||
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
|
||||
});
|
||||
if (annotatedMethods.isEmpty()) {
|
||||
this.nonAnnotatedClasses.add(targetClass);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Non-empty set of methods
|
||||
annotatedMethods.forEach((method, scheduledMethods) ->
|
||||
// 处理 scheduled 相关信息
|
||||
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
|
||||
"': " + annotatedMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
```
|
||||
|
||||
- 处理定时任务注解
|
||||
```java
|
||||
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
|
||||
try {
|
||||
Runnable runnable = createRunnable(bean, method);
|
||||
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 = scheduled.initialDelay();
|
||||
// 延迟执行时间
|
||||
String initialDelayString = scheduled.initialDelayString();
|
||||
// 是否有延迟执行的时间
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check cron expression
|
||||
// 获取cron表达式
|
||||
String cron = scheduled.cron();
|
||||
// cron表达式是否存在
|
||||
if (StringUtils.hasText(cron)) {
|
||||
// 获取时区
|
||||
String zone = scheduled.zone();
|
||||
if (this.embeddedValueResolver != null) {
|
||||
// 字符串转换
|
||||
cron = this.embeddedValueResolver.resolveStringValue(cron);
|
||||
zone = this.embeddedValueResolver.resolveStringValue(zone);
|
||||
}
|
||||
if (StringUtils.hasLength(cron)) {
|
||||
// cron 是否延迟
|
||||
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
|
||||
processedSchedule = true;
|
||||
if (!Scheduled.CRON_DISABLED.equals(cron)) {
|
||||
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 = scheduled.fixedDelay();
|
||||
// 间隔时间>0
|
||||
if (fixedDelay >= 0) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
// 创建任务,间隔时间定时任务
|
||||
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
|
||||
}
|
||||
// 延迟时间
|
||||
String fixedDelayString = scheduled.fixedDelayString();
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check fixed rate
|
||||
// 获取调用频率
|
||||
long fixedRate = scheduled.fixedRate();
|
||||
if (fixedRate >= 0) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
// 创建调用频率的定时任务
|
||||
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
|
||||
}
|
||||
String fixedRateString = scheduled.fixedRateString();
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether we had any attribute set
|
||||
Assert.isTrue(processedSchedule, errorMessage);
|
||||
|
||||
// Finally register the scheduled tasks
|
||||
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());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 定时任务
|
||||
- CronTask
|
||||
- cron定时任务
|
||||
- FixedDelayTask
|
||||
- 间隔时间的定时任务
|
||||
- FixedRateTask
|
||||
- 调用频率的定时任务
|
||||
- ScheduledTask
|
||||
- 定时任务对象
|
||||
### cron 表达式解析
|
||||
- `org.springframework.scheduling.support.CronSequenceGenerator.doParse`
|
||||
```java
|
||||
private void doParse(String[] fields) {
|
||||
setNumberHits(this.seconds, fields[0], 0, 60);
|
||||
setNumberHits(this.minutes, fields[1], 0, 60);
|
||||
setNumberHits(this.hours, fields[2], 0, 24);
|
||||
setDaysOfMonth(this.daysOfMonth, fields[3]);
|
||||
setMonths(this.months, fields[4]);
|
||||
setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8);
|
||||
|
||||
if (this.daysOfWeek.get(7)) {
|
||||
// Sunday can be represented as 0 or 7
|
||||
this.daysOfWeek.set(0);
|
||||
this.daysOfWeek.clear(7);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 执行定时任务
|
||||
- 这里以 CronTask 任务进行分析,其他定时任务同理
|
||||
- `org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleCronTask`
|
||||
```java
|
||||
@Nullable
|
||||
public ScheduledTask scheduleCronTask(CronTask task) {
|
||||
// 从未执行的任务列表中删除,并且获取这个任务
|
||||
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
|
||||
boolean newTask = false;
|
||||
// 没有这个任务
|
||||
if (scheduledTask == null) {
|
||||
scheduledTask = new ScheduledTask(task);
|
||||
newTask = true;
|
||||
}
|
||||
// 任务调度器是否为空
|
||||
if (this.taskScheduler != null) {
|
||||
|
||||
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
|
||||
}
|
||||
else {
|
||||
// 添加到cron任务列表
|
||||
addCronTask(task);
|
||||
// 保存到没有执行的任务中
|
||||
this.unresolvedTasks.put(task, scheduledTask);
|
||||
}
|
||||
return (newTask ? scheduledTask : null);
|
||||
}
|
||||
|
||||
```
|
Loading…
x
Reference in New Issue
Block a user