首页 > 编程语言 >Spring源码分析(五)BeanDefinition(下)

Spring源码分析(五)BeanDefinition(下)

时间:2023-09-13 13:32:17浏览次数:35  
标签:name Spring age 合并 String 源码 BeanDefinition 属性

上篇文章已经对 BeanDefinition 做了一系列的介绍,这篇文章讲一些 BeanDefinition 合并的一些知识,完善整个 BeanDefinition 的体系,Spring 在创建一个 bean 时多次进行了 BeanDefinition 的合并。本文主要对应官网中 1.7 小节

上篇文章主要说了 BeanDefinition 的一些属性,其中有以下几个属性:

org.springframework.beans.factory.config.BeanDefinition

Spring源码分析(五)BeanDefinition(下)_xml

这两个属性和 BeanDefinition 的合并相关,那么先考虑一个问题,什么是合并?

什么是合并?

先看官网上的一段介绍:

Spring源码分析(五)BeanDefinition(下)_spring_02

大概翻译如下:

一个 BeanDefinition 包含了很多的配置信息,包括构造参数,setter 方法的参数还有容器特定的一些配置信息,比如初始化方法,静态工厂方法等等。一个子 BeanDefinition 可以从它的父 BeanDefinition 继承配置信息,不仅如此,还可以覆盖其中的一些值或者添加一些自己需要的属性。使用 BeanDefinition 的父子定义可以减少很多的重复属性的设置。父 BeanDefinition 可以作为 BeanDefinition 定义的模板。

我们通过一个例子来观察下合并发生了什么,编写一个 Demo 如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="parent" abstract="true"
        class="com.wxx.official.merge.TestBean">
      <property name="name" value="parent"/>
      <property name="age" value="1"/>
   </bean>
   <bean id="child"
        class="com.wxx.official.merge.DerivedTestBean"
        parent="parent" >
      <property name="name" value="override"/>
   </bean>
</beans>
public class DerivedTestBean {
   private String name;


   private String age;


   public void setName(String name) {
      this.name = name;
   }


   public void setAge(String age) {
      this.age = age;
   }


   public String getName() {
      return name;
   }


   public String getAge() {
      return age;
   }
}


public class TestBean {
   private String name;


   private String age;


   public void setName(String name) {
      this.name = name;
   }


   public void setAge(String age) {
      this.age = age;
   }


   public String getName() {
      return name;
   }


   public String getAge() {
      return age;
   }
}


public class Test {
   public static void main(String[] args) {
      ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml");
      DerivedTestBean derivedTestBean = (DerivedTestBean) cc.getBean("child");
      System.out.println("derivedTestBean 的 name = " + derivedTestBean.getName());
      System.out.println("derivedTestBean 的 age = " + derivedTestBean.getAge());
   }
}

运行结果:

derivedTestBean 的 name = override
derivedTestBean 的 age = 1

在上面例子中,我们将 DerivedTestBean 的 parent 属性设置为了 parent,指向了我们的 TestBean,同时将 TestBen 的 age 属性设置为 1,但是我们在配置文件中并没有直接设置 DerivedTestBean 的 age 属性。但是最后的运行结果却发现 DerivedTestBean 中的 age 属性有值了,并且为 1。就是我们在其 parent bean(也就是 TestBean)中设置的值。也就是说,子 BeanDefinition 会从父 BeanDefinition 中继承没有的属性。另外,DerivedTestBean 和 TestBean 都指定了 name 属性,但是可以发现,这个值并没有被覆盖掉,也就是说,子 BeanDefinition 中已经存在的属性不会被父 BeanDefinition 所覆盖

合并的总结:

  • 子 BeanDefinition 会从父 BeanDefinition 中继承没有的属性
  • 这个过程中,子 BeanDefinition 中已经存在的属性不会被父 BeanDefinition 所覆盖

关于合并需要注意的点:

  • 子 BeanDefinition 中 class 属性如果为 null,同时父 BeanDefinition 又指定了 class 属性,那么子 BeanDefinition 也会继承这个 class 属性。
  • 子 BeanDefinition 必须要兼容父 BeanDefinition 中所有的属性,这是什么意思呢?以我们上面的 demo 为例,我们在父 BeanDefinition 中指定了 name 和 age 属性,但是如果子 BeanDefinition 中提供了一个 name 的 setter 方法,这个时候 Spring 会在启动的时候报错。因为子 BeanDefinition 不能继承所有来自父 BeanDefinition 的属性。
  • 关于 BeanDefinition 中 abstract 属性的说明:

a. 并不是作为父 BeanDefinition 就一定要设置 abstract 属性为 true,abstract 只代表了这个 BeanDefinition 是否要被 Spring 进行实例化并被创建对应的 bean,如果为 true,代表容器不需要去对其进行实例化。

b. 如果一个 BeanDefinition 被当作父 BeanDefinition 使用,并且没有指定其 class 属性,那么必须要设置其 abstract 为 true。

c. abstract=true 一般会和父 BeanDefinition 一起使用,因为当我们设置某个 BeanDefinition 的 abstract=true 时,一般都是要将其当作 BeanDefinition 的模板来使用,否则这个 BeanDefinition 也没有意义。除非我们使用其他 BeanDefinition 来继承它的属性

Spring 在哪些阶段做了合并?

  1. 扫描并获取到 BeanDefinition:

这个阶段的操作主要发生在 invokeBeanFactoryPostProcessors,对应的执行该方法的类为:PostProcessorRegistrationDelegate,对应的调用栈如下:

Spring源码分析(五)BeanDefinition(下)_spring_03

方法源码如下:

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessorsxxx

Spring源码分析(五)BeanDefinition(下)_xml_04

省略部分代码,这部分代码和 BeanFactoryPostProcessor 接口相关,和这篇的 BeanDefinition 的合并无关,下片容器的扩展点再介绍

画图如下:

Spring源码分析(五)BeanDefinition(下)_spring_05

即每次调用 beanFactory.getBeanNamesForType 都进行了一次 BeanDefinition 的合并,getBeanNamesForType 这个方法主要目的是为了指定类型 BeanDefinition 的名称,之后通过 BeanDefinition 的名称找到指定的 BeanDefinition,然后获取对应的 bean,比如上面方法三次获取的都是 BeanDefinitionRegistryPostProcessor 这个类型 BeanDefinition。

现在思考一个问题,为什么这一步需要合并???

  1. 实例化

Spring 在实例化一个对象也会进行 BeanDefinition 的合并。

第一次:

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

Spring源码分析(五)BeanDefinition(下)_实例化_06

第二次:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

Spring源码分析(五)BeanDefinition(下)_spring_07

我们可以发现这两次合并有一个共同的特点,就是合并之后立马利用了合并之后的 BeanDefinition,简称为 mbd,然后做了一系列的判断,比如上的 dependsOn != null && mbd.isSingleton()。基于上面几个例子我们来分析:为什么要合并???

为什么要合并?

在扫描阶段,之所以发生了合并,是因为 Spring 需要拿到指定的实现了 BeanDefinitionRegistryPostProcessor 接口 BeanDefinition 的名称,也就是说,Spring 需要用到 BeanDefinition 的名称,才进行了一次 BeanDefinition 的合并。在实例化阶段,是因为 Spring 需要用到 BeanDefinition 中一系列属性做判断所有才进行了一次合并。总结起来就是一个原因:Spring 需要用到 BeanDefinition 的属性,要保证获取到的 BeanDefinition 属性是正确的。

那么问题来了,为什么获取到的 BeanDefinition 属性可能是不正确的呢?

主要两个原因:

  1. 作为子 BeanDefinition,属性本身就可能有缺失,比如开头的例子,子 BeanDefinition 没有 age 属性,age 属性在父 BeanDefinition 中。
  2. Spring 提供了很多的扩展点,在启动容器的时候,可能会修改 BeanDefinition 中的属性。比如一个正常实现了 BeandFactoryPostProcessor 就能修改容器中任意的 BeanDefinition 属性。在后面的容器的扩展点再说。

合并的代码分析:

因为合并的代码其实很简单,这里一并分析了,也可以加深对合并的理解:

org.springframework.beans.factory.support.AbstractBeanFactory#getMergedLocalBeanDefinition

Spring源码分析(五)BeanDefinition(下)_spring_08

org.springframework.beans.factory.support.AbstractBeanFactory#getMergedBeanDefinition

Spring源码分析(五)BeanDefinition(下)_spring_09

Spring源码分析(五)BeanDefinition(下)_spring_10

上图的这段代码整体不难理解,可能发生的疑惑主要是两个点:

1. pbd = getMergedBeanDefinition(parentBeanName);

这里进行的是父 BeanDefinition 合并,是方法的递归调用,这是因为在合并的时候父 BeanDefinition 可能还不是一个合并后的 BeanDefinition

2. containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()

如果加了下面的嵌套配置,那么 containingBd 就不会为 null:

<bean id="indexService" class="com.wxx.official.merge.IndexService" scope="prototype">
   <property name="lookUpService">
      <bean class="com.wxx.official.merge.LookUpService" scope="singleton"></bean>
   </property>
</bean>

在这个例子中,containingBd 是 IndexService,此时 IndexService 是一个原型的 BeanDefinition,但 LookUpService 是一个单例的 BeanDefinition,这个时候经过合并,LookUpService 也会变成一个原型的 BeanDefinition。

总结:

这篇文章我觉得重要的是,要明白 Spring 为什么合并,之所以每次都需要用到 BeanDefinition 进行一次合并,是为了每次拿的是最新的,最有效的 BeanDefinition,因为利用容器提供了一些扩展点我们可以修改 BeanDefinition 中的属性。关于容器的扩展点,比如上文提到的 BeanFactoryPostProcessor 以及 BeanDefinitionRegistryPostProcessor,在后面的文章再写了。

最后一句话,BeanDefinition 是整个 Spring 的基石,我本人也是在花费大量时间研究相关知识,加油,共勉!!

标签:name,Spring,age,合并,String,源码,BeanDefinition,属性
From: https://blog.51cto.com/u_15668812/7454784

相关文章

  • 基于 COLA 架构的 Spring Cloud Alibaba(二)整合 MyBatis-Plus、 Knife4j
    上一篇中,我们介绍了项目的基本架构和相关知识。这一篇,我们将在上一篇已搭建好的项目基础架构上进行整合MyBatis-Plus、Knife4j。1.整合MyBatis-Plus1.1.关于MyBatis-PlusMyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效......
  • subDomainBrute源码分析
    SubDomainsBrute简介​ SubDomainsBrute是一款目标域名收集工具,用小字典递归地发现三级域名、四级域名、五级域名等不容易被探测到的域名。字典较为全面,小字典就包括3万多条,大字典多达8万条。默认使用114DNS、百度DNS、阿里DNS这几个快速又可靠的公共DNS进行查询,可随时修改配置......
  • Bug库____org.springframework.jdbc.IncorrectResultSetColumnCountException: Incorr
    Bug:使用到了spring的jdbctemplate模板使用到以下template.queryForObject(sql,requiredType)template.queryForList(sql,elementType,args)报以下错误org.springframework.jdbc.IncorrectResultSetColumnCountException:Incorrectcolumncount:expected1,actual3检查完......
  • Android后台模拟点击探索(附源码)攻略
    ​本攻略将详细介绍如何在Android应用中使用后台模拟点击的技术。通过模拟点击,我们可以在后台执行一些用户交互操作,例如点击按钮、输入文本等。这对于自动化测试、批量操作等场景非常有用。步骤一:添加权限首先,在AndroidManifest.xml文件中添加以下权限:<uses-permissionandro......
  • SpringCould总结 | 第八篇 工程加载配置中心的配置文件
    //工程结构//pom文件<projectxmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">......
  • SpringCould总结 | 第四篇 服务负载均衡feign
    //工程结构//pom文件<projectxmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">......
  • SpringBoot入门(一) springBoot框架搭建和启动
    1.创建maven工程MavenProject      //CODE    <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xs......
  • SpringBoot教程(二)springboot的配置文件
    一.springboot配置文件的类型application.propertiesapplication.yml项目结构,因为不可以同时使用这两种文件启动时任选一个放到resources下即可 二.properties配置文件的使用packagecom.lpinfo.shop.lpinfoshop;importorg.springframework.beans.factory.annotation.Autowi......
  • 基于注解的AOP日志切面控制SpringAOP
    1.配置注解(作用于方法上,相当于要告诉aop对哪些方法做切面植入)importjavax.jdo.annotations.Element;importjava.lang.annotation.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceAspectPointCutTag{Stringnam......
  • 领域驱动模型DDD(四)——Eventuate Tram Saga源码讲解
    前言虽然本人一直抱怨《微服务架构设计模式》中DDD模式下采用的EventuateTramSaga不算简单易用,但是为了更加深入了解原文作者的设计思路,还是花了点时间去阅读源码,并且为了自己日后自己返回来看的懂,就斗胆地对整个EventuateTramSaga从注册到执行的代码运行流程进行注释解读下,......