一、样例
1.1 demo代码
package com.lazy.snail;import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.Scheduled;/*** @ClassName MyTask* @Description TODO* @Author lazysnail* @Date 2024/10/29 17:56* @Version 1.0*/
@Component
public class MyTask {@Scheduled(fixedRate = 3 * 1 * 1000)public void doTask() {System.out.println("doTask");}
}
package com.lazy.snail;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;@Configuration
@EnableScheduling
@ComponentScan(basePackages = "com.lazy.snail")
public class AppConfig {}
1.2 demo概述
AppConfig中注解:
- @Configuration 声明配置类
- @EnableScheduling 启用Spring的计划任务执行功能
- @ComponentScan 组件扫描
MyTask中注解
- @Component 声明组件
- @Scheduled 标记该方法是一个计划任务
1.3 样例效果
运行结果:
二、源码流程
2.1 容器初始化阶段
- 内部bean定义信息注册,ConfigurationClassPostProcessor等
- 主配置类注册,AppConfig类
想了解过程,看此篇第二节
2.2 容器刷新阶段
2.2.1 bean工厂后置处理器处理
ConfigurationClassPostProcessor实例化
ConfigurationClassPostProcessor处理bean定义信息的注册
2.2.2 主配置类解析
主要关注AppConfig类上@EnableScheduling注解。
// ConfigurationClassParser
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {// @Import注解处理 @EnableScheduling中有@Import(SchedulingConfiguration.class)processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
}
2.2.2.1 @EnableScheduling
2.2.2.1.1 @Import处理
@EnableScheduling中有@Import注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {}
SchedulingConfiguration源码:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();}}
2.2.2.1.2 小结
@EnableScheduling通过@Import引入了一个配置类SchedulingConfiguration。
SchedulingConfiguration中的@Bean注解的方法会在后面实例化一个
ScheduledAnnotationBeanPostProcessor。
@EnableScheduling --> ScheduledAnnotationBeanPostProcessor
2.2.3 配置类bean定义信息加载
加载结果:
2.2.4 注册bean后置处理器
实例化ScheduledAnnotationBeanPostProcessor
通过SchedulingConfiguration中的工厂方法scheduledAnnotationProcessor构造ScheduledAnnotationBeanPostProcessor。
2.2.5 单例实例化
主要看带有@Schduled注解方法的MyTask类的实例化过程。
createBeanInstance(构造)和populateBean(属性赋值)跟普通的bean一样。
重点在于initializeBean方法中,bean后置处理器在bean初始化后的应用。
核心是ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization方法。
// ScheduledAnnotationBeanPostProcessor
public Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||bean instanceof ScheduledExecutorService) {// 忽略AOP基础设施类return bean;}Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);if (!this.nonAnnotatedClasses.contains(targetClass) &&AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {// 找被@Scheduled注解的方法// targetClass:MyTaskMap<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);});if (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);if (logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " + targetClass);}} else {// 遍历处理方法annotatedMethods.forEach((method, scheduledAnnotations) ->scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));if (logger.isTraceEnabled()) {logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +"': " + annotatedMethods);}}}return bean;
}protected void processScheduled(Scheduled scheduled, Method method, Object bean) {try {// 创建一个runnable// bean:MyTask method:doTask()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);// 省略部分代码...long fixedRate = convertToMillis(scheduled.fixedRate(), scheduled.timeUnit());if (fixedRate >= 0) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule = true;// 新增一个ScheduledTask添加到fixedRateTasks// 同时新增至Set<ScheduledTask>集合中tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));}// 省略部分代码...} catch (IllegalArgumentException ex) {throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());}
}
三、总结
1. 注解解析
当你使用@Scheduled
时,Spring会通过反射机制解析这个注解。具体步骤如下:
- 注解的定义:
@Scheduled
注解有几个重要的属性,比如fixedRate
、fixedDelay
和cron
。在上述例子中,我们使用了fixedRate
。 - 元注解:
@Scheduled
是一个复合注解,可以使用@Target
和@Retention
来定义它的目标和生命周期。
2. 容器的初始化与实例化
在Spring应用程序启动时,容器会执行以下步骤来初始化和实例化带有@Scheduled
注解的组件:
- 扫描组件:通过
@ComponentScan
注解,Spring会扫描指定包中的类,寻找带有@Scheduled
注解的组件。 - Bean的注册:找到的组件会被注册到
ApplicationContext
中,成为一个Spring Bean。 - ScheduledAnnotationBeanPostProcessor:这是一个后处理器,它会在初始化Bean时处理
@Scheduled
注解。它的postProcessBeforeInitialization
方法会查找所有的@Scheduled
注解并进行解析。
3. 任务的调度
任务的调度通过ScheduledAnnotationBeanPostProcessor
来完成:
- 任务计划:该后处理器在处理
@Scheduled
注解时,会创建ScheduledTask
对象,使用Spring的TaskScheduler
来安排任务。 - 使用Scheduler:Spring提供了多种调度器,如
ConcurrentTaskScheduler
和ThreadPoolTaskScheduler
。这些调度器负责实际的任务执行。 - 执行任务:当达到预定的时间(例如,每5秒),调度器会调用
performTask()
方法,执行定时任务。