zoukankan      html  css  js  c++  java
  • SpringBoot自动配置原理

    注:版本为2.5.7

    首先看代码:

    @SpringBootApplication
    public class MyWebApplicationTest {
    	private static final Log logger = LogFactory.getLog(MyWebApplicationTest.class);
    	public static void main(String[] args) {
    		logger.info("ttttttt");
    		SpringApplication.run(MyWebApplicationTest.class, args);
    	}
    }
    

    0、简介

    众所周知,Spring为Java生态做出的贡献是相当大的,但实际应用Spring的时候会发现有大部分配置文件需要去配置,后序出现了注解式配置以后,倒是可以避免大部分配置文件,但仍然会存在一些繁杂的xml配置,且部分配置为大多数项目都需要配置的内容,多个项目之间存在多个重复配置,那么SpringBoot则是用来解决此问题,你可以认为SpringBoot默认给了很多配置类,比如我们引入Redis的start,那么SpringBoot就会自动加载其start中的配置类以达到接入redis的效果,比如我们引入mybatis的start,那么SpringBoot就会自动加载其start中的配置类以达到接入mybatis的效果。那么我们就来看看SpringBoot自动配置是如何实现的。

    注意:当我们看Spring源码的时候,我们可以理解为Spring-context为基础核心模块功能实现,SpringBoot则是在其基础之上实现自动配置的功能(如果你学习源码比较跳跃,可能无法感受到其中的巧妙)。

    1、@SpringBootApplication

    在我们SpringBoot中,启动类上此注解是必加的。

    1.1、@SpringBootApplication

    我们来看看这个注解里面有什么东西:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration 
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    

    其是一个组合注解,上面的四个注解就不提,下面的三个注解中,@ComponentScan都很熟悉就不提,@SpringBootConfiguration和@EnableAutoConfiguration我们深入查看

    1.2、@SpringBootConfiguration

    SpringBootConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration	// 所以,SpringBootConfiguration这个注解仅仅是标识为配置类,无其他任何卵用
    @Indexed
    

    @SpringBootConfiguration注解当中重要的为@Configuration,那么这就代表着我们的启动类为一个配置类。

    1.3、@ComponentScan

    这个没什么好说的,众所周知,就是一个扫描包,既然这里加入了扫描包的注解且为设置basePackage属性,那么就会去当前类所在的包。

    代码验证,在Spring解析配置类到达@ComponentScan注解的时候:

    此就是解析扫描包注解的地方,使用框中this.compentScanParser.parse方法解析注解信息并返回扫描获取到的bd数组,注意这是Set,所以去重了的。这个方法的位置:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

    然后进入这个parse方法:

    可以看到,不管你是用classes或者packages属性,都是被转成了数组,且如果数组没东西的话,那么就说明没配置包或者class属性,注意此时declaringClass为启动类Class,那么就拿到当前启动类的包名放进去,反正这个basePackages数组不可能为空的。

    至此我们知道了spring会扫描当前启动类包下的内容。

    1.4、@EnableAutoConfiguration

    EnableAutoConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage	
    @Import(AutoConfigurationImportSelector.class)	
    

    首先是一个AutoConfigurationPackage,然后是一个@Import类,其Import的内容等会来说。

    1.4.1、@AutoConfigurationPackage

    AutoConfigurationPackage

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    

    注意这里Import了一个AutoConfigurationPackages.Registrar的类,我们进入这个类:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    		@Override
    		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    		}
    		@Override
    		public Set<Object> determineImports(AnnotationMetadata metadata) {
    			return Collections.singleton(new PackageImports(metadata));
    		}
    	}
    

    其为AutoConfigurationPackages的静态内部类Registrar,这里重要的是registerBeanDefinitions方法,首先我们要知道,如果@Import里面的类是实现了ImportBeanDefinitionRegistrar接口的类,那么则会去调用这个类的registerBeanDefinitions方法,并且会将当前类上注解的信息和bd的注册器传入,这里如果不明白,看这篇博客:https://www.cnblogs.com/daihang2366/p/15172622.html。这里简单说一下:

    @Import的类分为三种:

    Import中的类 操作
    如果实现了ImportSelector接口 调用回调方法:selectImports
    如果实现了ImportBeanDefinitionRegistrar接口 调用回调方法:registerBeanDefinitions
    未实现以上接口 其他操作,例如配置类等

    那么我们这里的Registrar类,则会被回调进registerBeanDefinitions方法,重要的是我们要知道这个方法里面干了什么事情,先debug一下:

    注意看此处拿到了当前启动类所在的包名地址,然后调用register方法,我们再进入register方法中:

    这个里面BEAN就是AutoConfigurationPackages的全包名,意思就是容器里面如果不存在这个BeanDefinition就创建一个,然后将当前启动类的包名存进去,如果存在,则添加进去,此操作你可以理解为留存一个启动类包名的操作,此处还没有去进行其他的操作,仅仅是保存一下而已。

    1.4.2、@Import(AutoConfigurationImportSelector.class)

    在EnableAutoConfiguration注解当中,第二个注解则是Import一个类。

    AutoConfigurationImportSelector类是非常重要的类,其最重要的功能为加入自动配置的配置类,先看这个类的图:

    可以看到,这个类实现了一众Aware接口,这些接口中,会在这个类初始化的时候通过实现的回调接口返回其各种关键属性,其可参考这篇随笔:https://www.cnblogs.com/daihang2366/p/14992052.html

    然后实现了DefferedImportSelector接口,其上层接口为ImportSelector接口,ImportSelector不再赘述,上面已经说了去看另外一篇博客,当前还实现了Order接口,注意这个Order这个接口,在Spring当中,例如实例化,配置加载,aop等,都可以使用@Order注解来进行排序,Spring在操作的时候,如果是多个操作的情况下,基本上都会根据@Order或者Order接口进行排序,当前推荐你使用@Order,一个注解就完事的谁愿意再去实现个接口呢。

    我们再来看DefferedImportSelector接口:

    public interface DeferredImportSelector extends ImportSelector {
    	@Nullable
    	default Class<? extends Group> getImportGroup() {
    		return null;
    	}
    	interface Group {
    		void process(AnnotationMetadata metadata, DeferredImportSelector selector);
    		Iterable<Entry> selectImports();
    		class Entry {
    			.....注释了
    		}
    	}
    }
    

    注意这个接口里面有子接口,那么说明我们实现类里面也会有此类。注意这里的process方法非常重要,这里面做的是将自动配置的类加入到容器当中。

    回到AutoConfigurationImportSelector这个类,再看大概的源码,这里不重要的我都注释掉

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    	private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
    	private static final String[] NO_IMPORTS = {};
    	private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    	private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    	private ConfigurableListableBeanFactory beanFactory;
    	private Environment environment;
    	private ClassLoader beanClassLoader;
    	private ResourceLoader resourceLoader;
    	private ConfigurationClassFilter configurationClassFilter;
    		........各种属性的setter方法
    		private static class ConfigurationClassFilter {
    		........
    	}
    	private static class AutoConfigurationGroup
    			implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
    		private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
    		private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
    		private ClassLoader beanClassLoader;
    		private BeanFactory beanFactory;
    		private ResourceLoader resourceLoader;
    		private AutoConfigurationMetadata autoConfigurationMetadata;
    		.....各种属性的setter方法
    		@Override
    		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 = ((AutoConfigurationImportSelector) deferredImportSelector)
    					.getAutoConfigurationEntry(annotationMetadata);
    			this.autoConfigurationEntries.add(autoConfigurationEntry);
    			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    				this.entries.putIfAbsent(importClassName, annotationMetadata);
    			}
    		}
    		@Override
    		public Iterable<Entry> selectImports() {
    			if (this.autoConfigurationEntries.isEmpty()) {
    				return Collections.emptyList();
    			}
    			Set<String> allExclusions = this.autoConfigurationEntries.stream()
    					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
    			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
    					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
    					.collect(Collectors.toCollection(LinkedHashSet::new));
    			processedConfigurations.removeAll(allExclusions);
    
    			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
    					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
    					.collect(Collectors.toList());
    		}
    		.......
    	}
    	protected static class AutoConfigurationEntry {
    		.......
    	}
    }
    

    现在我们知道了此类的process方法为加入自动配置类的方法,那么现在有两个问题,

    第1:process方法中具体是怎么拿到需要的配置类的。

    第2:这个process方法是如何被调用的。

    1.4.2.1、process方法如何被调用的

    org.springframework.context.support.AbstractApplicationContext#refresh

    众所周知,refresh方法是Spring启动的入口。我们这里直接看invokeBeanFactoryPostProcessors方法。

    org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

    这没什么好说的,进入这一行的方法

    org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)

    public static void invokeBeanFactoryPostProcessors(
    			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    		.......
        	invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
    		.......
    	}
    

    跟此次无关的代码已经注释,进入这一行代码,

    org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors

    会进入当前ConfigurationClassPostProcessor的回调,这个类做的事情就是配置解析,注意这个类如果不理解,看这一篇博客:https://www.cnblogs.com/daihang2366/p/15049423.html

    如果不理解这种回调,看这一篇博客:https://www.cnblogs.com/daihang2366/p/15172622.html的第四章

    我们现在进入其的postProcessBeanDefinitionRegistry方法:

    org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

    这里就是安全性校验,不用看,进入processConfigBeanDefinitions方法。

    org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    		................这注释代码做的事情就是拿到当前容器中所有的bean,如果是配置类,就放置到configCandidates集合当中
    		do {
    			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
                parser.parse(candidates);
                ..................
    	}
    

    这里面上半部分注释的代码做的事情就是spring拿到当前容器中所有的bean,判断其是否为配置类,如果是配置类,则加入到configCandidates集合当中。然后调用parse方法去解析配置类,注意此时的candidates的size为1,这里面存在的元素就是我们的启动类,注意其@SpringBootConfiguration注解里面的配置类注解。

    进入parse方法。

    org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)

    可以看到集合中的唯一一个元素就是我们的启动类,且bd类型为注解bd,然后我们进入parse方法。

    我们这里就不继续进入parse方法,如果想深入了解,参考这篇:https://www.cnblogs.com/daihang2366/p/15049423.html

    这里大概说一下,parse方法里面执行如下逻辑:

    1、解析Component、PropertySources、PropertySource、ComponentScans、ComponentScan、处理@Import、ImportResource等

    然后我们看这个方法的最下面的一行代码:

    我们进入此方法

    org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process

    可以看到此时的deferredImports里面就存在着我们的AutoConfigurationImportSelector这个类,那么问题来了,是什么时候将我们的AutoConfigurationImportSelector放到这个集合当中来的呢?这个存在于上面解析bd注解中处理Import的时候的代码,详情可以自行翻阅或者看我上面列举的博客,这里简单描述:

    如果当前import的类类型为DefferedImportSelector的话,则会去调用handle方法,而这里的deferredImportSelectorHandler的值为:DeferredImportSelectorHandler,而它的handle方法为:

    看这里,如果deferredImportSelectors不为空,则直接add进去,当前这里不可能为空,因为本身就new了,至于这里为什么判空我也不是很清楚,知道的朋友可以留言一下,非常感谢。

    然后我们重新回到process方法当中来。

    这里我们到达779行的时候,则是遍历deferredImports集合然后当作参数传递给handler对象的register方法当中,那么我们来看看handler的register方法里面写了些什么:

    这里说白了就干两件事情,将配置类放置到configurationClasses集合当中,然后我们前面说过DefferedImportSelector接口内有一个内部接口,那么我们实现了这个接口的类肯定也要有这个内部接口的实现,此时groupings里面放置的就是我们AutoConfigurationImportSelector的内部接口实现的一个包装对象,如下:

    它只是使用了group这个属性来存放DefferedImportSelector接口中内部接口的实现类。

    然后register方法结束后,就会去调用processGroupImports方法。

    这里groupings只有一个元素,且这个元素里面包含了我们AutoConfigurationImportSelector中实现的内部接口实例。

    当前遍历元素的类为:org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGrouping,然后进入其getImports()方法,然后将返回结果进行配置解析。然后进入该方法中查看源码:

    进入group的process方法,

    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process

    这里是调用getAutoConfigurationEntry方法去获取需要注入配置的类,然后放置到this.autoConfigurationEntries中去。

    然后我们先结束这个方法,来看前面this.selectImport方法:return this.group.selectImports();

    这代码里面做的最主要的就是以下事情:

    1、取出this.autoConfigurationEntries中配置类列表。

    2、然后拿到我们配置排除的配置类列表。

    3、将所有的配置类列表中去除排除的配置类列表。

    4、对现有的配置类列表进行排序,这个排序就是根据我们前面说的@Order或者Order接口。

    那么这里当getImports()方法执行完成以后,对其返回值进行for循环的进行配置解析,如下:

    到这里,我们的@SpringBootApplication注解中所有内容已经解释完成,且其执行流程也完全解析。

    附:

    在上面的代码中org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process方法中的getAutoConfigurationEntry方法中可以拿到当前所有的需要加载的配置类,这里我们深入看看其实现方法:

    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    		if (!isEnabled(annotationMetadata)) {
    			return EMPTY_ENTRY;
    		}
    		// 拿到注解属性数据,例如需要排除的类、排除的类名
    		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    		// 拿到需要加载的配置类全包名
    		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    		// 去除重复的
    		configurations = removeDuplicates(configurations);
    		// 获取到需要排除的类
    		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    		// 校验要排除的类如果不存在,则抛异常
    		// 校验如果排除的类在自动配置类中不存在,则说明没必要排除,也会抛出异常
    		checkExcludedClasses(configurations, exclusions);
    		// 去除需要排除的类
    		configurations.removeAll(exclusions);
    		// 过滤掉不需要的配置类
    		configurations = getConfigurationClassFilter().filter(configurations);
    		// 拿到所有实现了AutoConfigurationImportListener接口的配置类.并执行其接口回调,如果实现了各种Aware接口,也进行回调
    		fireAutoConfigurationImportEvents(configurations, exclusions);
    		// 封装进AutoConfigurationEntry对象当中.
    		return new AutoConfigurationEntry(configurations, exclusions);
    	}
    

    这里每一行的作用都注释清楚了,但是其有几个重要的方法需要详细解释,例如getCandidateConfigurations、filter、fireAutoConfigurationImportEvents方法。

    List configurations = getCandidateConfigurations(annotationMetadata, attributes);

       protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
       	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
       			getBeanClassLoader());
       	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;
       }
    

    这里面很明显是通过SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());这行代码去获取到配置类的。

    getSpringFactoriesLoaderFactoryClass()这行代码中的内容为:

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    	return EnableAutoConfiguration.class;
    }
    

    然后进入其方法当中:

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
       ClassLoader classLoaderToUse = classLoader;
       if (classLoaderToUse == null) {
          classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
       }
       String factoryTypeName = factoryType.getName();
       return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    

    这里factoryTypeName为EnableAutoConfiguration注解的全包名:org.springframework.boot.autoconfigure.EnableAutoConfiguration。

    注意return的那一行代码,就是拿当前classpath下所有的spring.factories中以factoryTypeName为key的值,而这些值就是需要加载的配置类的全包名。

    注意这些配置类并不是全部都加载,是需要filter过滤的。

    我们再来看看当前工程中的spring.factories

    随便找一个spring.factories

    那么有没有这么一种可能,我们如果引入mybatis-starter后,当前的classpath下就存在它的spring.factories文件,然后写我们自己的配置类,那么就不就是自定义实现了starter吗?对的没错,就是这么干的。那么我们还会有问题,如果编写配置类,这个等会会给案例。我们可以来看看myabtisplus-starter中是否有这个文件:

    看到这里你应该是明白了,这里我就不过多的阐述。

    configurations = getConfigurationClassFilter().filter(configurations);

    前面我们搞清楚了是如何获取到自动配置类,以及简单说了一下自定义starter的奥秘,那么我们前面获取到那么多的自动配置类(大部分都来自于spring-boot-actuator-autoconfigure这个工程),肯定不会全部都使用,我们这里就来解析其filter过滤方法。

    filter过滤方法中涉及到@Conditional注解以及根据Conditional扩展出来的组合注解。

    首先我们要知道,所有的配置类不可能全部都是必须的,需要进行筛选,既然筛选肯定是筛选规则需要的,那么在我们的认知中条件注入Bean的注解为Conditional注解,其参数中放置一个类,类中编写注入规则根据其返回值来决定当前bean是注入,那么自动配置类也是一样的,只不过在自动配置类当中,@Conditional是自动配置的条件,这个倒不是很重要,毕竟只是一个注解,具体的作用还得看spring在不同的地方进行不同的应用,在不同地方应用的时候那么这个注解就拥有不同的作用。

    这里先举例一个rabbitMQ自动配置类的代码,介绍其组合出新的Conditional条件注解,然后再返回代码中的filter方法进行解析。

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RabbitTemplate.class)
    @ConditionalOnBean(RabbitTemplate.class)
    @ConditionalOnEnabledHealthIndicator("rabbit")
    @AutoConfigureAfter(RabbitAutoConfiguration.class)
    public class RabbitHealthContributorAutoConfiguration
    		extends CompositeHealthContributorConfiguration<RabbitHealthIndicator, RabbitTemplate> {
    	@Bean
    	@ConditionalOnMissingBean(name = { "rabbitHealthIndicator", "rabbitHealthContributor" })
    	public HealthContributor rabbitHealthContributor(Map<String, RabbitTemplate> rabbitTemplates) {
    		return createContributor(rabbitTemplates);
    	}
    }
    

    这里对于各种组合Conditional注解以下有一个参考表(该图来拉勾教育课程资料pdf):

    我们随便进入一个注解中,例如常见的@ConditionalOnClass:

    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnClassCondition.class)
    public @interface ConditionalOnClass {
    	Class<?>[] value() default {};
    	String[] name() default {};
    }
    

    可以看到,其是组合了@Conditional注解进行的操作。

    然后使用OnClassCondition类来决定是否成立,具体逻辑课自行翻阅,这里不再说这个,不同的组合注解里面放置了类似OnClassCondition的类,然后根据注解的属性值来执行相应的校验逻辑。

    我们回到上面说的filter方法当中,我们不进入方法实际的代码中也可以猜出来,就是拿到配置类的Condition注解,然后调用其设置的校验类的回调方法,根据其返回值判断是否成立,当然肯定会传入相应关键的参数,例如注解信息等,这个返回值不一定是boolean,也可能是其他包装的类,当然,意思就是一样的,理解就行,没必要在这里太过浪费时间。

    fireAutoConfigurationImportEvents(configurations, exclusions);

    这个类主要的作用就是执行配置的AutoConfigurationImportListener接口回调。

    拿到所有实现了AutoConfigurationImportListener接口的配置类.并执行其接口回调,如果实现了各种Aware接口,也进行回调

    先进入这个方法当中:

    private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
        List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
        if (!listeners.isEmpty()) {
            AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
            for (AutoConfigurationImportListener listener : listeners) {
                invokeAwareMethods(listener);
                listener.onAutoConfigurationImportEvent(event);
            }
        }
    }
    

    这里getAutoConfigurationImportListeners方法是拿到所有spring.factories文件中实现了AutoConfigurationImportListener接口的类,然后去调用这些类的回调方法(说的抽象一点就是通知这些类配置类已经获取完毕),就像Spring中或者其他框架中的回调一样,说的不抽象就是去调用回调,抽象点就是xxx通知等,只是这些回调被赋予了某些含义,所以衍生出抽象的概念(这个不理解没事,因为我表达能力很拉跨)。

    代码中将前面所有筛选出来经过排除、filter过滤后的自动配置类列表封装进AutoConfigurationImportEvent,然后再循环的拿到所有AutoConfigurationImportListener实现类,调用其回调方法onAutoConfigurationImportEvent,并传递进去所有的自动配置类封装对象,在这之前,还拥有一行代码invokeAwareMethods(listener);这个方法代码如下:

    private void invokeAwareMethods(Object instance) {
        if (instance instanceof Aware) {
            if (instance instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
            }
            if (instance instanceof BeanFactoryAware) {
                ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
            }
            if (instance instanceof EnvironmentAware) {
                ((EnvironmentAware) instance).setEnvironment(this.environment);
            }
            if (instance instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
            }
        }
    }
    

    代码很简单,就是如果当前的实现类实现了各种Aware回调,则也会对其进行回调处理,将一些关键属性给我们的实现类,那么如果你在源码里面把invokeAwareMethods和listener.onAutoConfigurationImportEvent互换行位置,项目指定跑步起来。

    这里还有一个问题,getAutoConfigurationImportListeners这一行代码是如何获取到所有的AutoConfigurationImportListener回调的,换句话来说就是在spring.factories文件中定义什么key才可以被这行代码获取到(这涉及到我们如何进行扩展开发,总不能光靠百度谷歌),我们现在进入getAutoConfigurationImportListeners这行代码里面的内容:

    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
    }
    

    进入这个方法,没什么好说的:

    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        // 关键代码在这。
        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }
        List<T> result = new ArrayList<>(factoryImplementationNames.size());
        for (String factoryImplementationName : factoryImplementationNames) {
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }
        // 排序
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
    

    这里看最后进行了排序,这证实了前面我说的大部分地方都会进行排序操作,可自行进入源码查看(使用List的sort方法,然后自定义了排序器,排序器名称AnnotationAwareOrderComparator)。

    我们是来看loadFactoryNames(factoryType, classLoaderToUse)这行代码,注意此时factoryType为AutoConfigurationImportListener.class,方法返回类名字符串数组。

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    

    这个方法你如果仔细看这一片随笔的话,肯定会熟悉的,这里就不再进行赘述。我们可以搜索一下这个配置:

    还不少。

    至此,把SpringBoot里面各个注解的含义、源码、入口都进行了解释,下面我再单独拿一个自动配置类进行解释,加强一下印象。

    1.5、自动配置类举例

    举例看一个MyabtisPlus的自动配置类:MybatisPlusAutoConfiguration.class,代码参考:https://github.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.java

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
    @ConditionalOnSingleCandidate(DataSource.class)
    @EnableConfigurationProperties(MybatisPlusProperties.class)
    @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
    public class MybatisPlusAutoConfiguration implements InitializingBean {
         
        ...........一些属性的定义
             
         public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,
                                            ObjectProvider<Interceptor[]> interceptorsProvider,
                                            ObjectProvider<TypeHandler[]> typeHandlersProvider,
                                            ObjectProvider<LanguageDriver[]> languageDriversProvider,
                                            ResourceLoader resourceLoader,
                                            ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                            ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
                                            ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider,
                                            ApplicationContext applicationContext) {
            this.properties = properties;
            this.interceptors = interceptorsProvider.getIfAvailable();
            this.typeHandlers = typeHandlersProvider.getIfAvailable();
            this.languageDrivers = languageDriversProvider.getIfAvailable();
            this.resourceLoader = resourceLoader;
            this.databaseIdProvider = databaseIdProvider.getIfAvailable();
            this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
            this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable();
            this.applicationContext = applicationContext;
        }
    	@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
        @Bean
        @ConditionalOnMissingBean
        public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
            return plus自己定义创建的的sqlSessionFactory
        }
    }
    

    这个自动配置类在spring.factories文件中有定义:文件参考https://github.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-boot-starter/src/main/resources/META-INF/spring.factories

    # Auto Configure
    org.springframework.boot.env.EnvironmentPostProcessor=\
      com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\
      com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
      com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
    

    这里我们大概能看出来,核心操作就是替换SqlSessionFactory为plus自己搞出来的,在此是我们可以看到类上有这些注解:

    @Configuration(proxyBeanMethods = false),此注解代表当前类是配置类,proxyBeanMethods =false,则是使其不代理方法,不理解的话参考这篇随笔:https://www.cnblogs.com/daihang2366/p/15125874.html

    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}):意思则是当前classpath中存在这两个类的时候,本配置类才起效。

    @ConditionalOnSingleCandidate(DataSource.class):意思是当前容器里面只有一个bean,如果有多个的情况下必须指定一个bean为@Primary。

    @EnableConfigurationProperties(MybatisPlusProperties.class):意思是启用MybatisPlusProperties类的自动注入配置的功能,我们进入这个类:

    @ConfigurationProperties(
        prefix = "mybatis-plus"
    )
    public class MybatisPlusProperties {
        private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        private String configLocation;
        private String[] mapperLocations;
        private String typeAliasesPackage;
        private Class<?> typeAliasesSuperType;
        private String typeHandlersPackage;
        private String typeEnumsPackage;
        private boolean checkConfigLocation = false;
        private ExecutorType executorType;
        private Properties configurationProperties;
        @NestedConfigurationProperty
        private MybatisConfiguration configuration;
        @NestedConfigurationProperty
        private GlobalConfig globalConfig = GlobalConfigUtils.defaults();
    

    这个时候,比如当我们在application.yml或properties中书写属性,mybatis-plus.configLocation=xxxx,那么在MybatisPlusAutoConfiguration自动配置类的构造函数中拿到的MybatisPlusProperties对象里面的configLocation属性就能拿到我们给的值xxx了,那么这个就达到了一个配置自动注入的效果,那么现在又有一个问题MybatisPlusProperties里面的NestedConfigurationProperty注解是个啥玩意,其实它就是给MybatisConfiguration进行配置自动注入,比如现在MybatisConfiguration里面有一个属性为environment,那么这个时候我摩恩配置文件里面写mybatis-plus.configuration.environment=xxxx,那么就能把这个值注入到MybatisPlusProperties的configuration的environment属性当中去(不知道到这里的时候,你是否对Spring的整体设计和完善程度感到奇妙)。

    @AutoConfigureAfter:见名知意,当当前配置类被解析完成以后,就该去解析这里给的配置类了。

    方法上的注解:

    @Bean:如果你学过Spring,那这个你肯定会知道的,我就不过多的赘述。

    @ConditionalOnMissingBean:如果当前容器中没有指定的类,则调用此方法去注入Bean,如果没有指定,那么默认指定的类为当前返回值类型。

    到此我们大概了解了baomidou大佬的自动配置类,其关键在于,配置注入、自动配置类的配置、各种条件注入的注解、当前最重要的还是实现注入的逻辑(这个有兴趣自行了解,我去学习大佬的代码后后序再写随笔出来分享给大家)。

    2、run方法

    前面我们把@SpringBoot注解的操作看完了,但是我们还不知道SpringBoot启动入口方法里面干了些啥事,相当于是楼层有了,但地基还是懵的,所以我们这里来详细说一下run方法。

    先进入其run方法看源码:

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }
    

    注意,args是命令行参数,primarySource为启动类的class,估计有些小伙伴还不知道,在main方法中,args这数组是我们java -jar的时候传入的命令行参数,比如我们一个springboot应用,java -jar xxx.jar --server.port=9301,那么在启动类的吗main方法中args数组里面就有一个元素,值为--server.port=9301。

    继续进入run方法:

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
    

    可以看到,其new了一个SpringApplication,然后再去调用run方法。所以我们这里得先来看SpringApplication的构造方法。

    2.1、SpringApplication的构造方法:

    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
    

    调用的是这个构造器,直接进入对应的构造方法,注意这里没有父类,所以不存在super。

    org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 设置资源加载器,当前是空的null
        this.resourceLoader = resourceLoader;
        // 资源类不能为空,也就是我们的Application
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 把我们的Application放到primarySources数组当中
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 判断当前应用的类型,大多数都是Servlet
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 拿到当前spring.factories中配置的BootstrapRegistryInitializer,初始化器回调
        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        // 拿到当前spring.factories中配置的ApplicationContextInitializer,初始化器回调
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 拿到当前spring.factories中配置的ApplicationListener,初始化器回调
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 根据调用栈,拿到当前执行main方法的类名
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    代码中this.webApplicationType = WebApplicationType.deduceFromClasspath();这一行是用来判断当前应用类型,不同的应用类型后面是需要创建不同的ApplicationContext,进入这个方法看看:

    org.springframework.boot.WebApplicationType#deduceFromClasspath

    此种静态变量的值如下:

    image-20211230171100559

    ClassUtils.isPresent方法就是判断当前Class是否存在,存在则为True,否则为false,这个方法的代码很简单:

    public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
        try {
            forName(className, classLoader);
            return true;
        }
        catch (IllegalAccessError err) {
            throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
                                            className + "]: " + err.getMessage(), err);
        }
        catch (Throwable ex) {
            return false;
        }
    }
    

    这里继续看deduceFromClasspath方法,这里的逻辑如下:

    1、如果当前classpath中存在org.springframework.web.reactive.DispatcherHandler并且不存在org.springframework.web.servlet.DispatcherServlet并且不存在org.glassfish.jersey.servlet.ServletContainer的话,则说明当前应用类型为REACTIVE,这个是一种WebFlux,不同于webmvc,其是一种完全异步非阻塞的web框架。

    2、如果当前classpath中存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext的话,则说明是SERVLET,也就是我们最熟悉的webmvc。

    3、如果以上都不成立,则说明就是一个普通的应用,不是任何Web环境的。

    那么很肯定,当前就是Webmvc的环境,也就是SERVLET。

    我们现在继续看下一行getBootstrapRegistryInitializersFromSpringFactories

    org.springframework.boot.SpringApplication#getBootstrapRegistryInitializersFromSpringFactories

    private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
        ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
        getSpringFactoriesInstances(Bootstrapper.class).stream()
            .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
            .forEach(initializers::add);
        initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        return initializers;
    }
    

    首先是从spring.factories中拿到所有Bootstrapper实现类,然后强转为BootstrapRegistryInitializer后调用其initialize方法。

    然后从spring.factories中拿到所有BootstrapRegistryInitializer实现类,然后存至initializers。

    BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener都是Spring给用户扩展出来的一些初始化器回调。

    org.springframework.boot.SpringApplication#deduceMainApplicationClass

    这一行代码很简单,就是根据调用栈,如果发现方法名为main,则代表这个方法所在的类为Application启动类。

    到这里构造方法看完了,大部分都是干的一些初始化的内容,也没什么很特别的东西,唯一特别点的就是拿到spring.factories文件中的各种初始化器实现类

    2.2、SpringApplication.run方法

    org.springframework.boot.SpringApplication#run(java.lang.String...)

    public ConfigurableApplicationContext run(String... args) {
        // 创建计时器
        StopWatch stopWatch = new StopWatch();
        // 计时器->开始
        stopWatch.start();
        // 创建启动器上下文,并且执行BootstrapRegistryInitializer接口监听器回调
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        // 创建上下文对象,在try里面进行初始化
        ConfigurableApplicationContext context = null;
        // 设置环境,不重要
        configureHeadlessProperty();
        // 获取SpringApplicationRunListener监听器,从spring.factorires文件中,当前的listeners只有一个,为:EventPublishingRunListener
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 去执行SpringApplicationRunListener监听器的starting方法
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 将args命令行参数封装到ApplicationArguments对象里面去
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 初始化应用上下文环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            // 处理忽略Bean的信息
            configureIgnoreBeanInfo(environment);
            // 打印Banner信息
            Banner printedBanner = printBanner(environment);
            // 根据当前应用类型来创建context上下文
            context = createApplicationContext();
            // 可以理解为一个记录器,记录上下文的执行数据等,类似日志记录的那种
            context.setApplicationStartup(this.applicationStartup);
            // refresh刷新上下文以前,做一些准备
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 刷新上下文,其实就是ApplicationContext的refresh
            refreshContext(context);
            // 刷新后扩展方法,里面啥都没有
            afterRefresh(context, applicationArguments);
            // 计时器->结束
            stopWatch.stop();
            // log
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 监听器回调
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            // 监听器回调
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
    

    总结下来几个步骤:

    ​ 【1】、创建计时器并开始

    ​ 【2】、创建启动器上下文并执行监听器BootstrapRegistryInitializer的initialize方法

    ​ 【3】、创建上下文ConfigurableApplicationContext变量并设置环境一些属性

    ​ 【4】、获取SpringApplicationRunListener监听器并执行其starting方法

    ​ 【5】、将命令行参数封装到ApplicationArguments里面来

    ​ 【6】、初始化应用上下文环境

    ​ 【7】、处理忽略Bean的信息

    ​ 【8】、打印Banner信息

    ​ 【9】、根据当前应用类型来创建context上下文

    ​ 【10】、设置记录器

    ​ 【11】、为刷新上下前做准备

    ​ 【12】、刷新上下文,最重要的功能在这的

    ​ 【13】、刷新后的扩展方法,其实里面什么都没有

    ​ 【14】、计时器结束

    ​ 【15】、记录log

    ​ 【16】、SpringApplicationRunListener监听器回调started

    ​ 【17】、SpringApplicationRunListener监听器回调running

    2.2.1、第【1】步

    创建计时器并开始

    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    

    这个没有什么好说的,就是一个计时器,只不过这个计时器是Spring自己包装的。

    2.2.2、第【2】步

    创建启动器上下文并执行监听器BootstrapRegistryInitializer的initialize方法

    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    

    进入这个方法:

    org.springframework.boot.SpringApplication#createBootstrapContext

    private DefaultBootstrapContext createBootstrapContext() {
        DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
        this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
        return bootstrapContext;
    }
    

    注意其会拿到所有的BootstrapRegistryInitializer监听器然后进行遍历,并调用其initialize回调方法,此时传入创建好的启动器上下文。

    注意这个this.bootstrapRegistryInitializers是在SpringApplication的构造器里面进行加入的:

    2.2.3、第【3】步

    创建上下文ConfigurableApplicationContext变量并设置环境一些属性

    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    

    这两行代码,不痛不痒,不看也罢

    2.2.4、第【4】步

    获取SpringApplicationRunListener监听器并执行其starting方法

    // 获取SpringApplicationRunListener监听器,从spring.factorires文件中,当前的listeners只有一个,为:EventPublishingRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    

    getRunListeners就是获取spring.factories中获取SpringApplicationRunListener的类,然后封装到SpringApplicationRunListeners对象并返回赋值给变量listeners,注意此时获取到的SpringApplicationRunListener存在于SpringApplicationRunListeners中一个List集合中,这个集合也叫做listeners。

    然后listeners.starting则是拿到这个SpringApplicationRunListeners中List集合中的所有监听器并逐一去调用执行。

    这里为了防止不理解,贴一下getRunListeners方法的代码:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger,
                                                 getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
                                                 this.applicationStartup);
    }
    

    2.2.5、第【5】步

    将命令行参数封装到ApplicationArguments里面来

    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    

    不用细究,知道其把args封装起来就行了。

    2.2.6、第【6】步

    初始化应用上下文环境

    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    

    进入这个方法:

    org.springframework.boot.SpringApplication#prepareEnvironment

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                     "Environment prefix cannot be set via properties.");
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = convertEnvironment(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
    
    

    getOrCreateEnvironment这行代码是根据不同的环境去创建不同的环境配置对象:

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
            case SERVLET:
                return new ApplicationServletEnvironment();
            case REACTIVE:
                return new ApplicationReactiveWebEnvironment();
            default:
                return new ApplicationEnvironment();
        }
    }
    

    那么这个时候,大部分情况为ApplicationServletEnvironment。

    configureEnvironment这行代码则是将我们传入的命令行参数封装成SimpleCommandLinePropertySource对象并且放置到environment对象的propertySources的propertySourceList集合当中,其如何封装进入的可以进入configureEnvironment方法中的configurePropertySources方法。比如这个时候:

    ConfigurationPropertySources.attach(environment)这行代码是往environment里面再加入一些配置内容。

    listeners.environmentPrepared(bootstrapContext, environment)这行代码是去执行SpringApplicationRunListener回调的environmentPrepared方法通知其环境准备好了,我们进入这个方法:

    void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
       doWithListeners("spring.boot.application.environment-prepared",
             (listener) -> listener.environmentPrepared(bootstrapContext, environment));
    }
    

    你可能有疑问,这玩意直接拿出所有监听器遍历就行了,还整个兰姆达还整个方法,这不是脱了裤子放屁吗?

    不是的,进入doWithListeners方法后可以看到:

    private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
    			Consumer<StartupStep> stepAction) {
        StartupStep step = this.applicationStartup.start(stepName);
        this.listeners.forEach(listenerAction);
        if (stepAction != null) {
            stepAction.accept(step);
        }
        step.end();
    }
    

    因为Spring是要所有回调environmentPrepared方法时所花费的时间,看见没,这就叫专业。

    2.2.7、第【7】步

    处理忽略Bean的信息

    configureIgnoreBeanInfo(environment);
    

    这玩意没啥好说的,略过。

    2.2.8、第【8】步

    打印Banner信息

    Banner printedBanner = printBanner(environment);
    

    默认打印的内容为:

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
    

    如果我们在resources中新建一个banner.txt,那么就会打印我们文件中的内容。

    2.2.9、第【9】步

    根据当前应用类型来创建context上下文

    context = createApplicationContext();
    

    进入这个方法createApplicationContext中:

    protected ConfigurableApplicationContext createApplicationContext() {
        return this.applicationContextFactory.create(this.webApplicationType);
    }
    

    注意这里的applicationContextFactory的值为:

    private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
    

    那么我们进入ApplicationContextFactory.DEFAULT方法当中。

    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            switch (webApplicationType) {
                case SERVLET:
                    return new AnnotationConfigServletWebServerApplicationContext();
                case REACTIVE:
                    return new AnnotationConfigReactiveWebServerApplicationContext();
                default:
                    return new AnnotationConfigApplicationContext();
            }
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                                            + "you may need a custom ApplicationContextFactory", ex);
        }
    };
    

    那么这个时候ConfigurableApplicationContext的值就是AnnotationConfigServletWebServerApplicationContext了。

    2.2.10、第【10】步

    设置记录器

    context.setApplicationStartup(this.applicationStartup);
    

    这个不重要,设置一个记录器而已。

    2.2.11、第【11】步

    为刷新上下前做准备

    直接贴出该方法里面的代码:

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
    			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    			ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        bootstrapContext.close(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
    

    context.setEnvironment(environment);

    这一行代码就是给ApplicationContext设置环境属性对象,contex的值为AnnotationConfigServletWebServerApplicationContext。

    postProcessApplicationContext(context);

    这一行代码就是个context设置一些属性,比如注册bean名称生成器、设置ResourceLoader、设置ClassLoader等。

    applyInitializers(context);

    执行ApplicationContextInitializer监听器的initialize方法,注意是排过序的。

    listeners.contextPrepared(context);

    执行SpringApplicationRunListener监听器的contextPrepared方法,通知其刷新前准备好了。

    bootstrapContext.close(context);

    设置启动器上下文关闭事件对象,BootstrapContextClosedEvent,具体什么用不需要去细究,这是框架内部的事情

    if (this.logStartupInfo) {
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
    }:

    操作一些日志的打印。

    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

    拿到当前ApplicationContext的bean工厂,然后将我们main方法中传入的args封装成的对象注册到工厂当中,注意是单例的。

    if (printedBanner != null) {
    beanFactory.registerSingleton("springBootBanner", printedBanner);
    }:

    将Banner注册进容器中。

    if (beanFactory instanceof DefaultListableBeanFactory) {
    ((DefaultListableBeanFactory) beanFactory)
    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
    context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }

    设置一些bean生成策略和懒加载策略,这里一般只需要默认即可,如果需要详细了解,可以查看我的其他随笔看到bean的生成策略,懒加载PostProcessor可以进入其源码详细查看。

    后面就一行代码比较重要:

    listeners.contextLoaded(context);

    调用SpringApplicationRunListener监听器的contextLoaded方法。

    2.2.12、第【12】步

    刷新上下文,最重要的功能在这的

    进入refreshContext(context);方法中

    private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }
        refresh(context);
    }
    

    进入这个方法:refresh(context);

    protected void refresh(ConfigurableApplicationContext applicationContext) {
       applicationContext.refresh();
    }
    

    注意此时的applicationContext为AnnotationConfigServletWebServerApplicationContext,那么我们进入这个context的refresh方法。

    但注意AnnotationConfigServletWebServerApplicationContext本身没有refresh方法,其父类ServletWebServerApplicationContext才拥有refresh方法。

    看这个方法的代码:

    public final void refresh() throws BeansException, IllegalStateException {
        try {
            super.refresh();
        }
        catch (RuntimeException ex) {
            WebServer webServer = this.webServer;
            if (webServer != null) {
                webServer.stop();
            }
            throw ex;
        }
    }
    

    那么注意看,此处refresh是final的,说明只要是ServletWebServerApplicationContext的子类,它调用refresh方法就全部到达ServletWebServerApplicationContext的refresh方法。

    这里我们进入super.refresh方法:

    org.springframework.context.support.AbstractApplicationContext#refresh:

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                invokeBeanFactoryPostProcessors(beanFactory);
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                                "cancelling refresh attempt: " + ex);
                }
                destroyBeans();
                cancelRefresh(ex);
                throw ex;
            }
            finally {
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }
    

    这里就是很经典的Spring的refresh方法了,这一个方法走完后,Spring刷新上下文就结束了。整体流程如下:

    img

    这里是Spring本身的一些内容。到这以后,你可能有感觉了,SpringBoot就是在Spring之上做了自动配置的操作,核心还是spring自己的内容。

    2.2.13、第【13】步

    刷新后的扩展方法,其实里面什么都没有

    protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
    }
    

    这个就像refresh中的postProcessBeanFactory(beanFactory);一样,预留着的,虽然现在没代码块,可能以后的版本中就有其他操作了。

    2.2.14、第【14】步

    计时器结束

    stopWatch.stop();
    

    这个没什么需要解释的,就是将计时器结束。

    2.2.15、第【15】步

    记录log

    这个没什么需要解释的,就是记录log

    2.2.16、第【16】步

    SpringApplicationRunListener监听器回调started

    执行SpringApplicationRunListener的started回调方法,通知其启动完成

    2.2,17、第【17】步

    SpringApplicationRunListener监听器回调running

    执行SpringApplicationRunListener的running回调方法,通知其处在运行当中

    总结

    1、SpringBoot本身还是使用的Spring的内容。

    2、在自定义的Start中加入spring.factories文件,放入指定的key就例如自动配置类,那么SpringBoot启动时就会夹在我们的配置类,已完成自动配置的效果。

    3、spring.factories中还可以配置各种监听器的。

    4、AutoConfigurationImportSelector的process方法进入的流程是本身spring提供的扩展方法,其process方法当中就是加载自动配置类到容器中的。

    下一篇:自定义Start实践、内嵌Tomcat解析、SpringMVC自动配置、Spring数据源自动配置解析

  • 相关阅读:
    神经网络(2)---neurons and the brain
    P2P system:How Chord tackles failures
    如何成为更好的自己
    P2P system: Chord
    P2P system: FastTrack and BitTorrent
    P2P system: GNUTELLA
    P2P system: Napster
    P2P system: Introduction
    幸福公开课(2)
    MTV与MVC 多对多表的创建方式 前后端传输数据编码格式 ajax 批量插入数据 自定义分页器
  • 原文地址:https://www.cnblogs.com/daihang2366/p/15763111.html
Copyright © 2011-2022 走看看