前言
笔者目前在一家银行工作,正在参与手机银行项目的功能开发,正好碰到一家分行搬迁,直接合并到总行营业部,因此在手机银行上涉及到网点,开户机构的功能的页面,都需要不展示该机构,当笔者刚拿到这个需求的时候,非常惊讶,认为这种功能应该早就做好了,应该是可以直接在后管中进行配置,一通分析下来,发现居然并没有这种功能。
应业务老师的要求,控制dept的这种功能应该由核心系统控制,其他系统从核心系统定时获取最新的dept,另外,业务老师决定将搬迁合并视为特殊情况,启用表中的预留的字段,定义为特殊机构,方便以后其它的特殊情况进行扩展。
因此,笔者需要开发一个定时任务,定时从核心系统获取dept信息。在开发这个功能之余,笔者对于Spring如何是实现定时任务非常好奇,于是打算阅读源码,了解其底层原理。
1. 如何开启定时任务?
要开启一个定时任务,在SpringBoot中非常方便:
启动类添加@EnableScheduling
注解在自己的定时任务类中使用@Scheduled
注解
@Component
public class Task1 {
//每10秒执行一次
@Scheduled(fixedRate = 10000)
public void sayHello() {
System.out.println("hello");
}
}
2. @Scheduled注解
@EnableScheduling
注解开启了定时任务的功能后,Spring就能识别到@Scheduled
标注的方法,并且按照参数配置,定时执行任务,先来看看这个注解的组成。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String cron() default "";
String zone() default "";
long fixedDelay() default -1L;
String fixedDelayString() default "";
long fixedRate() default -1L;
String fixedRateString() default "";
long initialDelay() default -1L;
String initialDelayString() default "";
}
@Scheduled
有8个参数,先来看看这8个参数都有什么用:
cron:可以通过cron表达式的方式来配置定时任务的执行周期zone:指明cron表达式的时区fixedDelay:上一个任务调用结束后---下一次任务调用开始的间隔(要等待上次任务结束)fixedDelayString:同上,只不过给的值是String类型fixedRate:以固定间隔调用该方法(不需要等待上次任务完成)fixedRateString:同上,只不过给的值是String类型initialDelay:第一次按照fixedDelay或fixedRate执行该方法之前的等待时间initialDelayString:同上,只不过给的值是String类型
cron表达式这里不做介绍,通常可以使用一些在线的生成器来生成想要的cron表达式
3. 原理分析
其实,Spring能够实现定时任务,依赖于Spring的BeanPostProcessor接口,主要过程如下:
通过ScheduledAnnotationBeanPostProcessor
类中的postProcessAfterInitialization()
方法,获取所有被@Scheduled
标注的方法processScheduled()
中,对于一个方法上标注的多个@Scheduled
注解会按照cron>fixedDelay>fixedRate的顺序放到任务队列中,并且之后会按照这个顺序执行注册定时任务,即让bean与这些定时任务形成映射关系(记录这个bean有哪些定时任务)由ScheduledTaskRegistrar
通过scheduleTasks()
方法来调度任务队列中的任务
public Object postProcessAfterInitialization(final Object bean, String beanName) {
Class targetClass = AopUtils.getTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
//获取@Scheduled标注的所有方法
Map annotatedMethods = MethodIntrospector.selectMethods(targetClass,
new MethodIntrospector.MetadataLookup() {
@Override
public Set inspect(Method method) {
Set 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: " + bean.getClass());
}
}
else {
// Non-empty set of methods
for (Map.Entry entry : annotatedMethods.entrySet()) {
Method method = entry.getKey();
for (Scheduled scheduled : entry.getValue()) {
//执行这些方法
processScheduled(scheduled, method, bean);
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
...
//解析initialDelayString参数
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
...
}
//解析cron参数
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
...
//存放到任务队列中并调度
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
...
//解析fixedDelay参数
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0L) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
...
//存放到任务队列中并调度
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
//解析fixedRate参数
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0L) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
...
//存放到任务队列中并调度
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
Assert.isTrue(processedSchedule, errorMessage);
Map var19 = this.scheduledTasks;
//,注册定时任务,将任务存放在map中,让其与bean形成映射关系
synchronized(this.scheduledTasks) {
Set registeredTasks = (Set)this.scheduledTasks.get(bean);
if (registeredTasks == null) {
registeredTasks = new LinkedHashSet(4);
//将任务存放在map中
this.scheduledTasks.put(bean, registeredTasks);
}
((Set)registeredTasks).addAll(tasks);
}
} catch (IllegalArgumentException ex) {
throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
以上就是定时任务的原理,看源码一定不能像看书一样从头看到尾,而是有针对的去阅读,当在工作中接触到新的东西的时候,在空闲时间去了解背后的底层原理,这样才能记忆的更加深刻,理解的更加透彻。
发表评论