编程语言SpringBoot运行源码分析

    作者:程序员高级码农II更新于: 2020-11-16 22:22:58

    大神带你学编程,欢迎选课

    SpringBoot运行源码分析:spring应用上下文准备。编程语言往往使程序员能够比使用机器语言更准确地表达他们所想表达的目的。对那些从事计算机科学的人来说,懂得程序设计语言是十分重要的,因为在当今所有的计算都需要程序设计语言才能完成。

    Spring应用上下文的准备

    我们在上一节完成了应用上下文的创建工作,SpringApplication 继续通过 prepareContext方法来进行应用上下文的准备工作。首先,通过图 4-4 来整体了解一下 prepareContext 的核心功能及流程。

    编程语言程序员精选的12个Github项目_编程语言视频_java视频_程序员学习视频_课课家

    配合流程图,看一下 SpringApplication 中 prepareContext 方法源代码及功能注解。

    1. private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment 
    2. environment, 
    3. SpringApplicationRunL 
    4. isteners 
    5. listeners , 
    6. Applicat ionArguments applicat ionArguments, Bann 
    7. er printedBanner) { 
    8. //没置上下文的配置环境 
    9. context . setEnvironment (environment); 
    10. //应用上下文后置处理 
    11. postProcessApplicationContext( context);//在 context 刷新之前,Appl icat ionContext Init 
    12. ial izer 初始化 context 
    13. applyInitializers(context); 
    14. //通知监听器 context 准备完成,该方法以 上为上下文准备阶段,以下为上下文加载阶段 
    15. listeners . contextPrepared(context);//打印日志,启动 Profile 
    16. if (this . logStartupInfo)- 
    17. logStartupInfo(context . getParent() == nu1l); 
    18. logStartupProfileInfo( context); 
    19. //获得 ConfigurableL istableBeanFactory 并炷册单例对象 
    20. ConfigurableL istableBeanFactory beanFactory = context . getBeanFactory(); 
    21. beanFactory. registerSingleton("springApplicat ionArguments", applicationAr 
    22. guments); 
    23. if (printedBanner != null) { 
    24. //注册打印日志对象 
    25. beanF actory. registerSingleton("springBootBanner", printedBanner); 
    26. if (beanFactory instanceof DefaultlistableBeanFactory) { 
    27. //没置是否允许覆盖炷册 
    28. ((DefaultListableBeanFactory) beanFactory) 
    29. . setAllowBeanDefinitionOverriding(this . allowBeanDefinitionOverriding); 
    30. //获取全部配置源,其中包含 primarySources 和 sources 
    31. Set<0bject> sources = getAllSources(); 
    32. Assert . notEmpty(sources, "Sources must not be empty"); 
    33. //将 sources 中的 Bean 加载到 context 中 
    34. load(context, sources . toArray(new 0bject[0])); 
    35. //遁知监听器 context 加载完成 
    36. listeners . contextLoaded(context); 

    通过流程图和具体代码可以看出,在该方法内完成了两步操作:应用上下文的准备和加载。

    下面我们针对具体的源代码进行详细讲解。

    应用上下文准备阶段

    在上下文准备阶段,主要有 3 步操作:对 context 设 置 environment、应用上下文后置处理和 ApplicationContextlnitializer 初始化 context 操作。

    首先是对 context 设置 environment,代码和业务操作都很简单。

    1. public void setEnvironment (ConfigurableEnvironment environment) { 
    2. //设置 context 的 environment 
    3. super. setEnvi ronment( environment); 
    4. //设置 context 的 reader 属性的 conditionEvaluator 属性 this.reade 
    5. er. settEnvironment(environment) ; 
    6. //设置 context 的 scanner 属性的 environment 属性 
    7. this. scanner. setEnvi ronment ( envi ronment); 

    随 后 , 便 是 进 行 Spring 应 用 上 下 文 的 后置处理 , 这 一 步 是 通 过postProcessApplicationContext 方法来完成的。

    1. protected void postProcessApplicat ionContext (ConfigurableApplicat ionConEext 
    2. context){ 
    3. f (this. beanNameGenerator != null) { 
    4. // 如果 beanNameGenerator 为 null, 则将当前的 beanNameGenerator 按照默认名字进 
    5. 行注册 
    6. context . getBeanFactory(). regi sterSingleton( 
    7. Annotat ionConfigUtils .CONF IGURATION BEAN NAME GENERATOR, 
    8. this . beanNameGenerator); 
    9. esourceLoader 为 null 时, 则根据 context 的类型分别进行 Resourceloader 和 CL 
    10. assLoader 的设置 
    11. if (this .resourceLoader != null) { 
    12. F (context instanceof GenericApplicationContext) { 
    13. ((GenericApplicationContext) context) . setResourcel oader(this . resource 
    14. Loader); 
    15. if (context instanceof DefaultResourceLoader) { 
    16. ( (DefaultResourceLoader) context) 
    17. . setClassLoader(this.resourceLoader. getClassLoader()); 
    18. //如果为 true 则获取并没置转换服务 
    19. f (this .addConversionService) { 
    20. context . getBeanFactory(). setConversionService( 
    21. ApplicationConversionService . getSharedInstance()); 

    postProcessApplicationContext 方 法 主 要 完 成 上 下 文 的 后 置 操 作 , 默 认 包 含beanNameGeneratorResourceL oader.ClassL oader 和 ConversionService 的设置。该方法可由子类覆盖实现,以添加更多的操作。

    而在此阶段,beanNameGenerator 和 resourceL oader 都为 null,因此只操作了最后-一步的设置转换服务。

    最后,在通知监听器 context 准备完成之前,通过 applylnitializers 方法对上下文进行初始化。

    所使用的 ApplicationContextInitializer 正是我们在 SpringApplication 初始化阶段设置在itializers 变量中的值,只不过在通过 getlnitializers 方法获取时进行了去重和排序。

    1. protected void applyInitializers(ConfigurableApplicat ionContext context) { 
    2. /获取 Appl icat ionContextInitializer 集合并遍历 
    3. for (ApplicationContextInitializer initializer : getInitializers()) { 
    4. //解析当前 initial izer.实现的 Appl icat ionContextInitializer 的泛型参数 
    5. Class requiredType = GenericTypeResolver . resolveTypeArgument( 
    6. initializer . getClass(), ApplicationContextInitializer.class); 
    7. 1 断言判断所需类似是否与 context 类型匹配 
    8. Assert. isInstanceOf(requiredType, context, "Unable to call initialize 
    9. r."); 
    10. // 初始化 context 
    11. initializer. initialize(context); 

    完成以上操作之后,程序便调用 SpringApplicationRunListeners 的 contextPrepared 方法通知监听器,至此第一阶段的准备操作完成。

    应用上下文加载阶段

    应用上下文加载阶段包含以下步骤:打印日志和 Profile 的设置、设置是否允许覆盖注册、获取全部配置源、将配置源加载入上下文、通知监控器 contex 加载完成。

    首先进入应用上下文加载阶段的操作为打印日志和 Profile 的设置,对此不展开讲解。随后,便是获得 ConfigurableL istableBeanFactory 并注册单例对象,注册的单例对象包含:

    ApplicationArguments 和 Banner。 当 BeanFactory 为 DefaultL istableBeanFactory 时,进入设置是否允许覆盖注册的处理逻辑。

    此处需注意的是,当进行了 ApplicationArguments 类单例对象的注册之后,也就意味着我们在使用 Spring 应用上下文的过程中可以通过依赖注入来使用该对象。

    1. @Resource 
    2. private ApplicationArguments applicat ionArguments; 

    完成以.上操作后,便进入配置源信息的处理阶段,这一步通过 getAllSources 方法来对配置源信息进行合并操作。

    1. public Set getAllSources() { 
    2. Set<0bject> allSources = new LinkedHashSet<>(); 
    3. if (!CollectionUtils.isEmpty(this . primarySources)) { 
    4. allSources.addAll(this.primarySources); 
    5. if (!CollectionUtils . isEmpty(this. sources)) { 
    6. allSources. addAll(this.sources); 
    7. return Collections . unmodifiableSet(allSources); }以上操作逻辑很简单,如果 Set 集合中不存在 primarySources 配置源或 sources 配置源,则将其添加入 Set 中,同时将 Set 设置为不可修改,并返回。

      前面章节已经提到,变量 primarySources 的值 来自 SpringApplication 的构造参数,变量sources 的值来自 setResources 方法。

      当获得所有的配置源信息之后,通过 load 方法将配置源信息加载到上下文中,代码如下。

      1. protected void load(ApplicationContext context, Object[] sources) { 
      2. /日志打印 
      3. BeanDefinitionLoader loader = createBeanDefinitionLoader( 
      4. getBeanDefinitionRegistry(context), sources); 
      5. f (this. beanNameGenerator != nu1l). 
      6. loader. setBeanNameGenerator(this . beanNameGenerator); 
      7. if (this.resourceLoader != nu1l) { 
      8. loader . setResourceLoader(this . resourceLoader); 
      9. if (this. environment != null) { 
      10. loader . setEnvironment (this . environment) ; 
      11. loader. load(); 

      该方法主要通过 BeanDefinitionL oader 来完成配置资源的加载操作。我们进一步查看方法createBeanDefinitionL oader 的源代码,会发现它最终调用了 BeanDefinitionL oader 的构造方法,并进行初始化操作。

      1. BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { 
      2. this. sources = sources; 
      3. this . annotatedReader = new AnnotatedBeanDefinitionReader(registry); 
      4. this . xmlReader = new XmlBeanDefinitionReader(registry); 
      5. if (isGroovyPresent()) 
      6. this. groovyReader = new GroovyBeanDefinitionReader(registry); 

      通过 BeanDefinitionLoader 的构造方法我们可以看到 BeanDefinitionLoader 支持基于AnnotatedBeanDefinitionReaderXmlBeanDefinitionReader、GroovyBeanDefinitionReader等 多种类型的加载操作。

      在执行完 BeanDefinitionL oader 的创建及基本属性设置之后,调用其 load方法,该方法最终执行以下代码。

      1. private int load(0bject source) { 
      2. Assert. notNull(source, "Source must not be null"); 
      3. if (source instanceof Class) { 
      4. return load((Class) source); 
      5. }if (source instanceof Resource) 
      6. return load( (Resource) source); 
      7. if (source instanceof Package) { 
      8. return load( (Package) source); 
      9. if (source instanceof CharSequence) { 
      10. return load( (CharSequence) source); 
      11. throw new IllegalArgumentException("Invalid source type ”+ source. getC 
      12. lass());} 

      从以上代码可以看出,BeanDefinitionLoader 加载支持的范围包括:

      Class、Resource、 Package 和 CharSequence 四种。 前面我们已经提到变量 sources的来源有 primarySources 配置源和 sources 配置源。变量 primarySources 在初始化时接收的类型为 Class,而变量 sources 通过 set(Set )方法接收的参数为 String 集合。

      因此,在实际使用的过程中,Resource 和 Package 的判断分支始终无法进入执行阶段。

      完成以上操作后,接下来执行 SpringApplicationRunListeners 的 contextL oaded 方法通知监听器上下文加载完成,至此整个 Spring 应用上下文的准备阶段完成。

      在过去的几十年间,大量的编程语言被发明、被取代、被修改或组合在一起。尽管人们多次试图创造一种通用的程序设计语言,却没有一次尝试是成功的。之所以有那么多种不同的编程语言存在的原因是,编写程序的初衷其实也各不相同;新手与老手之间技术的差距非常大,而且有许多语言对新手来说太难学;还有,不同程序之间的运行成本(runtime cost)各不相同。

      未登录