您现在的位置是:首页 >技术交流 >Spring源码方法解析:obtainFreshBeanFactory网站首页技术交流

Spring源码方法解析:obtainFreshBeanFactory

你可以叫我仔哥呀 2023-06-18 20:00:03
简介Spring源码方法解析:obtainFreshBeanFactory

一、前言

在之前的文章中我们对Spring有的启动流程有了一个大体的了解;学习源码的目的并非要把每一个类每一个方法都搞得十分透彻,学习源码更多的是为了学习一种思想、更好的使用框架。闲言少叙,我们直接进入今天的主题。

1、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-4.2.xsd">

  <!--导入属性文件-->
  <context:property-placeholder location="classpath:person.properties"/>

  <bean id="person" class="com.example.demo.entity.Person">

    <property name="name" value="${person.name}"></property>
    <property name="age" value="${person.age}"></property>
  </bean>
</beans>

2、Main方法

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    final Person person = context.getBean("person", Person.class);
    System.out.println(person);
}

二、obtainFreshBeanFactory

该方法位于AbstractApplicationContext类中的refresh方法,从方法名中我们大致可以猜测出当前方法的作用是去获取刷新后的BeanFactory(Bean工厂),接下来我们进入方法内部。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();
    return getBeanFactory();
}

解析:当前方法返回了一个ConfigurableListableBeanFactory的对象,方法体内总共包含两个方法,分别是

  1. refreshBeanFactory:刷新Bean工厂
  2. getBeanFactory:获取Bean工厂

1、refreshBeanFactory

首先我们解释refreshBeanFactory方法,进入方法。

/**
 * This implementation performs an actual refresh of this context's underlying
 * bean factory, shutting down the previous bean factory (if any) and
 * initializing a fresh bean factory for the next phase of the context's lifecycle.
 */
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
}
try {
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    beanFactory.setSerializationId(getId());
    customizeBeanFactory(beanFactory);
    loadBeanDefinitions(beanFactory);
    synchronized (this.beanFactoryMonitor) {
        this.beanFactory = beanFactory;
    }
}
catch (IOException ex) {
    throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
我们先看注释对方法作用整体有个了解:此实现执行此上下文的底层的实际刷新bean工厂关闭以前的bean工厂,并且为上下文生命周期的下一阶段初始化一个新的bean工厂。<br />源码解析:
  1. 首先判断是否已经存在了Bean工厂,如果存在的话则销毁。
  2. createBeanFactory():创建BeanFactory,直接new了一个DefaultListableBeanFactory

image.png
用于创建该Bean的参数是也是一个工厂(父工厂),由于是单一的Spring环境并不存在父子工厂,所以传进去的是null
image.png

tips:Spring中是有父子容器的概念的,最明显的体现就是在SpringMVC中,由于当前是在单一的Spring环境所以不存在父子工厂。

至此我们所使用的Bean工厂已经被实例化了,并且做了一些默认配置,例如初始化一些列的Map对象用于存放后续的Bean相关东西(这部分后续会单独分析)<br />Bean工厂创建好后,按照我们一般的思维,当我们所需要的对象创建好后下一步就该给对象赋值了,Spring中也是这么做的。

2、setSerializationId

这个方法就简单多了,就是给当前Bean工厂设置一个ID值。这里不多赘述

3、customizeBeanFactory

自定义BeanFactory,从方法名中也可以知道该方法就是用来当前Bean工厂设置一些自定义属性的。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
    //设置BeanDefinition是否允许被重写
    beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//是否允许循环依赖
if (this.allowCircularReferences != null) {
    beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}

4、loadBeanDefinitions(重要)

当属性值都设置好了,接下来就是要将我们的对象(也就是bean)装到容器中了,我们知道在Spring中我们在配置文件里写的bean会被封装成BeanDefinition并存入Spring容器中。接下来我们看看Spring是如何实现的,源码如下:

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    //创建一个XML解析器用解析XML配置文件
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
    // resource loading environment.
    // 设置必要的参数
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    //初始化解析器
    initBeanDefinitionReader(beanDefinitionReader);
    //加载BeanDefinition(重点)
    loadBeanDefinitions(beanDefinitionReader);
}
其他的几个方法相对来说不是那么重要,我们直接看最后的 loadBeanDefinitions(beanDefinitionReader);
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int count = 0;
    for (String location : locations) {
        count += loadBeanDefinitions(location);
    }
    return count;
}

Spring是支持多个配置文件的,所以这里是循坏的形式去加载Bean,我们进入该方法。(删除多余方法)

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
 ResourceLoader resourceLoader = getResourceLoader();
 if (resourceLoader == null) {
  	//抛异常:省略
 }

 if (resourceLoader instanceof ResourcePatternResolver) {
  // Resource pattern matching available.
  try {
   Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
   int count = loadBeanDefinitions(resources);
   if (actualResources != null) {
    Collections.addAll(actualResources, resources);
   }
   return count;
  }
  catch (IOException ex) {
    //省略
 }
 else {
  // Can only load single resources by absolute URL.
  Resource resource = resourceLoader.getResource(location);
  int count = loadBeanDefinitions(resource);
  if (actualResources != null) {
   actualResources.add(resource);
  }
  return count;
 }
}

解析:
1、获取资源加载器,用于加载资源文件(XML配置,该资源加载器在父类中初始化,所以学习Spring源码的过程中查看父类构造器是一个很重要的点,往往很多属性都是在父类中初始化)
2、获取到了资源处理器,接下来就是要处理资源了,通过Debug可以看到最终走到了这一步
image.png
3、通过一路Debug,最终我们到了XmlBeanDefinitionReader这个类,从类名中就可以猜出该类的作用是读取XML格式的配置文件,并且解析成BeanDefinition。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    //省略日志

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

    if (!currentResources.add(encodedResource)) {
        //省略异常 
    }

    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
        //省略异常
    }
    finally {
        //省略
    }
}

tips:在Spring中很多实际做事儿的方法都是以do开头的,例如 doLoadBeanDefinitions

整体方法也很好理解就是加载资源,然后加载Bean。我们进入doLoadBeanDefinitions(删除无关代码)

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;
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }

}

解析:

  1. 根据输入流获取Document对象,就是Xml被解析后的产物。
  2. 注册BeanDefinition(所谓的注册,其实就是往容器里存)

继续进入registerBeanDefinitions方法

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;
}

解析:

  1. 创建文档解析器
  2. 获取已经注册的BeanDefinition数量
  3. 注册BeanDefinition(重要,放到后续解析)

三、小结

未命名文件.png
至此我们对obtainFreshBeanFactory方法的作用有了大致的了解。一句话结论就是,该方法用于获取刷新后的Bean工厂,并且加载配置文件并解析注册成BeanDefinition。对于如果注册由于篇幅限制这里先不展开,这部分放到下一篇文章来详细解析。希望对你有所帮助

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。