从0-1了解Spring是如何运行起来的(三):Context预处理,为加载容器做准备

前言

最深刻了解一个框架的思想的方式,莫过于看源码,本系列旨在于从Springboot底层源码(Version - 2.6.6)出发,一步步了解springboot是如何运行起来的。

从0-1了解SpringBoot如何运行(一):Environment环境装配

从0-1了解SpringBoot是如何运行起来的(二):定制你的banner

在前述的文章中,我们主要了解了SpringBoot是如何实现环境装配和banner打印的,这一期我们主要来了解SpringBoot是如何创建Context,并对context进行相应的预处理。

 public ConfigurableApplicationContext run(String... args) {
     ......
     context = createApplicationContext();
     context.setApplicationStartup(this.applicationStartup);
     prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
     ......
 }
复制代码

createApplicationContext

 protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
 }
复制代码

createApplicationContext这行代码,主要就是根据当前的当前SpringBoot的webApplicationType进行判断,采用简单工厂的设计模式来生成相应的context对象。

 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);
    }
 };
复制代码

从更详细的代码中可以看出,Context一共有三类,分别对应SERVLET、REACTIVE、DEFAULT三种不同的网络类型。

 context.setApplicationStartup(this.applicationStartup);
复制代码

紧接着一行源码比较简单,就是往对应的Context对象中设置相应的ApplicationStarterUp。

prepareContext

prepareContext方法内的对应源码较多,是本章主要的重点环节。对这个我们逐行进行分析。

 private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
       ApplicationArguments applicationArguments, Banner printedBanner) {
     // 将前置加载的配置信息存放到context中。
    context.setEnvironment(environment);
     // 1、紧接着对Context进行后处理
    postProcessApplicationContext(context);
     // 2、从当前Spring环境中找到对应的context的初始化器,并进行初始化处理。
    applyInitializers(context);
     // 3、前置有聊过这个,会发送特定的context事件,从而让相应的监听器处理事件信息,达到解耦。
    listeners.contextPrepared(context);
     // 4、发送领域事件,关闭相应的bootstrapContex。
    bootstrapContext.close(context);
     // 5、打印配置信息
    if (this.logStartupInfo) {
       logStartupInfo(context.getParent() == null);
       logStartupProfileInfo(context);
    }
     //获取对应的bean工厂,并往其中注册应用的启动参数
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
     // 如果这个时候的输出banner不为空,那么页注册进去。
    if (printedBanner != null) {
       beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
     //设置context支持循环引用以及 bean定义的重载
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
       ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
       if (beanFactory instanceof DefaultListableBeanFactory) {
          ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
       }
    }
     //如果需要懒加载,那么此时还需要设置相应的后处理器。
    if (this.lazyInitialization) {
       context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    //同时,获取所有的来源文件信息 这里主要指的是启动类。
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
     //根据相应的sources去进行加载
    load(context, sources.toArray(new Object[0]));
     //加载完成后发送相应的事件消息。
    listeners.contextLoaded(context);
 }
复制代码

postProcessApplicationContext

上述源代码中的第一个重要的点在于postProcessApplicationContext(context),具体源代码如下:

 protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.beanNameGenerator != null) {
        //1、如果当前容器名字的生成器不为空,则往容器工厂中注册容器名字生成器。
       context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
             this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        //2、如果配置的加载器不为空,那么此时需要将配置加载器、类加载器都保存到容器工厂中。
       if (context instanceof GenericApplicationContext) {
          ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
       }
       if (context instanceof DefaultResourceLoader) {
          ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
       }
    }
     //3、如果进行了占位符值的转换,那么此时将相应的转换服务也保存到容器中。
    if (this.addConversionService) {
       context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
    }
 }
复制代码

见名知意,这个方法是对Application就行相应的后处理。主要的步骤流程有以下几步:

  1. 如果当前容器名字生成器不为空,则往容器工厂中注册容器名字生成器
  2. 如果配置的加载器不为空,那么此时需要将配置加载器、类加载器都保存到容器工厂中。
  3. 如果进行了占位符值的转换,那么此时将转换服务也保存到容器中。

紧接着,这个时候执行applyInitializers(context);方法,该方法的源码如下:

 protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
       Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
             ApplicationContextInitializer.class);
       Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
       initializer.initialize(context);
    }
 }
复制代码

该方法的主要原理如下:

1、前置先将Context的初始化器都存储到环境中。

2、然后利用GenericTypeResolver.resolveTypeArgument方法,判断每个初始化器的初始的context类型。从而筛选出当前用户使用的context模型。

3、第三步,使用筛选出来的context进行初始化。

思考回顾:

很巧的是,我公司的一些业务中也是都采用了Context来包装上下文的请求参数。那么针对于不同业态的context初始化,也可以考虑采用这种方法来增加对于代码的复用性。

contextPrepared

在Context初始化后,会执行listeners.contextPrepared(context);代码,这个前置的文章中已经聊过很多次了。就是发送相应的SpringBoot自身定义的领域事件,从而初始化相应内容信息。相似的事件处理还有以下几个。

image-20220522192750768.png

紧接着,会去发送领域事件,用于关闭当前的BootstrapContext。也就是bootstrapContext.close(context);这行。我看的时候十分疑惑,这个bootStrapContext是干啥的呢?这里我截取了bootstrapContext中的源码的注释信息。其中有条比较关键的信息:

 A simple bootstrap context that is available during startup and Environment post-processing up to the point that the ApplicationContext is prepared.
复制代码

翻译出来大概是这个意思:一个简单的引导上下文,在启动和环境后处理期间可用,直到准备好ApplicationContext为止。(换句话,bootStrapContext就是个临时产品,等到context初始化完成后就会被删除。)。在删除了bootstrapContext后,会紧接着执行如下代码:

 if (this.logStartupInfo) {
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
 }
复制代码

主要的内容逻辑如下:

1、判断当前是否需要日志输出启动信息。

2、如果不需要,直接结束;否则需要判断当前context是否属于root,如果属于则需要记录相应的启动日志信息。

3、紧接着会去加载相应的启动配置信息,源代码如下:

 protected void logStartupProfileInfo(ConfigurableApplicationContext context) {
     Log log = getApplicationLog();
     if (log.isInfoEnabled()) {
         List<String> activeProfiles = quoteProfiles(context.getEnvironment().getActiveProfiles());
         if (ObjectUtils.isEmpty(activeProfiles)) {
             List<String> defaultProfiles = quoteProfiles(context.getEnvironment().getDefaultProfiles());
             String message = String.format("%s default %s: ", defaultProfiles.size(),
                                            (defaultProfiles.size() <= 1) ? "profile" : "profiles");
             log.info("No active profile set, falling back to " + message
                      + StringUtils.collectionToDelimitedString(defaultProfiles, ", "));
         }else {
             String message = (activeProfiles.size() == 1) ? "1 profile is active: "
                 : activeProfiles.size() + " profiles are active: ";
             log.info("The following " + message + StringUtils.collectionToDelimitedString(activeProfiles, ", "));
         }
     }
 }
复制代码

这段代码主要执行的逻辑简单描述如下:从前置context的配置信息中获取他的激活的环境信息。此时判断是否为空,为空则表示用户没有选择环境信息。否则将用户选择的一个或多个环境配置类型打印到日志中。(其实就是我们日常见到的这句话)

image-20220522194604854.png

load

输出完日志后,紧跟着的一段代码不太关键,主要是向beanFactory中去注册相应的bean,如系统启动参数的Bean、Banner信息的bean等等。而比较关键的代码是load(context, sources.toArray(new Object[0])),其具体源码如下:

 protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
       logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
     //创建加载器
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    // 注入bean名字生成器、资源加载器、环境变量信息
    if (this.beanNameGenerator != null) {
       loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
       loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
       loader.setEnvironment(this.environment);
    }
     //进行实质性的加载
    loader.load();
 }
复制代码

这里的sources主要指的是Application启动类。上述代码会根据context获取对应的容器注册类,也就是BeanDefinitionRegistry,这个是Spring相对比较关键的设计之一。其实通俗来说,BeanDefinition Registry就是一个大型的Map结构,其Key是Bean的名字,value是对应BeanDefinition。这里的`BeanDefinition是Spring自己定义的一个对象。其关键作用描述如下:

     A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
     This is just a minimal interface: The main intention is to allow a BeanFactoryPostProcessor to introspect and modify property values and other bean metadata.
     
     BeanDefinition描述一个bean实例,该实例具有属性值、构造函数参数值和具体实现提供的进一步信息。这只是一个最小的接口:主要目的是允许BeanFactoryPostProcessor内省和修改属性值和其他bean元数据。
复制代码

大白话来说,BeanDefinition其实就是一个包含所有类信息的定义对象。将其存储在Registry中,是为了方便后续采用反射机制来生成bean,并对相应的Bean进行修改和属性的替换。在创建完bean的加载器,并设置好相应的bean名字生成器资源加载器环境变量后。Spring就会调用加载器进行加载了。即执行loader.load();部分的代码。

20201118133518_73866.thumb.1000_0.jpg

loader.load();的具体源码如下:

 void load() {
    for (Object source : this.sources) {
       load(source);
    }
 }
复制代码

首先是会对每个资源都调用相应的load方法。(这里通常只有启动类。)在load方法中,会判断当前资源的类型,根据当前Source类型是类、配置、包还是文本信息,来调用不同的加载方法。

 private void load(Object source) {
    Assert.notNull(source, "Source must not be null");
    if (source instanceof Class<?>) {
       load((Class<?>) source);
       return;
    }
    if (source instanceof Resource) {
       load((Resource) source);
       return;
    }
    if (source instanceof Package) {
       load((Package) source);
       return;
    }
    if (source instanceof CharSequence) {
       load((CharSequence) source);
       return;
    }
    throw new IllegalArgumentException("Invalid source type " + source.getClass());
 }
复制代码

对类进行加载的方法源代码如下所示:

 private void load(Class<?> source) {
     //判断当前该资源类是否是采用grovvy定义的资源类
    if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
       // 若是,则采用特定的资源类加载器进行加载。
       GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
       ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
    }
     //否则判断当前source是否符合1、非匿名类;2、对Grovvy加载关闭;3、拥有无参构造器
    if (isEligible(source)) {
        //满足以上三点,才能将相应source进行注册。
       this.annotatedReader.register(source);
    }
 }
复制代码

咱们具体剖析this.annotatedReader.register(source);这行代码,其源代码如下所示:

 private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
       @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
       @Nullable BeanDefinitionCustomizer[] customizers) {
     //新建BeanDefinition
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
       return;
    }
    abd.setInstanceSupplier(supplier);
     //获取bean定义的作用范围:如单例、多例等。
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
     // 生成bean的名字信息
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    if (qualifiers != null) {
       for (Class<? extends Annotation> qualifier : qualifiers) {
           //设置这个bean的一些属性:如是否需要懒初始化、当前bean是否属于优先加载的等
          if (Primary.class == qualifier) {
             abd.setPrimary(true);
          }else if (Lazy.class == qualifier) {
             abd.setLazyInit(true);
          }else {
             abd.addQualifier(new AutowireCandidateQualifier(qualifier));
          }
       }
    }
     //紧接着还需要对bean做一些自定义化的处理.
    if (customizers != null) {
       for (BeanDefinitionCustomizer customizer : customizers) {
          customizer.customize(abd);
       }
    }
     // 将对应的beanDefinition及bean名字组装成新的holder进行处理.简单来说就是对内容进行聚合并提供getter、setter方法信息。
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
     //设置当前bean是否需要被aop进行增强代理
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
     //最后一步将新的这个bean对象保存到registry中.
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
 }
复制代码

整段代码比较细致性的流程内容都写成了注释,主要实现的功能就是,新建一个BeanDefinition,并对这个BenaDefition设置作用范围、生成名字、设置懒初始化等一系列操作。最后汇总成Holder并保存到registry中,供后续使用。到此为止,整个Context的预处理的准备工作就全部完成了。

总结:

总的来说,本章内容相对枯燥,主要涉及的内容有如下几个:

1、如何根据不同的启动类型,生产出对应的Context上下文。针对这个问题,Spring采用的解决办法是采用简单工厂的方式,生成对应的Context上下文。

2、紧接着,Spring对相应的Context内容进行了处理,包括如下几个:

  • 设置Context的环境配置信息;
  • 销毁原有的bootStrapContext;
  • 设置context的Bean工厂信息,包括注册启动参数的Bean、设置懒初始化后处理器等;
  • 将相应的启动类,注册成相应BeanDefinition保存到registry中,用于后续refresh使用。

参考文章:

Spring是如何保存bean对象的(spring源码02)

猜你喜欢

转载自juejin.im/post/7101223677491937293