往期博客


《Spring源码深度解析 郝佳 第2版》容器的基本实现与XML文件的加载

《Spring源码深度解析 郝佳 第2版》XML标签的解析

《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决

《Spring源码深度解析 郝佳 第2版》ApplicationContext

《Spring源码深度解析 郝佳 第2版》AOP

《Spring源码深度解析 郝佳 第2版》JDBC、MyBatis原理


SpringBoot是为了简化新Spring应用的初始化搭建以及开发过程。该框架使用了特定的方式来进行配置,主要特点

  1. 内嵌tomcat
  2. 简化Maven配置
  3. 自动配置Spring
  4. 对xml配置没有严格要求

目录

  1. 自己写一个Starter
  2. Spring启动流程run方法
  3. Starter自动化配置原理
  4. Conditional机制实现
  5. Tomcat启动

一、自己写一个Starter

写简单的栗子,基本步骤

  1. 新建Maven工程,在pom文件定义相关信息,如需要用到Spring则引入spring-boot-dependencies进而引入spring-boot-autoconfigure
  2. 定义一个接口如XxxService,对外暴露可以直接调用。定义一个实现类XxxServiceImpl实现接口,加上@Component注解放置Spring容器
  3. 编写XxxAutoConfiguration 自动配置类,类上标注@Configuration注解标识是一个类似xml的配置类,然后配置@ComponentScan扫描XxxServiceImpl所在的包(将扫包的义务交给Starter自己,如果都需要主模块main函数去扫包,则后续不易维护,在SpringBoot之前,需要将扫包配置到xml文件中,交给主模块完成,耦合度较高)
  4. 最后在Spring的根路径下,即resources文件夹下创建META-INF/spring.factories文件,用于指定当前Starter的XxxAutoConfiguration自动配置类的路径

// 1. 新建study-springboot-mystarter,配置xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>study-springboot-mystarter</artifactId><version>1.0-SNAPSHOT</version><!--    Starter导入自己的依赖--><!--    自动配置的依赖--><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.0.1.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement></project>

// 2. 编写对外暴露的接口HelloService以及实现类HelloServiceImpl

package service;public interface HelloService {public void sayHello();
}
package service.impl;import org.springframework.stereotype.Component;
import service.HelloService;@Component
public class HelloServiceImpl implements HelloService {public void sayHello() {System.out.println("你好呀!");}
}

// 3. 编写自动配置类

package config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration // 声明当前类是等同于xml的配置类
@ComponentScan({"service.impl"}) // 扫描包路径配置
public class HelloServiceAutoConfiguration {
}

// 4. 创建spring.factories文件

其实不在Starter中写 @ComponentScan,在Spring主服务模块是可以扫外部包的,但是有时候主服务扫描外部引入的Starter的全部包或者不知道需要扫哪些包,会很麻烦,耦合度较高,现在直接在Starter中的spring.factories指定特定的包即可,符合单一职责

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\config.HelloServiceAutoConfiguration

// 5.使用


二、SpringBoot启动run方法流程

往期博客分析过run方法,参考 《Spring源码深度解析 郝佳 第2版》ApplicationContext

// ConfigurableApplicationContextpublic ConfigurableApplicationContext run(String... args) {// 1. 计时器StopWatch stopWatch = new StopWatch();stopWatch.start();// 2. 容器ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();this.configureHeadlessProperty();SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting();Collection exceptionReporters;try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 3.准备环境,根据webApplicationType 准备上下文环境// webApplicationType 是在构造函数时根据ClassPath下内容设置的ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);Banner printedBanner = this.printBanner(environment);// 4. 创建容器,根据webApplicationType给contextClass赋值,然后初始化对应上下文容器context = this.createApplicationContext();exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);// 5. 准备容器相关this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 6. 刷新容器,这里委托ConfigurableApplicationContext接口的refesh(),// 真实调用的是AbstractApplicationContext.refresh(),也就是上面分析的部分this.refreshContext(context);this.afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}try {listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}}

其中关键步骤

  • // 4.创建容器,根据webApplicationType给contextClass赋值,然后初始化对应上下文容器,context = this.createApplicationContext();
  • // 5. 准备容器,调用prepareContext,内部调用load方法完成BeanDefinitionLoader的初始化,然后进行BeanDefinition的注册加载
  • // 6. 刷新容器,委托ConfigurableApplicationContext接口的refesh()。真实调用的是AbstractApplicationContext.refresh(),也就是上面分析的部分this.refreshContext(context),后面会调用this.afterRefresh(context, applicationArguments);

// 1. 创建容器createApplicationContext

该步骤就是实例化一个ApplicationContext,需要根据webApplicationType的值来判断初始化,而webApplicationType是在SpringApplication构造函数时根据ClassPath下内容设置的,这也是后面启动Tomcat的关键地方

  • SERVLET,Web上下文,包含web服务,对应AnnotationConfigServletWebServerApplicationContext
  • REACTIVE,反应式上下文,对应AnnotationConfigReactiveWebServerApplicationContext
  • DEFAULT,Spring上下文,对应AnnotationConfigApplicationContext

private Class<? extends ConfigurableApplicationContext> applicationContextClass;protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch(this.webApplicationType) {// 有web的默认上下文case SERVLET:contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");break;case REACTIVE:contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");break;// 没有web的默认上下文default:contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");}} catch (ClassNotFoundException var3) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);}}return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);}

三、Starter自动化配置原理

1. @SpringBootApplication

研究Starter到底是怎么启动的?分析入口需要是@SpringBootApplication注解

package org.springframework.boot.autoconfigure;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited@SpringBootConfiguration // 标注当前为配置类,相当于xml
@EnableAutoConfiguration // 自动装配全局开关
// 包扫描
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;}

2. @EnableAutoConfiguration

然后接着分析@EnableAutoConfiguration ,它是自动装配全局开关,想要使用Starter你得首先需要导入它啊,其实也就是将Starter中暴露的bean放到Spring中,这个过程是自动的,因此导入Starter之后可以直接@Autowired注入

他的原理就是 @Import(AutoConfigurationImportSelector.class) ,导入了AutoConfigurationImportSelector类,实现Starter自动化导入

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 导入了一个AutoConfigurationImportSelectorpublic @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};}

3. AutoConfigurationImportSelector类

重写selectImports选择自动装配Starter

自动配置类位置从该Starter的spring.factories中获得,可能获取到的并不一定要自动装配,如根本没有引入对应的Starter

因此依靠selectImports自动化导入内部的自动配置类

// AutoConfigurationImportSelector
@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// 1. 是否需要自动导入,也就是是否开启全局开关if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}// 2. 读取spring.factories的自动配置类,封装到 AutoConfigurationEntryAutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}

3.1 isEnabled是否开启装配Starter

// AutoConfigurationImportSelector
protected boolean isEnabled(AnnotationMetadata metadata) {if (getClass() == AutoConfigurationImportSelector.class) {return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);}return true;}

3.2 spring.factories的读取逻辑

找出spring.factories中全部的自动配置类全路径名,包含主服务模块的和引入Starter的,但是主模块中会有可能根本没有引入对应的Starter对应的自动配置类路径,哪如何处理?等会分析…。然就封装到AutoConfigurationEntry中的List<String> configurations,以便后续通过自动配置类上的@ComponentScan注册各个Starter暴露的bean

读取的时机则是在SpringBoot启动的时候完成,具体原理就是使用了Spring的BeanDefinitionRegistryPostProcessor拓展点实现了ConfigurationClassPostProcessor从而实现一系列拓展,后面再分析启动时加载spring.factories的原理逻辑。

// AutoConfigurationImportSelector
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {// 1. 不需要自动装配Starterif (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 2. 获取所有注解类AnnotationAttributes attributes = getAttributes(annotationMetadata);// 3. 需要导入的Starter的自动配置类,// 以便后面获取里面配置的@ComponentScan扫描各个Starter中暴露的beanList<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}
// 3. 需要导入的Starter的自动配置类,以便后面获取里面配置的@ComponentScan扫描各个Starter中暴露的bean
// AutoConfigurationImportSelector
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 3.1 委托 SpringFactoriesLoader 读取Starter的spring.factpriesList<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());// Starter必须指定spring.factories,否则报错提示Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}
// // 3.1 委托 SpringFactoriesLoader 读取Starter的spring.factpries
// SpringFactoriesLoaderpublic static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = factoryType.getName();// loadSpringFactories方法返回一个map// 包含spring.factories里面配置的自动各个Starter的自动配置类return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}

委托方法loadSpringFactories读取加载spring.factories


// SpringFactoriesLoader
// 读取spring.factories内容到mapprivate static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {// 读取 META-INF/spring.factoriesEnumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (Entry)var6.next();String factoryTypeName = ((String)entry.getKey()).trim();String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryImplementationName = var9[var11];result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}}

可以debug查看AutoConfigurationEntry看到

3.3 spring.factories的读取时机

那么Spring在启动的时候如何加载spring.factories呢?结合往期博客对启动run方法的分析



ConfigurationClassPostProcessor类作为Spring的拓展点,是SpringBoot一系列功能的基础入口。

// ConfigurationClassPostProcessor
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 需要注册的BeanDefinitionHolderList<BeanDefinitionHolder> configCandidates = new ArrayList(); // 需要注册的全路径名String[] candidateNames = registry.getBeanDefinitionNames();String[] var4 = candidateNames;int var5 = candidateNames.length;for(int var6 = 0; var6 < var5; ++var6) {String beanName = var4[var6];BeanDefinition beanDef = registry.getBeanDefinition(beanName);// 该配置类已经注册if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (this.logger.isDebugEnabled()) {this.logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {// 将全路径名转为BeanDefinitionHolder,保存到configCandidatesconfigCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// 需要注册的configCandidates不为空if (!configCandidates.isEmpty()) {// 排序configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});SingletonBeanRegistry sbr = null;// 单例注册器if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry)registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator)sbr.getSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator");if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// 接下来就是进入 ConfigurationClassParser  实例的parse方法ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 待注册的 BeanDefinitionHolderSet<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);HashSet alreadyParsed = new HashSet(configCandidates.size());do {// 1. 完成解析,内部完成selectImports的调用和processImports的调用parser.parse(candidates);parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);if (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());}// 2. 完成注册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();Iterator var12 = alreadyParsed.iterator();while(var12.hasNext()) {ConfigurationClass configurationClass = (ConfigurationClass)var12.next();alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}String[] var23 = newCandidateNames;int var24 = newCandidateNames.length;for(int var14 = 0; var14 < var24; ++var14) {String candidateName = var23[var14];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());if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {((CachingMetadataReaderFactory)this.metadataReaderFactory).clearCache();}}}

总结:在ApplicationContext的refresh方法中,会调用invokeBeanFactoryPostProcessors激活相应的处理器,具体的方法就是在postProcessBeanDefinitionRegistry方法内部一系列逻辑委托ConfigurationClassPostProcessor的processConfigBeanDefinitions拿到了spring.factories的自动配置类全路径名,然后封装为BeanDefinitionHolder,然后下面进行

  • ConfigurationClassParser的parse方法,解析BeanDefinitionHolder,内部调用selectImports先解析、processImports再解析,完成自动装配Starter对应的bean
  • ConfigurationClassBeanDefinitionReader的loadBeanDefinitions注册BeanDefinition

3.4 自动配置类的解析

前面分析完了ConfigurationClassPostProcessor的postConfigBeanDefinitions方法,接下来就是分析ConfigurationClassParser类的parse方法,也就是这里先后调用selectImports和proceImports方法

// ConfigurationClassParserpublic void parse(Set<BeanDefinitionHolder> configCandidates) {Iterator var2 = configCandidates.iterator();while(var2.hasNext()) {BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();// 拿到BeanDefinitionBeanDefinition bd = holder.getBeanDefinition();try {// BeanDefinition的类型,三种// 以此种为例,注解配置的自动配置类if (bd instanceof AnnotatedBeanDefinition) {this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());} else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)bd).hasBeanClass()) {this.parse(((AbstractBeanDefinition)bd).getBeanClass(), holder.getBeanName());} else {this.parse(bd.getBeanClassName(), holder.getBeanName());}} catch (BeanDefinitionStoreException var6) {throw var6;} catch (Throwable var7) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", var7);}}// 最后调用this.deferredImportSelectorHandler.process();}
// 委托
public void process() {List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;this.deferredImportSelectors = null;try {if (deferredImports != null) {ConfigurationClassParser.DeferredImportSelectorGroupingHandler handler = ConfigurationClassParser.this.new DeferredImportSelectorGroupingHandler();deferredImports.sort(ConfigurationClassParser.DEFERRED_IMPORT_COMPARATOR);deferredImports.forEach(handler::register);// 委托handler.processGroupImports();}} finally {this.deferredImportSelectors = new ArrayList();}}
// 委托
public void processGroupImports() {Iterator var1 = this.groupings.values().iterator();while(var1.hasNext()) {ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)var1.next();Predicate<String> exclusionFilter = grouping.getCandidateFilter();grouping.getImports().forEach((entry) -> {ConfigurationClass configurationClass = (ConfigurationClass)this.configurationClasses.get(entry.getMetadata());try {// 进入processImportsConfigurationClassParser.this.processImports(configurationClass, ConfigurationClassParser.this.asSourceClass(configurationClass, exclusionFilter), Collections.singleton(ConfigurationClassParser.this.asSourceClass(entry.getImportClassName(), exclusionFilter)), exclusionFilter, false);} catch (BeanDefinitionStoreException var5) {throw var5;} catch (Throwable var6) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", var6);}});}}

processImports方法处理spring.factories中的自动配置类首先就是就是需要加载自动配置类,这个时候AutoConfigurationImportSelector的selectImports方法就要发挥作用了

他返回的自动配置类需要进一步解析,因为可能对应@Import、@Bean、@ComponentScan多种类型,进一步解析递归调用processImports方法

 private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {if (!importCandidates.isEmpty()) {if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));} else {this.importStack.push(configClass);try {Iterator var6 = importCandidates.iterator();while(var6.hasNext()) {ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();Class candidateClass;// 1. 要先对 XxxImportSelector类处理,调用它的selectImports、processImports方法if (candidate.isAssignable(ImportSelector.class)) {candidateClass = candidate.loadClass();ImportSelector selector = (ImportSelector)ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);Predicate<String> selectorFilter = selector.getExclusionFilter();if (selectorFilter != null) {exclusionFilter = exclusionFilter.or(selectorFilter);}// DeferredImportSelectorif (selector instanceof DeferredImportSelector) {this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);} else {// 1.1 slelectImports方法String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);// 1.2 processImports方法this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);}}   // 2. 对XxxImportBeanDefinitionRegistrar类处理else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {candidateClass = candidate.loadClass();ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());} // 3. 一般配置类处理else {this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());// 如@Bean、@ComponentScan等类this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);}}} catch (BeanDefinitionStoreException var17) {throw var17;} catch (Throwable var18) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);} finally {this.importStack.pop();}}}}
// 自动配置类解析前置处理
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {// 这里 shouldSkip 是 @Conditional 注解实现原理的时机,下面会说明// 即不满足@Conditional注解的话,就不会再进行下面selectImports、processImports等if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}return;}this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter);do {// 真正的解析sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);} while(sourceClass != null);this.configurationClasses.put(configClass, configClass);}}

其中对于@ComponentScan的注册,有可能需要递归的完成,其中注册核心逻辑就是在processImports—>processConfigurationClass—>doProcessConfigurationClass方法

@Nullableprotected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {this.processMemberClasses(configClass, sourceClass, filter);}// 1. 处理 @PropertySources 注解Iterator var4 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();AnnotationAttributes importResource;while(var4.hasNext()) {importResource = (AnnotationAttributes)var4.next();if (this.environment instanceof ConfigurableEnvironment) {this.processPropertySource(importResource);} else {this.logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");}}// 2. 处理 @ComponentScan 注解Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {Iterator var14 = componentScans.iterator();while(var14.hasNext()) {AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();// 解析全路径Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());Iterator var8 = scannedBeanDefinitions.iterator();while(var8.hasNext()) {BeanDefinitionHolder holder = (BeanDefinitionHolder)var8.next();BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {// 递归调用this.parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);// 3. 处理 @ImportSource 注解importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");String[] var20 = resources;int var22 = resources.length;for(int var23 = 0; var23 < var22; ++var23) {String resource = var20[var23];String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// 4. @Bean的Methods处理Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);Iterator var18 = beanMethods.iterator();while(var18.hasNext()) {MethodMetadata methodMetadata = (MethodMetadata)var18.next();configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 5. 处理接口中的默认方法,在JDK9之后,接口中可以包含default修饰的包含方法体的方法this.processInterfaces(configClass, sourceClass);if (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);return sourceClass.getSuperClass();}}return null;}

最终对于@ComponentScan的扫描是委托于ClassPathBeanDefinitionScanner来实现的,他通过字节码扫描效率会比反射机制要高很多。

3.5 Conditional机制

它的作用:它是Spring提供的一个更通用的基于条件的bean的创建,通过使用@Conditional注解,会根据满足的某一个特定条件创建一个特定的bean。如一个JAR包中包含多个bean,这多个bean之间存在先后创建的依赖关系,因此可以使用@Conditional注解完成自动配置。或者是配置自动装配的开关。

它的原理:分析注解@ConditionalOnProperty 原理,首先需要判断哪里调用了这个注解,发现是OnPropertyConditional类,查看他的方法,内部只有一个public的方法是getMatchOutcome,得知是由这个方法完成核心逻辑的

// OnPropertyConditional// 将是否匹配的条件疯转到 ConditionOutcome 中,内部包含成员变量match 为true、false
@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {// 扫描出 @ConditionalOnProperty 注解的信息List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));// 不匹配的配置类,不需要自动装配List<ConditionMessage> noMatch = new ArrayList<>();// 匹配的配置类,需要自动装配List<ConditionMessage> match = new ArrayList<>();for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {// 核心的验证逻辑ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());// 加入对应的结果(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());}if (!noMatch.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));}return ConditionOutcome.match(ConditionMessage.of(match));}

// 核心的验证逻辑 determineOutcome

private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {Spec spec = new Spec(annotationAttributes);// 处理不匹配的两种情况// 1. missProperties对应属性缺失List<String> missingProperties = new ArrayList<>();// 2. nonMatchingProperties对应不匹配List<String> nonMatchingProperties = new ArrayList<>();// 这两个结果的填充,内部使用PropertyResolver来验证spec.collectProperties(resolver, missingProperties, nonMatchingProperties);if (!missingProperties.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).didNotFind("property", "properties").items(Style.QUOTE, missingProperties));}if (!nonMatchingProperties.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).found("different value in property", "different value in properties").items(Style.QUOTE, nonMatchingProperties));}return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));}
1. getMatchOutcome调用切入点

结合前面processImports方法调用的流程,在processConfigurationClass步骤中主要逻辑就是对即将解析的注解做预处理

// 自动配置类解析前置处理
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {// 这里 shouldSkip 是 @Conditional 注解实现原理的时机// 即不满足@Conditional注解的话,就不会再进行下面selectImports、processImports等if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}return;}this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter);do {// 真正的解析sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);} while(sourceClass != null);this.configurationClasses.put(configClass, configClass);}}

分析shouldSkip方法,这个方法关键地方

  1. condition的获取,代码走到这一步已经是对一个特定的自动配置类解析,可以拿到这个配置类上面的注解等信息
  2. condition的运行匹配,如@ConditionalOnProperty注解就是调用OnPropertyCondition类的getMatchOutcome方法
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {if (metadata != null && metadata.isAnnotated(Conditional.class.getName())) {if (phase == null) {return metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata)metadata) ? this.shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION) : this.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);} else {List<Condition> conditions = new ArrayList();// 1. 获取所有的conditionIterator var4 = this.getConditionClasses(metadata).iterator();while(var4.hasNext()) {String[] conditionClasses = (String[])var4.next();String[] var6 = conditionClasses;int var7 = conditionClasses.length;for(int var8 = 0; var8 < var7; ++var8) {String conditionClass = var6[var8];Condition condition = this.getCondition(conditionClass, this.context.getClassLoader());conditions.add(condition);}}AnnotationAwareOrderComparator.sort(conditions);var4 = conditions.iterator();Condition condition;ConfigurationPhase requiredPhase;do {do {if (!var4.hasNext()) {return false;}condition = (Condition)var4.next();requiredPhase = null;if (condition instanceof ConfigurationCondition) {requiredPhase = ((ConfigurationCondition)condition).getConfigurationPhase();}} while(requiredPhase != null && requiredPhase != phase);// 2. condition匹配} while(condition.matches(this.context, metadata));return true;}} else {return false;}}
2. PropertyResolver属性自动化解析

SpringBoot会读取properties文件的原理就是通过PropertyResolver来实现的,如我们使用@Value来读取properties文件中的内容也是此原理

我们以@Value注解来分析,看看那个类调用了Value.class,发现是QualifierAnnotationAutowireCandidateResolver类

debug查看解析完properties中的值后,是怎么填充到属性中的?发现在BeanFactory接口中有resloveDependcy方法,该方法主要是依赖查找,对于@Value类型的依赖则是需要读取properties文件,参考:https://www.cnblogs.com/binarylei/p/12337145.html

发现获取Value表达式属性之后,会进入DefaultListableBeanFactory类的resloveEmbeddedValue方法

// AbstractBeanFactoy
@Nullablepublic String resolveEmbeddedValue(@Nullable String value) {if (value == null) {return null;} else {String result = value;Iterator var3 = this.embeddedValueResolvers.iterator();do {if (!var3.hasNext()) {return result;}StringValueResolver resolver = (StringValueResolver)var3.next();// 交给了 resolveStringValueresult = resolver.resolveStringValue(result);} while(result != null);return null;}}

该方法内部委托resolveStringValue实现properties文件的读取解析,他的作用时机切入点是在PropertySourcePlaceholderConfigurer类,它实现了BeanFactoryPostProcess

而StringValueResolver初始化就是在PropertySourcePlaceholderConfigurer的postProcessBeanFactory方法中,分析这个方法

  1. 初始化MutablePropertySources
  2. 初始化PropertySourcesPropertyResolver
  3. StringValueResolver初始化
  4. StringValueResolver注册

《Spring源码深度解析 郝佳 第2版》SpringBoot体系分析、Starter的原理相关推荐

  1. 《Spring源码深度解析 郝佳 第2版》AOP

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  2. 《Spring源码深度解析 郝佳 第2版》ApplicationContext

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度 ...

  3. 《Spring源码深度解析 郝佳 第2版》事务

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  4. 《Spring源码深度解析 郝佳 第2版》JDBC、MyBatis原理

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  5. 《Spring源码深度解析 郝佳 第2版》XML标签的解析

    目录 往期博客<Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 分析了xml文件的加载,接下来就是标签的解析,入口函数有两个 默认标签的解析 自定义标签的解析 一 ...

  6. 《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 往期博客完成了xml文件加载 ...

  7. 《Spring源码深度解析 郝佳 第2版》容器的基本实现与XML文件的加载

    目录 Spring的整体架构 容器的基本实现与XML文件的加载 一.Spring的整体架构 Spring是一个分层架构,主要包含以下部分 Core Container Data Access Web ...

  8. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  9. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

最新文章

  1. Vim改装编辑器的安装与使用简介
  2. 转)微软Olap服务MDX函数应用举例
  3. OAuth2.0在项目中认证流程介绍
  4. Tecplot如何提取某点数据并导出
  5. Spring Boot热部署
  6. iframe页面改动parent页面的隐藏input部件value值,不能触发change事件。
  7. java自带数据库_Derby-jdk自带数据库的使用 - Java天堂
  8. c语言strlren函数的原代码,VB常用函数
  9. Javascript各种运算符第五课(小一节)
  10. 计算机与科学 研究生考试内容,计算机科学与技术考研考哪些科目 备考技巧有哪些...
  11. Hadoop原理——HDFS原理
  12. 北理乐学c语言答案猴子,【北理乐学】机智的大师
  13. HTML实现个人简历
  14. 无线WIFI I/O控制器技术分享
  15. 第6章 Python 数字图像处理(DIP) - 彩色图像处理1 - RGB彩色模型,RGB to Gray,CMK和CMYK彩色模型,HSI彩色模型
  16. (二)史玉柱:我的营销心得有感------主要关注网络游戏策划
  17. POJ - 3264
  18. 《逆袭进大厂》第十五弹之智力情景题 | 个人博客
  19. win7系统端口映射
  20. [句子成分] 二、谓语 表语

热门文章

  1. 1.谷粒商城1-5文字版
  2. IOC和DI入门案例
  3. 纵横布局,传统硬盘厂商的突围
  4. excel查找在哪里_excel大神教程:5个职场人士最常用的公式,马住不谢
  5. Feathers组件--之--列表
  6. docker私库harbor安装配置
  7. orical 经验总结
  8. 中控 c语言,「分享」C语言如何编写图形界面
  9. 修改linux终端命令行颜色+PS1设置
  10. 安全应用与恶意软件在您的手机一较高下