美文网首页程序员spring bootspring boot源码解析
SpringBoot启动 源码深度解析(一)

SpringBoot启动 源码深度解析(一)

作者: 凡毓不凡 | 来源:发表于2020-05-09 01:06 被阅读0次

    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@Repositoryjavax.annotation.ManagedBeanjavax.inject.Named 或者一些自定义注解
    • 创建完上下文之后,紧接着调用prepareContext(...) 填充上下文属性,将environment添加到上下文中上下文后置处理postProcessApplicationContext()依次调用所有得ApplicationContextInitializer实例(构造方法中解析得initializers,上下文初始化之后,最先可以访问上下文的是initializers,所以我们可以在Dubbo看到类似的扩展)
    • 执行listeners.contextPrepared(context)通知上下文准备完成,如果有Banner就先注册banner,注册springApplicationArguments 单例 bean;注册springBootBanner 单例bean
    • 然后执行load(context)方法加载上下文,方法里面会创建BeanDefinitionLoader对象,同时构造器中会创建AnnotatedBeanDefinitionReader对象,然后执行BeanDefinitionLoaderload()方法。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
    1. 首先创建注解通用beanDefinetion类AnnotatedGenericBeanDefinition,类主包含一个成员:元数据对象类AnnotationMetadata
      构造器中同时会初始化这个元数据注解类,实例对象为:StandardAnnotationMetadata,这个类会保存参数class对应得所有注解
    2. 然后通过调用ConditionEvaluator类得方法shouldSkip方法判断类是否标注了@Conditional条件注解,没有得话直接false。表示不跳过,执行注册,有的话需要判断注解类得元数据是否包含@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean等注解。此时需要将配置阶段ConfigurationPhase设置为PARSE_CONFIGURATION,否则将配置阶段ConfigurationPhase设置为REGISTER_BEAN,递归当前shouldSkip方法。
    3. 设置完属性之后,由于ConfigurationPhase参数不为空,则下一次递归得时候将会跳过if(phase == null)判断逻辑,直接执行后面得逻辑。获取条件类Condition得实例,循环实例化,并添加到到临时集合中。然后对填充完得集合做一次排序。遍历集合判断类型是否是ConfigurationCondition类型,若是,则获取ConfigurationCondition实例对应配置得ConfigurationPhase,目前只有OnBeanCondition配置得是REGISTER_BEAN,若跟当前设置得相同并且不匹配matches方法,则应该跳过注册,否则可以执行注册流程。
    4. org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata),进到方法内,会调用org.springframework.boot.autoconfigure.condition.SpringBootCondition#getMatchOutcome()抽象方法,具体得条件解析去实现,执行匹配判断逻辑。
    5. 解析类得@Scope注解,调用org.springframework.context.annotation.AnnotationScopeMetadataResolver#resolveScopeMetadata方法
    6. 最后创建一个BeanDefinitionHolder对象用来保存beanDefinetion,接着调用definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry),若当前注解beanDefinetion@Scope属性为ScopedProxyMode.NO,表示不需要代理,则直接返回;否则判断代理模式是否为ScopedProxyMode.TARGET_CLASSCGLIB代理),调用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中。

    文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!

    相关文章

      网友评论

        本文标题:SpringBoot启动 源码深度解析(一)

        本文链接:https://www.haomeiwen.com/subject/dnntnhtx.html