从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自身定义的领域事件,从而初始化相应内容信息。相似的事件处理还有以下几个。

在这里插入图片描述

​ 紧接着,会去发送领域事件,用于关闭当前的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的配置信息中获取他的激活的环境信息。此时判断是否为空,为空则表示用户没有选择环境信息。否则将用户选择的一个或多个环境配置类型打印到日志中。(其实就是我们日常见到的这句话)

在这里插入图片描述

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();部分的代码。

在这里插入图片描述

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值