SpringBoot IoC(5)功能注解

SpringBoot简化了很多配置,甚至有些功能无需配置,只要一个默认属性的注解就能实现,比如之前曾讲到过的与事务相关的@EnableTransactionManagement,在SpringBoot中还有很多这样的注解,虽然功能不一样,但它们的实现原理都如出一辙。以下将对两个典型的注解进行分析。

@EnableScheduling

如果业务中需要用到定时任务的话,使用@Scheduled配合@EnableScheduling就能轻松实现。

image.png

导入了SchedulingConfiguration这个类,于是进入。

image.png

导入了ScheduledAnnotationBeanPostProcessor这个类,于是进入。

定时任务方法分析

ScheduledAnnotationBeanPostProcessor是一个后置处理器,重点在postProcessAfterInitialization方法。

方法前半部分是找到带有@Scheduled@Schedules注解的方法,后半部分的过程为:

  1. 将定时任务方法封装成ScheduledMethodRunnable对象,它的run方法是利用反射执行定时任务方法
  2. 分析注解Scheduled属性,看它是cron、fixedDelay、fixedRate中的哪一种,封装成对象,最终在registrar成员变量体现。fixedDelay指上次执行完毕之后的间隔时间,fixedRate指上次执行开始之后的间隔时间。

image.png

以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();
复制代码

实现SchedulingConfigurerTaskSchedulerScheduledExecutorService任意一种都可以自定义线程池属性,如果未做任何配置,返回的是ThreadPoolTaskScheduler对象实例。

image.png

如图,对象实例在TaskSchedulingAutoConfiguration定义,ConditionalOnMissingBean告诉我们如果做了任意配置,就不会返回ThreadPoolTaskScheduler对象实例。注意到线程池名字和核心数由TaskSchedulingProperties决定,在配置文件中定义spring.task.scheduling.threadNamePrefixspring.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。

image.png

导入了MapperScannerRegistrar这个类,于是进入。

观察到它实现了ImportBeanDefinitionRegistrar接口,于是找到它的registerBeanDefinitions方法,它给IoC容器注入了MapperScannerConfigurer这个bean,这个bean实现了BeanDefinitionRegistryPostProcessor接口,于是找到它的postProcessBeanDefinitionRegistry方法,最后一行scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)),原来是在这里扫描了路径下的文件,返回接口类,最后将接口类添加进bdm。

总结

对功能注解简单概括:

  1. 进入注解内部
  2. 查看注解引入类
  3. 对引入类分析
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享