Spring中@EnableScheduling注解的工作原理詳解
概述
- 開發(fā)人員使用注解 @EnableScheduling;
- 注解@EnableScheduling導入SchedulingConfiguration;
- SchedulingConfiguration定義基礎設施bean ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor;
- ScheduledAnnotationBeanPostProcessor在容器啟動時做如下事情
- 登記所有使用@Scheduled注解的bean方法到一個ScheduledTaskRegistrar,供調度任務執(zhí)行器TaskScheduler執(zhí)行。
- 為ScheduledTaskRegistrar指定任務執(zhí)行器TaskScheduler,該任務執(zhí)行器來自容器中的bean TaskScheduler/ScheduledExecutorService(如果不指定,ScheduledTaskRegistrar自己會本地創(chuàng)建一個ConcurrentTaskScheduler)
- 告訴ScheduledTaskRegistrar將所注冊的調度任務,也就是使用@Scheduled注解的bean方法,調度到任務執(zhí)行器TaskScheduler執(zhí)行。
詳細分析
開發(fā)人員注解@EnableScheduling
@EnableScheduling // <==
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}@EnableScheduling注解導入配置@SchedulingConfiguration
@Import(SchedulingConfiguration.class) // <==
public @interface EnableScheduling {
}SchedulingConfiguration 定義bean scheduledAnnotationProcessor
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
// Bean 名稱使用 :
// org.springframework.context.annotation.internalScheduledAnnotationProcessor
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 定義為基礎設施bean
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization檢測處理每個@Scheduled注解的方法 ScheduledAnnotationBeanPostProcessor實現了DestructionAwareBeanPostProcessor,BeanPostProcessor等接口。作為一個BeanPostProcessor,ScheduledAnnotationBeanPostProcessor會針對每個bean的創(chuàng)建,在bean生命周期方法#postProcessAfterInitialization中,掃描該bean中使用了注解@Scheduled的方法,
@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);
// this.nonAnnotatedClasses 是一個緩存,用于記錄處理過程中所發(fā)現的不包含任何被@Scheduled注解的方法的類
if (!this.nonAnnotatedClasses.contains(targetClass)) {
// 獲取類 targetClass 上所有使用注解 @Scheduled 的方法
// 注意 : 某個方法上可能同時使用多個注解 @Scheduled ,所以以下 annotatedMethods 的每個 Entry 是
// 一個方法對應一個 @cheduled 集合
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()) {
// 如果當前類 targetClass 不包含任何使用注解 @Scheduled 的方法,將其添加到 this.nonAnnotatedClasses
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
else {
// Non-empty set of methods
// 當前類 targetClass 上找到了使用注解 @Scheduled 的方法,記錄在 annotatedMethods 中,
// 現在將它們逐個處理,使用的處理為方法 processScheduled
annotatedMethods.forEach((method, scheduledMethods) ->
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}#rocessScheduled處理方法上的每個@Scheduled注解,生成一個ScheduledTask并登記到this.scheduledTasks。this.scheduledTasks數據結構為 :
- Map數據類型;
- key是一個對象,其類就是含有方法使用了注解@Scheduled的類;
- value是一個ScheduledTask集合,方法上的每個注解@Scheduled對應一個ScheduledTask;
實例分析
舉例來講,加入組件MyScheduledTask類中有兩個方法#method1,#method2上一用使用了五個注解Scheduled,則this.scheduledTasks會出現一項針對組件MyScheduledTask bean的項,key是組件MyScheduledTask bean對象自身,value是五個ScheduledTask。
/**
* Process the given {@code @Scheduled} method declaration on the given bean.
* @param scheduled the @Scheduled annotation
* @param method the method that the annotation has been declared on
* @param bean the target bean instance
* @see #createRunnable(Object, Method)
*/
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
// 將使用了 @Scheduled 注解的方法包裝成一個 Runnable 對象 , 隨后構建 ScheduledTask 對象時
// 會用得到
Runnable runnable = createRunnable(bean, method);
// 用于記錄當前 @Scheduled 注解是否已經被處理,初始化為 false
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
// 用于保存針對當前 @Scheduled 注解生成的 ScheduledTask,
// 該方法完成時,該集合內元素數量通常為 1
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// Determine initial delay
// 確定 initial delay 屬性 : 基于注解屬性 initialDelay 或者 initialDelayString 分析得到,
// 二者只能使用其中之一
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();
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 (!Scheduled.CRON_DISABLED.equals(cron)) {
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
// 包裝成為一個 CronTask
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();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
// 包裝成為一個 FixedDelayTask
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");
}
// 包裝成為一個 FixedDelayTask
tasks.add(this.registrar.scheduleFixedDelayTask(
new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
}
// Check fixed rate
// 檢查這是否是一個固定周期執(zhí)行類型的注解
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");
}
// 包裝成為一個 FixedRateTask
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());
}
}經過ScheduledAnnotationBeanPostProcessor以上這些處理,每個bean中所包含的@Scheduled注解都被發(fā)現了,這樣的每條信息最終對應生成一個ScheduledTask,該ScheduledTask會被ScheduledTaskRegistrar registrar登記調度。這意味著該ScheduledTask從此刻起在程序運行期間就會按照@Scheduled注解所設定的時間點被執(zhí)行。
到此這篇關于Spring中@EnableScheduling注解的工作原理詳解的文章就介紹到這了,更多相關@EnableScheduling注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解基于Spring Cloud幾行配置完成單點登錄開發(fā)
這篇文章主要介紹了詳解基于Spring Cloud幾行配置完成單點登錄開發(fā),小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02
springboot多模塊項目mvn打包遇到存在依賴但卻無法發(fā)現符號問題
在SpringBoot多模塊項目中,如果遇到依賴存在但無法發(fā)現符號的問題,常見原因可能是pom.xml配置問題,例如,如果某個模塊僅作為依賴而不是啟動工程,不應在其pom中配置spring-boot-maven-plugin插件,因為這將影響jar包的生成方式2024-09-09
Java集合排序規(guī)則接口Comparator用法解析
這篇文章主要介紹了Java集合排序規(guī)則接口Comparator用法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-09-09

