文章目录

  • 前言
    • 1、@SpringBootApplication注解剖析
      • 1.1、@SpringBootConfiguration
      • 1.2、@EnableAutoConfiguration
        • 1.2.1@AutoConfigurationPackage
        • 1.2.2 @Import(AutoConfigurationImportSelector.class)
      • 1.3@ComponentScan
    • 2.SpringApplication.run(启动类.class,args)方法剖析
      • 2.1实例化SpringApplication对象
      • 2.2 run(args):调用run方法
        • 2.2.1run(args)方法——第三步之创建Spring应用上下文
        • 2.2.2run(args)方法——第四步之Spring应用上下文前置处理
        • 2.2.3run(args)方法——第五步之刷新容器

前言

SpringBoot 设计的目的是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。SpringBoot相对Spring的优点主要有两个:
1.起步依赖-会将很多jar包按照功能合并成stater整体进行版本管理和引用,解决Spring集成其他框架时jar版本管理问题
2.自动装配-引入相关的jar包后SpringBoot会自动注册一些比较关键的bean,并进行默认配置,不用我们进行特殊配置,解决Spring重量级XML配置问题。比如整合Mybatis时的SqlSessionFactory

注:其中起步依赖主要是解决版本控制问题,主要设计在于POM文件,这里主要探究第二优点自动装配。

SpringBoot启动依靠的是带有main方法的启动类,启动类的内容可以分为两个部分一个是启动类上@SpringBootApplication这个注解;第二部分是main方法里的SpringApplication.run(启动类.class,args)方法。下面主要就是分析一下这两部分分别是什么作用?完成了什么功能?怎样实现的自动装配?以及SpringBoot的启动流程分析。


1、@SpringBootApplication注解剖析

@SpringBootApplication是个组合注解包含四个元注解和@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan组成,下面逐个分析。

1.1、@SpringBootConfiguration

@SpringBootConfiguration也是一个组合注解由元注解和@Configuration构成,@Configuration是@Component的一个衍生注解主要作用是标记当前类是个配置类。

1.2、@EnableAutoConfiguration

@EnableAutoConfiguration也是一个组合注解由元注解和@AutoConfigurationPackage、@Import注解构成,Spring中有很多Enable开头的注解,其作用大都是借助@Import来收集并注册特定场景相关的bean。@EnableAutoConfiguration的主要作用就是借助@Import来收集并注册所有符合自动装配条件的bean。

1.2.1@AutoConfigurationPackage

注:很多人以为@SpringBootApplication可以扫描启动类当前包及其子包下面的类是由此注解完成的,是错误的

@AutoConfigurationPackage由元注解和@Import注解组成

@Import注解导入了AutoConfigurationPackages.Registrar.class实现了ImportBeanDefinitionRegistrar接口会调用registerBeanDefinitions方法

进入AutoConfigurationPackages#register,这里主要为Spring容器里注入了BasePackages的BeanDefinition目的是讲启动类的包路径传入容器,官网解释在后面整合jpa时会用到,这里暂不做探究。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {// 如果已经存在该 BEAN ,则修改其包(package)属性// BEAN 就是 AutoConfigurationPackages,用于存储自动配置包以供稍后引用if (registry.containsBeanDefinition(BEAN)) {BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();// 将构造函数的第一个参数设置为包名列表constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));// 如果不存在该 BEAN ,则创建一个 Bean ,并进行注册} else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(BasePackages.class);// 将beanClass设置为BasePackagesbeanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);// 将构造函数的第一个参数设置为包名列表,也就是BasePackages的构造函数beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 注册beanDefinitionregistry.registerBeanDefinition(BEAN, beanDefinition);}}

1.2.2 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector类是SpringBoot实现自动装配的关键。AutoConfigurationImportSelector实现了DeferredImportSelector接口会调用process和selectImports方法(在何处调用会在后面2.2.3讲到),其中selectImports方法会返回一个数组,数组中的类都会注册到Spring容器中

AutoConfigurationImportSelector.AutoConfigurationGroup.class
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {// 断言Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() -> String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));// 获得 AutoConfigurationEntry 对象// 核心方法:获取并过滤全部自动装配的类AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);// 添加到 autoConfigurationEntries 中this.autoConfigurationEntries.add(autoConfigurationEntry);// 添加到 entries 中for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}}
AutoConfigurationImportSelector.AutoConfigurationGroup.class
public Iterable<Entry> selectImports() {// 如果为空,则返回空数组if (this.autoConfigurationEntries.isEmpty()) {return Collections.emptyList();}// 获得 allExclusionsSet<String> allExclusions = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());// 获得 processedConfigurationsSet<String> processedConfigurations = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));// 从 processedConfigurations 中,移除排除的processedConfigurations.removeAll(allExclusions);// 处理,返回结果return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) // 排序.stream().map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) // 创建 Entry 对象.collect(Collectors.toList()); // 转换成 List}

由源码可以看到selectImports只是对process中封装到autoConfigurationEntries的结果进行分组排序等处理后返回,下面主要看到process中的getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);方法

AutoConfigurationImportSelector.class
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {// 1. 判断是否开启注解。如未开启,返回空串if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 2. 获得注解的属性AnnotationAttributes attributes = getAttributes(annotationMetadata);// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,// 将这些值作为自动配置类导入到容器中,自动配置类就生效了List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的configurations = removeDuplicates(configurations);// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)Set<String> exclusions = getExclusions(annotationMetadata, attributes);// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)checkExcludedClasses(configurations, exclusions);// 4.2 从 configurations 中,移除所有不希望自动配置的配置类configurations.removeAll(exclusions);// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。//@ConditionalOnMissingClass : classpath中不存在该类时起效//@ConditionalOnBean : DI容器中存在该类型Bean时起效//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效//@ConditionalOnExpression : SpEL表达式结果为true时//@ConditionalOnProperty : 参数设置或者值一致时起效//@ConditionalOnResource : 指定的文件存在时起效//@ConditionalOnJndi : 指定的JNDI存在时起效//@ConditionalOnJava : 指定的Java版本存在时起效//@ConditionalOnWebApplication : Web应用环境下起效//@ConditionalOnNotWebApplication : 非Web应用环境下起效//要判断@Conditional是否满足// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。configurations = filter(configurations, autoConfigurationMetadata);// 6. 将自动配置导入事件通知监听器//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,// 并触发fireAutoConfigurationImportEvents事件。fireAutoConfigurationImportEvents(configurations, exclusions);// 7. 创建 AutoConfigurationEntry 对象return new AutoConfigurationEntry(configurations, exclusions);}

1.3@ComponentScan

这个注解才是@SpringBootApplication会默认扫描启动类所在包以及子包路径下全部类的原因

2.SpringApplication.run(启动类.class,args)方法剖析

SpringApplication#run主要完成的事件可以分成两部分1.实例化SpringApplication对象2. run(args):调用run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {//SpringApplication的启动由两部分组成://1. 实例化SpringApplication对象//2. run(args):调用run方法return new SpringApplication(primarySources).run(args);}

2.1实例化SpringApplication对象

在实例化SpringApplication中设置的初始化器和监听器都是在/META-INF/spring.factories 中获取的

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.sources = new LinkedHashSet();this.bannerMode = Mode.CONSOLE;this.logStartupInfo = true;this.addCommandLineProperties = true;this.addConversionService = true;this.headless = true;this.registerShutdownHook = true;this.additionalProfiles = new HashSet();this.isCustomEnvironment = false;this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");//项目启动类 SpringbootDemoApplication.class设置为属性存储起来this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)this.webApplicationType = WebApplicationType.deduceFromClasspath();// 设置初始化器(Initializer),最后会调用这些初始化器//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 设置监听器(Listener)setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类this.mainApplicationClass = deduceMainApplicationClass();}

2.2 run(args):调用run方法

这里一个分为九步,最核心的是3、4、5下面会逐一介绍:

  1. 获取并启动监听器,监听器也是在spring.factories中获取的。
  2. 项目运行环境Environment的预配置
  3. 创建Spring容器
  4. Spring容器前置处理,这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
  5. 刷新容器
  6. Spring容器后置处理,扩展接口,设计模式中的模板方法,默认为空实现。
  7. 向监听器发出结束执行的事件通知
  8. 执行Runners
  9. 向监听器发布应用上下文就绪事件
public ConfigurableApplicationContext run(String... args) {// 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。StopWatch stopWatch = new StopWatch();stopWatch.start();// 初始化应用上下文和异常报告集合ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 配置 headless 属性configureHeadlessProperty();//   (1)获取并启动监听器SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {// 创建  ApplicationArguments 对象 初始化默认应用参数类// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//(2)项目运行环境Environment的预配置// 创建并配置当前SpringBoot应用将要使用的Environment// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体Banner printedBanner = printBanner(environment);// (3)创建Spring容器context = createApplicationContext();// 获得异常报告器 SpringBootExceptionReporter 数组//这一步的逻辑和实例化初始化器和监听器的一样,// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// (4)Spring容器前置处理//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。prepareContext(context, environment, listeners, applicationArguments,printedBanner);// (5):刷新容器refreshContext(context);// (6):Spring容器后置处理//扩展接口,设计模式中的模板方法,默认为空实现。// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理afterRefresh(context, applicationArguments);// 停止 StopWatch 统计时长stopWatch.stop();// 打印 Spring Boot 启动的时长日志。if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// (7)发出结束执行的事件通知listeners.started(context);// (8):执行Runners//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口callRunners(context, applicationArguments);} catch (Throwable ex) {// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}//   (9)发布应用上下文就绪事件//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,// 这样整个Spring Boot项目就正式启动完成了。try {listeners.running(context);} catch (Throwable ex) {// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}//返回容器return context;}

2.2.1run(args)方法——第三步之创建Spring应用上下文

这里根据实例SpringApplication时获取的应用类型来创建不同的应用上下文对象

SpringApplication.class
protected ConfigurableApplicationContext createApplicationContext() {// 根据 webApplicationType 类型,获得 ApplicationContext 类型// 这里创建容器的类型 还是根据webApplicationType进行判断的,// 该类型为SERVLET类型,所以会通过反射装载对应的字节码,// 也就是AnnotationConfigServletWebServerApplicationContext// 先判断有没有指定的实现类Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);break;case REACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}} catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);}}// 创建 ApplicationContext 对象return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}

2.2.2run(args)方法——第四步之Spring应用上下文前置处理

这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等

SpringApplication.class
private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {//设置容器环境,包括各种变量context.setEnvironment(environment);//设置上下文的 bean 生成器和资源加载器postProcessApplicationContext(context);//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)applyInitializers(context);//触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法listeners.contextPrepared(context);//记录启动日志if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beans//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}// Load the sources// 加载所有资源Set<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");//加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础load(context, sources.toArray(new Object[0]));//触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法listeners.contextLoaded(context);}

在前置处理中最核心的一步是加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础load(context, sources.toArray(new Object[0]));

BeanDefinitionLoader.class
private int load(Object source) {Assert.notNull(source, "Source must not be null");// 如果是 Class 类型,则使用 AnnotatedBeanDefinitionReader 执行加载if (source instanceof Class<?>) {return load((Class<?>) source);}// 如果是 Resource 类型,则使用 XmlBeanDefinitionReader 执行加载if (source instanceof Resource) {return load((Resource) source);}// 如果是 Package 类型,则使用 ClassPathBeanDefinitionScanner 执行加载if (source instanceof Package) {return load((Package) source);}// 如果是 CharSequence 类型,则各种尝试去加载if (source instanceof CharSequence) {return load((CharSequence) source);}// 无法处理的类型,抛出 IllegalArgumentException 异常throw new IllegalArgumentException("Invalid source type " + source.getClass());}

2.2.3run(args)方法——第五步之刷新容器

这里刷新容器最终调用的是AbstractApplication#refresh方法

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.// 第一步 刷新前的预处理prepareRefresh();// Tell the subclass to refresh the internal bean factory.// 第二步 1.创建BeanFactory实例,默认实现是DefaultListableBeanFactory//       2.解析XML中的<bean>为BeanDefition 并注册到 BeanDefitionRegistryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.// 第三步 BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 第四步 BeanFactory准备工作完成后的后置处理工作,钩子方法,等子类重写postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.// 第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean// 提前初始化工厂后置处理器bean,并调用postProcessBeanFactory方法//其中BeanFactoryPostProcessor比较重要的一个ConfigurationClassPostProcessor在这里调用,//用来遍历BeanDefinitionRegistry中现有的BeanDefinition解析@Import、@Configuration// 、@ComponentScan等注解将注解覆盖到的类也注册到BeanDefinitionRegistry中invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.// 第六步 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执registerBeanPostProcessors(beanFactory);// Initialize message source for this context.// 第七步 初始化MessageSource组件(做国际化功能;消息绑定,消息解析);initMessageSource();// Initialize event multicaster for this context.// 第八步 初始化事件派发器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 第九步 ⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑,钩子方法onRefresh();// Check for listener beans and register them.// 第十步 注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器beanregisterListeners();// Instantiate all remaining (non-lazy-init) singletons.// 第十一步 初始化所有剩下的⾮懒加载的单例bean//1).初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)//2).填充属性//3) .如果bean实现了Aware相关接口,则调用Aware接口的实现方法//4) .调用BeanPostProcessor处理器的前置方法//5).初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)//6).调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.// 第十二步 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 (ContextRefreshedEvent)finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

这也是Spring容器启动的经典方法这里就不每个步骤逐一过了,只重点关注和SpringBoot自动装配相关的步骤——第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean,就是在这一步解析的@SpringBootApplication这个组合注解,BeanFactoryPostProcessor比较重要的一个ConfigurationClassPostProcessor在这里调用,用来遍历BeanDefinitionRegistry中现有的BeanDefinition解析@Import、@Configuration 、@ComponentScan等注解将注解覆盖到的类也注册到BeanDefinitionRegistry中。

a.进入ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

ConfigurationClassPostProcessor.class
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId = System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);}this.registriesPostProcessed.add(registryId);// 核心方法processConfigBeanDefinitions(registry);}

b.进入ConfigurationClassPostProcessor#processConfigBeanDefinitions,这里遍历BeanDefinitionRegistry现有的全部类不包含@Configuration的类不会进行解析,这也是为什么配置类需要加@Configuration的原因

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}//如果不存在@Configuration直接return// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application contextSingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {// 核心解析方法parser.parse(candidates);parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);candidates.clear();if (registry.getBeanDefinitionCount() > candidateNames.length) {String[] newCandidateNames = registry.getBeanDefinitionNames();Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

c.进入ConfigurationClassParser#parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {// 注解解析BeanDefinition核心方法parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);}}this.deferredImportSelectorHandler.process();}

d.进入ConfigurationClassParser#processConfigurationClass

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}ConfigurationClass existingClass = this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// Otherwise ignore new imported config class; existing non-imported class overrides it.return;}else {// Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// Recursively process the configuration class and its superclass hierarchy.SourceClass sourceClass = asSourceClass(configClass);do {// 解析核心方法sourceClass = doProcessConfigurationClass(configClass, sourceClass);}while (sourceClass != null);this.configurationClasses.put(configClass, configClass);}

e.进入ConfigurationClassParser#doProcessConfigurationClass,在这里解析@PropertySource、@ComponentScan、@Import、@Bean、@ImportResource等注解,并将其覆盖的资源或类加载到容器上下文中,每个注解的具体解析细节这里就不深探讨了,主要梳理流程

ConfigurationClassParser.class
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes firstprocessMemberClasses(configClass, sourceClass);}//解析@PropertySource注解// Process any @PropertySource annotationsfor (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// 解析@ComponentScan注解// Process any @ComponentScan annotationsSet<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {// The config class is annotated with @ComponentScan -> perform the scan immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// 解析@Import注解// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), true);// 解析@ImportResource注解// Process any @ImportResource annotationsAnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// 解析@Bean注解// Process individual @Bean methodsSet<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;}

f.此处简单过一下@Import注解的解析过程,验证一下1.2.2 @Import(AutoConfigurationImportSelector.class)中process和selectImports方法的调用进入ConfigurationClassParser#processImports

在进入ConfigurationClassParser#handle

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);if (this.deferredImportSelectors == null) {DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();handler.register(holder);// 核心方法handler.processGroupImports();}else {this.deferredImportSelectors.add(holder);}}

进入ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports

public void processGroupImports() {for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {//getImports()中调用了AutoConfigurationImportSelector.AutoConfigurationGroup.class中的process和selectImportsgrouping.getImports().forEach(entry -> {ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());try {processImports(configurationClass, asSourceClass(configurationClass),asSourceClasses(entry.getImportClassName()), false);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +configurationClass.getMetadata().getClassName() + "]", ex);}});}}

进入ConfigurationClassParser.DeferredImportSelectorGrouping#getImports方法此处调用了AutoConfigurationImportSelector.AutoConfigurationGroup的process和selectImports

SpringBoot启动流程原理+自动装配原理相关推荐

  1. Alian解读SpringBoot 2.6.0 源码(十):启动流程之自动装配原理

    目录 一.背景 1.1.主类的加载 1.2.后置处理器的获取 二.配置类后处理器 2.1.获取配置类 2.2. 2.3.解析主类 2.3.1.整体解析过程 2.3.2.核心解析过程 2.3.3.延迟导 ...

  2. 我崩溃了!说一下springboot启动时的自动装配过程,大厂直通车!

    前言 在网络技术中基于浏览器的B/S结构无论在PC端还是手机端都充当着至关重要的角色. PC端自不必说,手机中很多应用虽然是以APP的形式存在,但它采用的还是B/S结构.如今日头条.微信的朋友圈等,这 ...

  3. 【SpringBoot】自动装配原理

    [SpringBoot]自动装配原理 文章目录 [SpringBoot]自动装配原理 一.pom.xml 1.spring-boot-dependencies 2.spring-boot-starte ...

  4. SPRINGBOOT启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

  5. SpringBoot自动装配原理浅析

    Springboot自动装配原理 SpringBoot是当下J2EE最为流行的框架,它有着轻量,快捷等特点,让程序员们可以专注在业务逻辑的编写上,而不用花太多的力气在一些环境的配置,整合组件的配置上面 ...

  6. 刨析 SpringBoot 自动装配原理,其实很简单

    J3 SpringBoot # 源码 # 自动装配 一日我在愉快得遨游时,看到有鱼友在问:SpringBoot 中引入了 Nacos 依赖为啥就可以直接使用 Nacos 中的相关功能呀! 认真思考了一 ...

  7. 面试终极宝典:Springboot自动装配原理

    Springboot的自动装配过程,网上大多都是罗列代码,即使看完了,也还存在很多的疑点.下面,这是我总结的在面试过程中介绍SpringBoot自动装配原理的话术,拿来即用就可. Springboot ...

  8. 雷神SpringBoot入门和自动装配原理

    SpringBoot-helloWord! 首先让当前的工程作为Springboot的子工程 <parent><groupId>org.springframework.boot ...

  9. SpringBoot自动装配原理解析——面试可以这样会回答

    1. 前言 SpringBoot是目前软件中最主流的框架,无论是工作还是面试基本都有它的身影,SpringBoot主要解决了传统spring的重量级xml配置Bean,实现了自动装配:所以,我们也常在 ...

  10. 根据自动装配原理在Springboot项目中自定义starter,并实现热插拔技术,自定义@enable

    根据自动装配原理在Springboot项目中自定义starter,并实现热插拔技术 自定义starter 简单步骤 使用测试 优化(热插拔技术) 自定义starter 简单步骤 创建项目,并引入aut ...

最新文章

  1. CCNA 第一章 网际互联
  2. python使用方法视频-Python读取视频的两种方法(imageio和cv2)
  3. UVa1153 Keep The Customer Satisfied(贪心)
  4. 跨域策略文件crossdomain.xml的配置方法
  5. Spring5参考指南: SpEL
  6. js 根据公历日期 算出农历_一招教会你公历换算成农历,要不要试试看
  7. .1 matlab,1 MATLAB集成环境
  8. 计算机技师工作调研,技师学院党委书记王庆余到计算机工程系进行“不忘初心、牢记使命”主题教育调研工作...
  9. 请谨慎设置WinForm控件DataGridView列的AutoSizeMode属性
  10. mysql数据库商户与买家_基于mysql实现离我最近的商家列表
  11. python system interpreter_2. Using the Python Interpreter:使用Python解释器
  12. 淘客渠道商备案及流程说明
  13. 【青少年编程】【三级】接苹果
  14. android浏览器全屏设置分辨率,小屏幕大世界 傲游云浏览器Android全屏模式
  15. 每天学习一个设计模式(八):创建型之抽象工厂模式
  16. COM08 -如何基于Davinci工具配置CAN通信协议栈实战课程【配置方法总述】
  17. VMware安装UniKylin系统
  18. USB串口导致鼠标乱跳
  19. 2018年python就业现状_2018年Python就业形势分析 拿数据说话
  20. SEM 同台展现实例

热门文章

  1. 发声计算机在线,文字转语音软件(文字转语音真人发声免费版)
  2. 在线答题助手c语言源码,很早之前发的逆水寒答题助手,开源!!自己可以修改成任何答题器源码!~~...
  3. 大学计算机基础总结与复习
  4. 用计算机怎样弄出告白密码,数字表白密码 表白密码大全
  5. 运筹学笔记 网络计划
  6. Linux基础知识大全(持续更新)
  7. 软件测试--IEEE829标准
  8. 语义分割之pspnet
  9. 微博签到打卡点数据集—北上广深杭
  10. C# NOPI读取Excel