SpringBoot 版本 : 2.2.1.RELEASE
入口类: SpringApplication;SpringApplicationBuilder
说明 : 由于SpringBoot建立在Spring之上,所以分析SpringBoot的启动过程其实与Spring是交错进行的,分析的时候会顺带将一些Spring的扩展点也提到
注:本文主要讲解一些比较重要的关键步骤,不能面面俱到,若有疑问,随时保持沟通
SpringBoot启动 源码深度解析(二)
SpringBoot启动 源码深度解析(三)
SpringBoot启动 源码深度解析(四)
1. 启动SpringBoot应用
- 方式一 :
@SpringBootApplication(scanBasePackages="com.union.autoseller")
public class SpringCloudDockerWebApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SpringCloudDockerWebApplication.class)
.main(SpringVersion.class)
.run(args);
}
}
- 方式二 :
@SpringBootApplication
public class SpringCloudDockerWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudDockerWebApplication .class, args);
}
}
- 以上两种方式,分别涉及到 SpringApplicationBuilder 类与 SpringApplication类,SpringApplicationBuilder可以构建出 SpringApplication,执行SpringApplicationBuilder 的run方法的时候本质还是调用的 SpringApplication的run方法,另外通过builder的方式可以指定父子上下文。
image.png
2. SpringApplication.run(String... args) 函数
image.png- 看到 getRunListeners(args)行,此行代码的作用是从 /META-INF/spring.factories 文件中反射获取对应的 SpringApplicationRunListener实例:
image.png
此处没有什么难度,读者只需要仔细留心一点就可发现。而 SpringApplicationRunListeners类会缓存一个 SpringApplicationRunListener集合,以便发布相应的事件通知(starting、environmentPrepared、contextPrepared等等),此处用户也可以自定义实现监听器。 - 紧接着发布 starting 通知事件( 后续还有很多事件)
- 然后看 prepareEnvironment(listeners, applicationArguments) 这行代码。点进去发现如下逻辑:
image.png ,首先判断当前是否存在环境,若存在直接返回,否则会根据 this.webApplicationType 字段判断到底应该创建哪种环境,该字段是在哪里赋值的呢? 原因是我们 执行 SpringApplication.run(SpringCloudDemoApplication.class, args)的时候会去 new SpringApplication()对象,而 SpringApplication的构造方法中,会去调用this.webApplicationType = WebApplicationType.deduceFromClasspath() 行代码初始化 webApplicationType的值。
image.png image.png
此处还有一个重要的初始化动作 setInitializers跟setListeners方法,跟前面说过的监听器的解析步骤是一样的,这些扩展都是在 /META-INF/spring.factories 文件中配置的。最终返回 WebApplicationType.SERVLET,那么此时会创建 StandardServletEnvironment对象,而我们都知道属性配置离不开环境,那么属性配置在哪里存放呢?跟踪到 抽象类 AbstractEnvironment
image.png ,发现是在创建 StandardServletEnvironment对象的无参构造时初始化抽象类的成员变量,此处拿出来单独说的原因是:有时读者阅读源码的时候可能会感觉这个配置到底存哪了呢?没看到有地方可以存放啊? 我们只要稍加留心即可,至于为什么这么做就不展开了。 - 紧接着调用 configureEnvironment(...)配置环境信息与激活的Profiles,ConfigurationPropertySources.attach(environment)中会在属性资源最后添加一个ConfigurationPropertySourcesPropertySource类型的名字为configurationProperties 的 PropertySource对象。
- 打印Logo:此处logo可以自定义,只需编写 banner.txt 在resources下即可。
-
创建上下文:也是根据 webApplicationType字段决定创建哪种上下文对象,由于是SERVLET,所以此处会实例化 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext,发现无参构造下面又藏着两个初始化动作:
image.png - AnnotatedBeanDefinitionReader :会创建ConditionEvaluator对象(用于判断@Conditional注解),然后调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)方法去注册实例:
// beanFactory中添加比较器实例
AnnotationAwareOrderComparator
// beanFactory中添加AutowireCandidateResolver得实例化对象,用来处理@Lazy注解 和 @qualifier注解
ContextAnnotationAutowireCandidateResolver
注册所有注解配置处理器bean定义:
// registry中注册bean定义, 用来处理配置类
ConfigurationClassPostProcessor
// registry中注册bean定义,用来处理@Autowired
AutowiredAnnotationBeanPostProcessor
// registry中注册bean定义,但是前提是否启用jsr250支持():javax.annotation.Resource。用来处理@PostConstruct注解与@PreDestroy注解
CommonAnnotationBeanPostProcessor
// registry中注册bean定义, 检查是否提供JPA support,即有没有javax.persistence.EntityManagerFactory类,有的话并且需要引入spring-orm框架,然后才会注册当前处理器。
PersistenceAnnotationBeanPostProcessor
// registry中注册bean定义,当执行上下文得preInstantiateSingletons()方法之后,会触发SmartInitializingSingleton#afterSingletonsInstantiated()得回调,用来处理带有@EventListener注解得类,然后通过默认实现DefaultEventListenerFactory创建ApplicationListener实例
EventListenerMethodProcessor
// registry中注册bean定义,EventListenerFactory得默认实现,,生成ApplicationListener实例
DefaultEventListenerFactory
- ClassPathBeanDefinitionScanner : 构造器会执行registerDefaultFilters()方法,默认会添加@Component 注解过滤,但是也会隐式得注册包含@Component 元注解得注解。例如:@Repository、 @Service、@Controller、@Repository、javax.annotation.ManagedBean、javax.inject.Named 或者一些自定义注解。
- 创建完上下文之后,紧接着调用prepareContext(...) 填充上下文属性,将environment添加到上下文中、上下文后置处理postProcessApplicationContext()、依次调用所有得ApplicationContextInitializer实例(构造方法中解析得initializers,上下文初始化之后,最先可以访问上下文的是initializers,所以我们可以在Dubbo看到类似的扩展)。
-
执行listeners.contextPrepared(context)通知上下文准备完成,如果有Banner就先注册banner,注册
springApplicationArguments 单例 bean
;注册springBootBanner 单例bean
-
然后执行load(context)方法加载上下文,方法里面会创建BeanDefinitionLoader对象,同时构造器中会创建
AnnotatedBeanDefinitionReader
对象,然后执行BeanDefinitionLoader
得load()方法。load()方法会根据传入得参数类型分为class、Resource、Package、CharSequence等类型,分别执行不同得load方法,由于SpringBoot启动时得参数基本都是传入启动类得class,所以这里会找到class类型去执行load()方法。调用AnnotationUtils.findAnnotation(type, Component.class) != null
判断给定得参数资源有没有被@Component注解修饰,有的话会调用org.springframework.context.annotation.AnnotatedBeanDefinitionReader
#register方法去注册当前参数class对应得Bean。register() -> registerBean() -> doRegisterBean()
, 在doRegisterBean()方法中执行具体得注册处理。
image.png
image.png
image.png
image.png
- 首先创建注解通用beanDefinetion类AnnotatedGenericBeanDefinition,类主包含一个成员:
元数据对象类
AnnotationMetadata,
构造器中同时会初始化这个元数据注解类,实例对象为:StandardAnnotationMetadata
,这个类会保存参数class对应得所有注解。 -
然后通过调用ConditionEvaluator类得方法shouldSkip方法
判断类是否标注了
@Conditional条件注解,没有得话直接false。表示不跳过,执行注册,有的话需要判断注解类得元数据是否包含@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean
等注解。此时需要将配置阶段ConfigurationPhase设置为PARSE_CONFIGURATION
,否则将配置阶段ConfigurationPhase设置为REGISTER_BEAN
,递归当前shouldSkip
方法。 - 设置完属性之后,由于ConfigurationPhase参数不为空,则下一次递归得时候将会跳过if(phase == null)判断逻辑,直接执行后面得逻辑。获取条件类Condition得实例,循环实例化,并添加到到临时集合中。然后对填充完得集合做一次排序。遍历集合判断类型是否是
ConfigurationCondition
类型,若是,则获取ConfigurationCondition实例对应配置得ConfigurationPhase,目前只有OnBeanCondition配置得是REGISTER_BEAN
,若跟当前设置得相同并且不匹配matches方法,则应该跳过注册,否则可以执行注册流程。 -
org.springframework.boot.autoconfigure.condition.SpringBootCondition
#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)
,进到方法内,会调用org.springframework.boot.autoconfigure.condition.SpringBootCondition
#getMatchOutcome()抽象方法,具体得条件解析去实现,执行匹配判断逻辑。 -
解析类得@Scope注解,调用
org.springframework.context.annotation.AnnotationScopeMetadataResolver
#resolveScopeMetadata方法 - 最后创建一个BeanDefinitionHolder对象用来保存beanDefinetion,接着调用definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry),若当前注解
beanDefinetion
@Scope属性为ScopedProxyMode.NO,表示不需要代理,则直接返回;否则判断代理模式是否为ScopedProxyMode.TARGET_CLASS(CGLIB代理
),调用org.springframework.context.annotation.ScopedProxyCreator
#createScopedProxy 再调用org.springframework.aop.scope.ScopedProxyUtils
#createScopedProxy实际得创建代理类得方法。
总结:@Conditional -> @Scope -> @common annotation :@Lazy/@DependsOn/@Role/@Description -> qualifier 属性验证 -> 自定义得BeanDefinitionCustomizer处理 -> 根据
元数据
对应得@Scope注解中具体得scopedProxyMode值判断是否需要启动代理模式 以及具体得代理模式
-> 注册bean定义
- 执行listeners.contextLoaded(context)通知上下文加载完成
-
紧接着调用refresh方法,重点来了:该方法会判断是否是AbstractApplicationContext上下文得子类型,
是的话会调用 org.springframework.context.support.AbstractApplicationContext
#refresh()方法。此处会进入到抽象父类上下文 AbstractApplicationContext中。
文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
网友评论