写点什么

spring 源码阅读之 bean 加载过程 (一)

  • 2024-05-27
    北京
  • 本文字数:6846 字

    阅读完需:约 22 分钟

如果想要阅读源码,首先要选择版本,然后将源代码下载到本地,导入 idea 中,话不多说,直接看步骤吧


这里我选择 5 版本,


下载源码


默认是 main 分支,看想学习的分支,比如我切换到 5 版本,截图如下:



2.安装 gradle


3.转换源码进 idea


下载完成后可以看到有个文档叫 import-into-idea.md,这里介绍了怎么将代码导进 idea,不过对我这种英语不好的真的是费劲,可以参考https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Spring/深入Spring源码系列(一)——在IDEA中构建Spring源码.md


查看 spring 与 gradle 的版本对应,目录:spring-framework/gradle/wrapper/gradle-wrapper.properties



最好是要保持版本一致,否则可能下面的命令执行报错


默认每次都会下载 gradle,现改成本地的文件即可



编译 spring-oxm(jdk8 报错,后来改成 jdk21 可以了,网上有说 11 也可以,可以自行实验,本人没有验证过,刚好在本地有 21 版本,直接拿来用了),由于开发使用 1.8 所以我就没有改系统默认 jdk 版本,先导入 idea,在 idea 里面操作了,最终编译成功,截图如下



可以看到 spring 源码现在不报错了




可以看到就像是我们自己的项目一样,可以看到提交记录和提交人等相关信息,也可以直接修改


spring 源码已下载到本地了,我最想了解的是 bean 的加载过程,然后点到代码里可以看到测试用例非常全面,试运行了一下,确实可以运行截图如下



接下来正式进入源码阅读时间


下面要看的测试用例为:


org.springframework.beans.factory.FactoryBeanTests#testFactoryBeanReturnsNull,代码如下


@Testvoid testFactoryBeanReturnsNull() {  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();  new XmlBeanDefinitionReader(factory).loadBeanDefinitions(RETURNS_NULL_CONTEXT);
assertThat(factory.getBean("factoryBean").toString()).isEqualTo("null");}
复制代码


先假设如果是自己的系统要做一个类似的获取 bean 的功能的话我会怎么设计:


1.解析 xml,生成 bean


2.将生成的 bean 存到一个地方


3.为了获取方便,大概率我会写一个 map,通过 bean 的 id 可以获取数据


以下是 spring 的类图


spring 源码阅读之 bean 加载过程(一)


如果想要阅读源码,首先要选择版本,然后将源代码下载到本地,导入 idea 中,话不多说,直接看步骤吧


这里我选择 5 版本,


下载源码


默认是 main 分支,看想学习的分支,比如我切换到 5 版本,截图如下:



2.安装 gradle


3.转换源码进 idea


下载完成后可以看到有个文档叫 import-into-idea.md,这里介绍了怎么将代码导进 idea,不过对我这种英语不好的真的是费劲,可以参考https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Spring/深入Spring源码系列(一)——在IDEA中构建Spring源码.md


查看 spring 与 gradle 的版本对应,目录:spring-framework/gradle/wrapper/gradle-wrapper.properties



最好是要保持版本一致,否则可能下面的命令执行报错


默认每次都会下载 gradle,现改成本地的文件即可



编译 spring-oxm(jdk8 报错,后来改成 jdk21 可以了,网上有说 11 也可以,可以自行实验,本人没有验证过,刚好在本地有 21 版本,直接拿来用了),由于开发使用 1.8 所以我就没有改系统默认 jdk 版本,先导入 idea,在 idea 里面操作了,最终编译成功,截图如下



可以看到 spring 源码现在不报错了




可以看到就像是我们自己的项目一样,可以看到提交记录和提交人等相关信息,也可以直接修改


spring 源码已下载到本地了,我最想了解的是 bean 的加载过程,然后点到代码里可以看到测试用例非常全面,试运行了一下,确实可以运行截图如下



接下来正式进入源码阅读时间


下面要看的测试用例为:


org.springframework.beans.factory.FactoryBeanTests#testFactoryBeanReturnsNull,代码如下


@Testvoid testFactoryBeanReturnsNull() {  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();  new XmlBeanDefinitionReader(factory).loadBeanDefinitions(RETURNS_NULL_CONTEXT);
assertThat(factory.getBean("factoryBean").toString()).isEqualTo("null");}
复制代码


先假设如果是自己的系统要做一个类似的获取 bean 的功能的话我会怎么设计:


1.解析 xml,生成 bean


2.将生成的 bean 存到一个地方


3.为了获取方便,大概率我会写一个 map,通过 bean 的 id 可以获取数据


以下是 spring 的类图



类图 1


具体分析:


上图看起来足够复杂,仔细分析之后发现我们写的那几步都包含在里面而已


黄色:用来存储 bean 的实体,最上层的类为 BeanDefinition,抽象类 AbstractBeanDefinition,在本测试用例中使用的是工具类 org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition 生成的 GenericBeanDefinition


public static AbstractBeanDefinition createBeanDefinition(@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setParentName(parentName); if (className != null) { if (classLoader != null) { bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }
复制代码


粉色:主要功能就是从 Document 中解析出来各个 bean 及相应的 attribute,properties 等,解析完之后交给工具类生成 BeanDefinition


主要代码如下:


public AbstractBeanDefinition parseBeanDefinitionElement(      Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); }
try { AbstractBeanDefinition bd = createBeanDefinition(className, parent); //设置bean的属性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd); parsePropertyElements(ele, bd); parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele));
return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); }
return null; }
复制代码


上面这段代码是类 BeanDefinitionParserDelegate 的,翻译过来就是 BeanDefinition 解析器代表,也就是说这个类主要负责从 document 解析为 BeanDefinition,他有几个重载方法,可返回 BeanDefinition,也可返回 BeanDefinitionHolder,当然入参也可不同


DefaultBeanDefinitionDocumentReader 获取到 BeanDefinitionParserDelegate 传过来的 BeanDefinition 后还可以进行装饰,装饰完再注册到 registry 中


protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);    if (bdHolder != null) {      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);      try {        // Register the final decorated instance.        BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());      }      catch (BeanDefinitionStoreException ex) {        getReaderContext().error("Failed to register bean definition with name '" +            bdHolder.getBeanName() + "'", ele, ex);      }      // Send registration event.      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));    }  }
复制代码


工具类方法


public static void registerBeanDefinition(      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)      throws BeanDefinitionStoreException {
// Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
复制代码


提到的 registry 就是在测试用例中 new 出来的 DefaultListableBeanFactory,这个类实现了 BeanDefinitionRegistry 接口,也实现了 AliasRegistry 接口,注册的最重要的逻辑便是


//代码来自类org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinitionthis.beanDefinitionMap.put(beanName, beanDefinition);//代码来自类org.springframework.core.SimpleAliasRegistrythis.aliasMap.put(alias, name);
复制代码


这样,后续使用时就可以通过 DefaultListableBeanFactory 提供的方法来获取 bean 的相关信息了


绿色:我觉得这部分的主要工作就是将 factory 给的 resource 数据转换成 document,然后再交给粉色去解析,主要起到一个转换作用,XmlBeanDefinitionReader 也有好几个重载方法,最终调的方法如下


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)      throws BeanDefinitionStoreException {
try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } }

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
复制代码


蓝色:这部分的主要功能我个人感觉就是 bean 的入口和出口,factory 获取 resource 资源,交给绿色部分,经过前面几个颜色的处理最后将 BeanDefinition 注册回 factory,这样就可以通过 factory 进行相关操作了


模块与模块之前都是通过接口进行交互,这样即使入口不使用 xml,也可以复用后面的逻辑,这样保证了每个类的单一职责,虽然看着复杂,但是确比较合理


图中有一些 type method 这种的是占用地方用的,因为我只看了这一点代码,并不了解全部,所以后面再看到相关的信息时会进行补充


接下来就是 classPathXmlApplicationContext 的测试用例,spring 提供的代码如下


@Testvoid singleConfigLocation() {    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(FQ_SIMPLE_CONTEXT);    assertThat(ctx.containsBean("someMessageSource")).isTrue();    ctx.close();}
复制代码


xml 文件如下:


<beans>
<bean id="someMessageSource" name="yourMessageSource" class="org.springframework.context.support.StaticMessageSource"/>
<bean class="org.springframework.context.support.ClassPathXmlApplicationContext" lazy-init="true"> <constructor-arg value="someNonExistentFile.xml"/> </bean>
</beans>
复制代码


正常的思路是那个测试用例的数据可以完全复用,再加上一些解析路径的功能,再将 map 里的数据生成 bean 就差不多了(单说解析 Bean 这块)


我们先来看一下 ClassPathXmlApplicationContext 的类继承结构



可以看到他的本质就是 ResourceLoader,即用来解析数据用的,下面我们来看下他的初始化过程


public ClassPathXmlApplicationContext(    String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)      throws BeansException {    super(parent);
//这行是解析所给的路径的,比如会解析${}对应到项目中目录位置 setConfigLocations(configLocations); //这里传的refresh是true,会在这里共用上面测试用例部分 if (refresh) { refresh(); }}
复制代码


通过上面的注解可以看到先是解析文件,然后调用 refresh



refresh 方法里面有一行获取 BeanFactory 的,可以看到获取到的是 ConfigurableListableBeanFactory,


而 obtainFreshBeanFactory()方法里有下面一行,发现最终用的 BeanFactory 为 DefaultListableBeanFactory,是不是有点眼熟了,没错,就是第一个测试用例中使用的,那后面的过程就是一致的了,下图中画出来的两句跟第一个测试用例的两句表达的是一个意思,obtainFreshBeanFactory()方法执行完之后在 factory 的 beanDefinitionMap 里已经存了 xml 里有的数据,即第一个类图里面的流程都 run 了一遍



整个 refresh 是初始化的核心方法,我将最关键的代码粘出来,


org.springframework.context.support.AbstractApplicationContext#refresh


@Overridepublic void refresh() throws BeansException, IllegalStateException {// 这中间会有一些别的操作,本次测试用例并未使用到,全都去掉了,占用篇幅太多    try {  // Tell the subclass to refresh the internal bean factory.  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory);//初始化操作
// Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);//生成bean方法
// Last step: publish corresponding event. finishRefresh(); }}
复制代码


如上备注所示,最关键的方法为,代码如下:



这个方法调的是 beanFactory.preInstantiateSingletons 方法



其中,preInstantiateSingletons 方法上两行有一个方法,getMergeLocalBeanDefinition(beanName);这里是把类图 1 初始化生成的 GenericBeanDefinition 转化成 RootBeanDefinition,后续操作都是用 RootBeanDefinition,并将 beanName 和新生成了 RootBeanDefinition 放入 mergedBeanDefinitions




接下来是



方法如下:该方法在 DefaultListableBeanFactory 中



getBean 方法是 AbstractBeanFactory 中,代码如下:



这个方法调用了自己类的 doGetBean 方法,在方法内部调用了 createBean,createBean 是个 abstract 方法,调用的是 AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])



调用如下:





最终调到这里完成了 bean 的生成



生成完 bean 之后会设置一些 RootBeanDefinition 的状态,还有类型等


将 beanName 放入到 registeredSingletons 中


将 beanName,获取到的 bean 放入 singletonObjects



到此,创建 bean 的基本主流程就结束了


以下是 spring 的类图



类图 2


通过类图 2 可以看到比类图 1 增加了紫色部分,玫粉色部分,蓝色部分也有所增加,以及最右边的橘色部分也增加了一个类


紫色部分:ClassPathXmlApplicationContext 的组成,他主要负责解析 xml 路径识别,以及构造 beanFactory,然后通过 factory 生成 BeanDefinition,以及初始化 bean


蓝色增长部分:初始化 bean 的部分,由 DefaultListableBeanFactory#instantiateSingleton 开始向 AbstractBeanFactory 类的方法转移,最终调用抽象方法 createBean,向子类转移 AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])


并最终调用新增的玫粉色部分生成 bean,并将生成的 bean 存入 DefaultSingletonBeanRegistry 中的属性中


玫粉色部分:即生成 bean 的终端,最终生成 bean 的地方


以上都是个人观点哈,如果有不正确的地方希望各位批评指正!


下期打算为各位介绍:


1.注解的类的生成过程


2.循环注入


3.抽象类能否加载


作者:京东物流 贾丽利


来源:京东云开发者社区

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
spring源码阅读之bean加载过程(一)_京东科技开发者_InfoQ写作社区