启动过程
1、springboot的入口程序
1 |
|
当程序开始执行之后,会调用SpringApplication的构造方法,进行某些初始参数的设置
1 | //创建一个新的实例,这个应用程序的上下文将要从指定的来源加载Bean |
在上述构造方法中,有一个判断应用类型的方法,用来判断当前应用程序的类型:
1 | static WebApplicationType deduceFromClasspath() { |
springboot启动的运行方法,可以看到主要是各种运行环境的准备工作
1 | public ConfigurableApplicationContext run(String... args) { |
下面详细介绍各个启动的环节:
1、创建并启动计时监控类,可以看到记录当前任务的名称,默认是空字符串,然后记录当前springboot应用启动的开始时间。
1 | StopWatch stopWatch = new StopWatch(); |
2、初始化应用上下文和异常报告集合
1 | ConfigurableApplicationContext context = null; |
3、设置系统属性java.awt.headless的值:
1 | /* |
4、创建所有spring运行监听器并发布应用启动事件
1 | SpringApplicationRunListeners listeners = getRunListeners(args); |
5、初始化默认应用参数类
1 | ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); |
6、根据运行监听器和应用参数来准备spring环境
1 | ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); |
7、创建banner的打印类
1 | Banner printedBanner = printBanner(environment); |
8、创建应用的上下文:根据不同哦那个的应用类型初始化不同的上下文应用类
1 | context = createApplicationContext(); |
9、准备异常报告器
1 | exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, |
10、准备应用上下文
1 | prepareContext(context, environment, listeners, applicationArguments, printedBanner); |
11、刷新应用上下文
1 | refreshContext(context); |
12、应用上下文刷新后置处理
1 | afterRefresh(context, applicationArguments); |
13、停止计时监控类:计时监听器停止,并统计一些任务执行信息
1 | stopWatch.stop(); |
14、输出日志记录执行主类名、时间信息
1 | if (this.logStartupInfo) { |
15、发布应用上下文启动完成事件:触发所有SpringapplicationRunListener监听器的started事件方法
1 | listeners.started(context); |
16、执行所有Runner执行器:执行所有applicationRunner和CommandLineRunner两种运行器
1 | callRunners(context, applicationArguments); |
17、发布应用上下文就绪事件:触发所有springapplicationRunnListener将挺起的running事件方法
1 | listeners.running(context); |
18、返回应用上下文
1 | return context; |
注意:
整个springboot框架中获取factorys文件的方式统一如下:
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { |
spring.factory文件中的类的作用:
1 | # PropertySource Loaders 属性文件加载器 |
自动装配
在之前的课程中我们讲解了springboot的启动过程,其实在面试过程中问的最多的可能是自动装配的原理,而自动装配是在启动过程中完成,只不过在刚开始的时候我们选择性的跳过了,下面详细讲解自动装配的过程。
1、在springboot的启动过程中,有一个步骤是创建上下文,如果不记得可以看下面的代码:
1 | public ConfigurableApplicationContext run(String... args) { |
2、在prepareContext方法中查找load方法,一层一层向内点击,找到最终的load方法
1 | //prepareContext方法 |
3、实际执行load的是BeanDefinitionLoader中的load方法,如下:
1 | //实际记载bean的方法 |
4、下面方法将用来判断是否资源的类型,是使用groovy加载还是使用注解的方式
1 | private int load(Class<?> source) { |
5、下面方法判断启动类中是否包含@Component注解,但是会神奇的发现我们的启动类中并没有该注解,继续更进发现MergedAnnotations类传入了一个参数SearchStrategy.TYPE_HIERARCHY,会查找继承关系中是否包含这个注解,@SpringBootApplication–>@SpringBootConfiguration–>@Configuration–>@Component,当找到@Component注解之后,会把该对象注册到AnnotatedBeanDefinitionReader对象中
1 | private boolean isComponent(Class<?> type) { |
当看完上述代码之后,只是完成了启动对象的注入,自动装配还没有开始,下面开始进入到自动装配。
6、自动装配入口,从刷新容器开始
1 |
|
7、在invokeBeanFactoryPostProcessors方法中完成bean的实例化和执行
1 | /** |
8、查看invokeBeanFactoryPostProcessors的具体执行方法
1 | public static void invokeBeanFactoryPostProcessors( |
9、开始执行自动配置逻辑(启动类指定的配置,非默认配置),可以通过debug的方式一层层向里进行查找,会发现最终会在ConfigurationClassParser类中,此类是所有配置类的解析类,所有的解析逻辑在parser.parse(candidates)中
1 | public void parse(Set<BeanDefinitionHolder> configCandidates) { |
10、继续跟进doProcessConfigurationClass方法,此方式是支持注解配置的核心逻辑
1 | /** |
11、查看获取配置类的逻辑
1 | processImports(configClass, sourceClass, getImports(sourceClass), true); |
12、继续回到ConfigurationClassParser中的parse方法中的最后一行,继续跟进该方法:
1 | this.deferredImportSelectorHandler.process() |
运行原理
1、启动器
1 | <dependency> |
springboot-boot-starter:就是springboot的场景启动器。springboot将所有的功能场景都抽取出来,做成一个个的starter,只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来,根据公司业务需求决定导入什么启动器即可。
2、主程序
1 | package com.oi; |
查看@SpringBootApplication
1 | /* |
1 | /* |
1 | /* |
1 | /* |
1 | /* |
1 | /* |
springboot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置的工作:spring.factories文件位于springboot-autoconfigure.jar包中。
所以真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中对应org.springframework.boot.autoconfigure.包下的配置项通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总称为一个实例并加载到IOC容器中。
自动配置
springboot配置文件的装配过程
1、springboot在启动的时候会加载主配置类,开启了@EnableAutoConfiguration。
2、@EnableAutoConfiguration的作用:
- 利用AutoConfigurationImportSelector给容器导入一些组件。
- 查看selectImports方法的内容,返回一个AutoConfigurationEntry
1 | AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, |
- 可以看到SpringFactoriesLoader.loadFactoryNames,继续看又调用了loadSpringFactories方法,获取META-INF/spring.factories资源文件
1 | public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { |
总结:将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;每一个xxxAutoConfiguration类都是容器中的一个组件,最后都加入到容器中,用来做自动配置,每一个自动配置类都可以进行自动配置功能
使用HttpEncodingAutoConfiguration来解释自动装配原理
1 | /* |
根据当前不同的条件判断,决定这个配置类是否生效!
总结:
1、springboot启动会加载大量的自动配置类
2、查看需要的功能有没有在springboot默认写好的自动配置类中华
3、查看这个自动配置类到底配置了哪些组件
4、给容器中自动配置类添加组件的时候,会从properties类中获取属性
@Conditional:自动配置类在一定条件下才能生效
@Conditional扩展注解 | 作用 |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |