SpringBoot简化了很多配置,甚至有些功能无需配置,只要一个默认属性的注解就能实现,比如之前曾讲到过的与事务相关的@EnableTransactionManagement
,在SpringBoot中还有很多这样的注解,虽然功能不一样,但它们的实现原理都如出一辙。以下将对两个典型的注解进行分析。
@EnableScheduling
如果业务中需要用到定时任务的话,使用@Scheduled
配合@EnableScheduling
就能轻松实现。
导入了SchedulingConfiguration
这个类,于是进入。
导入了ScheduledAnnotationBeanPostProcessor
这个类,于是进入。
定时任务方法分析
ScheduledAnnotationBeanPostProcessor
是一个后置处理器,重点在postProcessAfterInitialization
方法。
方法前半部分是找到带有@Scheduled
、@Schedules
注解的方法,后半部分的过程为:
- 将定时任务方法封装成
ScheduledMethodRunnable
对象,它的run方法是利用反射执行定时任务方法 - 分析注解Scheduled属性,看它是cron、fixedDelay、fixedRate中的哪一种,封装成对象,最终在registrar成员变量体现。fixedDelay指上次执行完毕之后的间隔时间,fixedRate指上次执行开始之后的间隔时间。
以cron属性为例,如图,其中expression是表达式,trigger分析了表达式并列出了执行时间,runnable则是执行定时任务方法的线程。
创建线程池
所有bean实例化完成以后,会发布ContextRefreshedEvent
事件。该后置处理器实现监听,最终进入finishRegistration
方法。
ScheduledAnnotationBeanPostProcessor->finishRegistration():
// 省略部分为查找IoC容器中实现SchedulingConfigurer接口的类,然后调用configureTasks方法设置线程池
......
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
try {
// 查找IoC容器中实现TaskScheduler接口的bean
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
} catch (NoUniqueBeanDefinitionException ex) {
try {
// 有多个的话,查找IoC容器中实现TaskScheduler接口且名称为taskScheduler的bean
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
} catch (NoSuchBeanDefinitionException ex2) {
}
} catch (NoSuchBeanDefinitionException ex) {
try {
// 若是没有实现TaskScheduler接口的bean ,查找IoC容器中实现ScheduledExecutorService接口的bean
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
} catch (NoUniqueBeanDefinitionException ex2) {
try {
// 有多个的话,查找IoC容器中实现ScheduledExecutorService接口且名称为taskScheduler的bean
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
} catch (NoSuchBeanDefinitionException ex3) {
}
} catch (NoSuchBeanDefinitionException ex2) {
}
}
}
this.registrar.afterPropertiesSet();
复制代码
实现SchedulingConfigurer
,TaskScheduler
,ScheduledExecutorService
任意一种都可以自定义线程池属性,如果未做任何配置,返回的是ThreadPoolTaskScheduler
对象实例。
如图,对象实例在TaskSchedulingAutoConfiguration
定义,ConditionalOnMissingBean
告诉我们如果做了任意配置,就不会返回ThreadPoolTaskScheduler
对象实例。注意到线程池名字和核心数由TaskSchedulingProperties
决定,在配置文件中定义spring.task.scheduling.threadNamePrefix
和spring.task.scheduling.pool.size
即可,不配置的话默认核心数为1,线程名格式为scheduling-xxx
。
执行任务
ScheduledTaskRegistrar->scheduleTasks():
// 就算前面没有设置,这边仍然会创建一个核心线程数为1的线程池
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
......
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
......
ScheduledTaskRegistrar->scheduleCronTask():
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
ThreadPoolTaskScheduler->schedule():
// 获取线程池
ScheduledExecutorService executor = getScheduledExecutor();
return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
// ReschedulingRunnable是一个线程类
ReschedulingRunnable->schedule():
// 计算出要执行定时任务的时间
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
// 与当前时间相差了多少
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
// 过initialDelay ms后执行任务,注意到this是自己
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
复制代码
cron的内部实现是计算好执行时间与当前时间的差,本质还是jdk的ScheduledThreadPoolExecutor
。在schedule()
方法中,this是ReschedulingRunnable对象,于是看它的run()方法。
ReschedulingRunnable->run():
// 实际开始执行时间
Date actualExecutionTime = new Date();
// 执行定时任务
super.run();
// 执行完任务的时间
Date completionTime = new Date();
// 记录时间
......
schedule();
复制代码
执行完任务后再一次调用schedule()
,schedule()
再一次执行run()方法,达到了循环的效果。
@MapperScan
@MapperScan
可以让属性路径下的类自动注入IoC容器,常用于注入Mapper。
导入了MapperScannerRegistrar这个类
,于是进入。
观察到它实现了ImportBeanDefinitionRegistrar
接口,于是找到它的registerBeanDefinitions
方法,它给IoC容器注入了MapperScannerConfigurer
这个bean,这个bean实现了BeanDefinitionRegistryPostProcessor
接口,于是找到它的postProcessBeanDefinitionRegistry
方法,最后一行scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS))
,原来是在这里扫描了路径下的文件,返回接口类,最后将接口类添加进bdm。
总结
对功能注解简单概括:
- 进入注解内部
- 查看注解引入类
- 对引入类分析