任务调度处理系列之 Spring源码分析-【SchedulingConfigurer实现原理】

/**

  • The default name of the {@link TaskScheduler} bean to pick up: “taskScheduler”.

  • Note that the initial lookup happens by type; this is just the fallback

  • in case of multiple scheduler beans found in the context.

  • @since 4.2

*/

// 看着注释就知道,和@Async的默认处理一样~~~~先类型 在回退到名称

public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = “taskScheduler”;

// 调度器(若我们没有配置,它是null的)

@Nullable

private Object scheduler;

// 这些都是Awire感知接口注入进来的~~

@Nullable

private StringValueResolver embeddedValueResolver;

@Nullable

private String beanName;

@Nullable

private BeanFactory beanFactory;

@Nullable

private ApplicationContext applicationContext;

// ScheduledTaskRegistrar:ScheduledTask注册中心,ScheduledTaskHolder接口的一个重要的实现类,维护了程序中所有配置的ScheduledTask

// 内部会处理调取器得工作,因此我建议先移步,看看这个类得具体分析

private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();

// 缓存,没有被标注注解的class们

// 这有个技巧,使用了newSetFromMap,自然而然的这个set也就成了一个线程安全的set

private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

// 缓存对应的Bean上 里面对应的 ScheduledTask任务。可议有多个哦~~

// 注意:此处使用了IdentityHashMap

private final Map<Object, Set> scheduledTasks = new IdentityHashMap<>(16);

// 希望此processor是最后执行的~

@Override

public int getOrder() {

return LOWEST_PRECEDENCE;

}

//Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke the scheduled methods

// 也可以是JDK的ScheduledExecutorService(内部会给你包装成一个TaskScheduler)

// 若没有指定。那就会走默认策略:去从起中先按照类型找TaskScheduler该类型(或者ScheduledExecutorService这个类型也成)的。

// 若有多个该类型或者找不到,就安好"taskScheduler"名称去找

// 再找不到,就用系统默认的:

public void setScheduler(Object scheduler) {

this.scheduler = scheduler;

}

// 此方法会在该容器内所有的单例Bean已经初始化全部结束后,执行

@Override

public void afterSingletonsInstantiated() {

// Remove resolved singleton classes from cache

// 因为已经是最后一步了,所以这个缓存可议清空了

this.nonAnnotatedClasses.clear();

// 在容器内运行,ApplicationContext都不会为null

if (this.applicationContext == null) {

// Not running in an ApplicationContext -> register tasks early…

// 如果不是在ApplicationContext下运行的,那么就应该提前注册这些任务

finishRegistration();

}

}

// 兼容容器刷新的时间(此时候容器硬启动完成了) 它还在afterSingletonsInstantiated的后面执行

@Override

public void onApplicationEvent(ContextRefreshedEvent event) {

// 这个动作务必要做:因为Spring可能有多个容器,所以可能会发出多个ContextRefreshedEvent 事件

// 显然我们只处理自己容器发出来得事件,别的容器发出来我不管~~

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).

// 为其他ContextRefreshedEvent侦听器提供同时执行其工作的机会(例如,Spring批量工作注册)

finishRegistration();

}

}

private void finishRegistration() {

// 如果setScheduler了,就以调用者指定的为准~~~

if (this.scheduler != null) {

this.registrar.setScheduler(this.scheduler);

}

// 这里继续厉害了:从容器中找到所有的接口SchedulingConfigurer的实现类(我们可议通过实现它定制化scheduler)

if (this.beanFactory instanceof ListableBeanFactory) {

Map<String, SchedulingConfigurer> beans =

((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);

List configurers = new ArrayList<>(beans.values());

// 同@Async只允许设置一个不一样的是,这里每个都会让它生效

// 但是平时使用,我们自顶一个类足矣~~~

AnnotationAwareOrderComparator.sort(configurers);

for (SchedulingConfigurer configurer : configurers) {

configurer.configureTasks(this.registrar);

}

}

// 至于task是怎么注册进registor的,请带回看postProcessAfterInitialization这个方法的实现

// 有任务并且registrar.getScheduler() == null,那就去容器里找来试试~~~

if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {

// 这块逻辑和@Async的处理一毛一样。忽略了 主要看看resolveSchedulerBean()这个方法即可

}

this.registrar.afterPropertiesSet();

}

// 从容器中去找一个

private T resolveSchedulerBean(BeanFactory beanFactory, Class schedulerType, boolean byName) {

// 若按名字去查找,那就按照名字找

if (byName) {

T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);

// 这个处理非常非常有意思,就是说倘若找到了你可以在任意地方直接@Autowired这个Bean了,可以拿这个共用Scheduler来调度我们自己的任务啦~~

if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {

((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(

DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);

}

return scheduler;

}

// 按照schedulerType该类型的名字匹配resolveNamedBean 底层依赖:getBeanNamesForType

else if (beanFactory instanceof AutowireCapableBeanFactory) {

NamedBeanHolder holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);

if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {

((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);

}

return holder.getBeanInstance();

}

// 按照类型找

else {

return beanFactory.getBean(schedulerType);

}

}

@Override

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {

}

@Override

public Object postProcessBeforeInitialization(Object bean, String beanName) {

return bean;

}

// Bean初始化完成后执行。去看看Bean里面有没有标注了@Scheduled的方法~~

@Override

public Object postProcessAfterInitialization(final Object bean, String beanName) {

// 拿到目标类型(因为此类有可能已经被代理过)

Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);

// 这里对没有标注注解的类做了一个缓存,防止从父去扫描(毕竟可能有多个容器,可能有重复扫描的现象)

if (!this.nonAnnotatedClasses.contains(targetClass)) {

// 如下:主要用到了MethodIntrospector.selectMethods 这个内省方法工具类的这个g工具方法,去找指定Class里面,符合条件的方法们

Map<Method, Set> annotatedMethods = MethodIntrospector.selectMethods(targetClass,

(MethodIntrospector.MetadataLookup<Set>) method -> {

//过滤Method的核心逻辑就是是否标注有此注解(Merged表示标注在父类、或者接口处也是ok的)

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

// 这里有一个双重遍历。因为一个方法上,可能重复标注多个这样的注解~~~~~

// 所以最终遍历出来后,就交给processScheduled(scheduled, method, bean)去处理了

annotatedMethods.forEach((method, scheduledMethods) ->

scheduledMethods.forEach(scheduled -> 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 {

// 标注此注解的方法必须是无参的方法

Assert.isTrue(method.getParameterCount() == 0, “Only no-arg methods may be annotated with @Scheduled”);

// 拿到最终要被调用的方法 做这么一步操作主要是防止方法被代理了

Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());

// 把该方法包装成一个Runnable 线程~~~

Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);

boolean processedSchedule = false;

String errorMessage = “Exactly one of the ‘cron’, ‘fixedDelay(String)’, or ‘fixedRate(String)’ attributes is required”;

// 装载任务,这里长度定为4,因为Spring认为标注4个注解还不够你用的?

Set tasks = new LinkedHashSet<>(4);

// Determine initial delay

// 计算出延时多长时间执行 initialDelayString 支持占位符如:@Scheduled(fixedDelayString = “${time.fixedDelay}”)

// 这段话得意思是,最终拿到一个initialDelay值~~~~~Long型的

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();

if (StringUtils.hasText(cron)) {

String zone = scheduled.zone();

// 由此可见,cron也可以使用占位符。把它配置在配置文件里就成~~~zone也是支持占位符的

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;

TimeZone timeZone;

if (StringUtils.hasText(zone)) {

timeZone = StringUtils.parseTimeZoneString(zone);

}

else {

timeZone = TimeZone.getDefault();

}

// 这个相当于,如果配置了cron,它就是一个task了,就可以吧任务注册进registrar里面了

// 这里面的处理是。如果已经有调度器taskScheduler了,那就立马准备执行了

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;

}

// 下面就不再说了,就是解析fixed delay、fixed rated、

// Check whether we had any attribute set

Assert.isTrue(processedSchedule, errorMessage);

// Finally register the scheduled tasks

// 最后吧这些任务都放在全局属性里保存起来~~~~

// getScheduledTasks()方法是会把所有的任务都返回出去的~~~ScheduledTaskHolder接口就一个Set getScheduledTasks();方法嘛

synchronized (this.scheduledTasks) {

Set registeredTasks = this.scheduledTasks.get(bean);

if (registeredTasks == null) {

registeredTasks = new LinkedHashSet<>(4);

this.scheduledTasks.put(bean, registeredTasks);

}

registeredTasks.addAll(tasks);

}

}

catch (IllegalArgumentException ex) {

throw new IllegalStateException(

“Encountered invalid @Scheduled method '” + method.getName() + "': " + ex.getMessage());

}

}

private static long parseDelayAsLong(String value) throws RuntimeException {

if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) {

return Duration.parse(value).toMillis();

}

return Long.parseLong(value);

}

private static boolean isP(char ch) {

return (ch == ‘P’ || ch == ‘p’);

}

//@since 5.0.2 获取到所有的任务。包含本实例的,以及registrar(手动注册)的所有任务

@Override

public Set getScheduledTasks() {

Set result = new LinkedHashSet<>();

synchronized (this.scheduledTasks) {

Collection<Set> allTasks = this.scheduledTasks.values();

for (Set tasks : allTasks) {

result.addAll(tasks);

}

}

result.addAll(this.registrar.getScheduledTasks());

return result;

}

// Bean销毁之前执行。移除掉所有的任务,并且取消所有的任务

@Override

public void postProcessBeforeDestruction(Object bean, String beanName) {

Set tasks;

synchronized (this.scheduledTasks) {

tasks = this.scheduledTasks.remove(bean);

}

if (tasks != null) {

for (ScheduledTask task : tasks) {

task.cancel();

}

}

}

}

 

这里已经把Spring怎么发现Task、执行Task的流程讲解通了。还有一个重要的类:ScheduledTaskRegistrar,它整体作为一个注册中心的角色,非常的重要 。

ScheduledTaskRegistrar

ScheduledTask注册中心,ScheduledTaskHolder接口的一个重要的实现类,维护了程序中所有配置的ScheduledTask

//@since 3.0 它在Spring3.0就有了

// 这里面又有一个重要的接口:我们可以通过扩展实现此接口,来定制化属于自己的ScheduledTaskRegistrar 下文会有详细介绍

// 它实现了InitializingBean和DisposableBean,所以我们也可以把它放进容器里面

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {

// 任务调度器

@Nullable

private TaskScheduler taskScheduler;

// 该类是JUC包中的类

@Nullable

private ScheduledExecutorService localExecutor;

// 对任务进行分类 管理

@Nullable

private List triggerTasks;

@Nullable

private List cronTasks;

@Nullable

private List fixedRateTasks;

@Nullable

private List fixedDelayTasks;

private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);

private final Set scheduledTasks = new LinkedHashSet<>(16);

// 调用者可以自己指定一个TaskScheduler

public void setTaskScheduler(TaskScheduler taskScheduler) {

Assert.notNull(taskScheduler, “TaskScheduler must not be null”);

this.taskScheduler = taskScheduler;

}

// 这里,如果你指定的是个TaskScheduler、ScheduledExecutorService都是可以的

// ConcurrentTaskScheduler也是一个TaskScheduler的实现类

public void setScheduler(@Nullable Object scheduler) {

if (scheduler == null) {

this.taskScheduler = null;

}

else if (scheduler instanceof TaskScheduler) {

this.taskScheduler = (TaskScheduler) scheduler;

}

else if (scheduler instanceof ScheduledExecutorService) {

this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));

}

else {

throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());

}

}

@Nullable

public TaskScheduler getScheduler() {

return this.taskScheduler;

}

// 将触发的任务指定为可运行文件(任务)和触发器对象的映射 typically custom implementations of the {@link Trigger} interface

// org.springframework.scheduling.Trigger

public void setTriggerTasks(Map<Runnable, Trigger> triggerTasks) {

this.triggerTasks = new ArrayList<>();

triggerTasks.forEach((task, trigger) -> addTriggerTask(new TriggerTask(task, trigger)));

}

// 主要处理 <task:*>这种配置

public void setTriggerTasksList(List triggerTasks) {

this.triggerTasks = triggerTasks;

}

public List getTriggerTaskList() {

return (this.triggerTasks != null? Collections.unmodifiableList(this.triggerTasks) :

Collections.emptyList());

}

// 这个一般是最常用的 CronTrigger:Trigger的一个实现类。另一个实现类为PeriodicTrigger

public void setCronTasks(Map<Runnable, String> cronTasks) {

this.cronTasks = new ArrayList<>();

cronTasks.forEach(this::addCronTask);

}

public void setCronTasksList(List cronTasks) {

this.cronTasks = cronTasks;

}

public List getCronTaskList() {

return (this.cronTasks != null ? Collections.unmodifiableList(this.cronTasks) :

Collections.emptyList());

}

// 判断是否还有任务

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后总结

ActiveMQ+Kafka+RabbitMQ学习笔记PDF

image.png

  • RabbitMQ实战指南

image.png

  • 手写RocketMQ笔记

image.png

  • 手写“Kafka笔记”

image

关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-VE4elnNU-1712890731277)]

最后总结

ActiveMQ+Kafka+RabbitMQ学习笔记PDF

[外链图片转存中…(img-9LypL2Hp-1712890731277)]

  • RabbitMQ实战指南

[外链图片转存中…(img-xzddNaMc-1712890731277)]

  • 手写RocketMQ笔记

[外链图片转存中…(img-W9FdteUx-1712890731278)]

  • 手写“Kafka笔记”

[外链图片转存中…(img-HeDfSEov-1712890731278)]

关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-LZ0kpCpu-1712890731278)]

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值