当前位置: 首页 > 编程笔记 >

Spring中BeanFactory解析bean详解

刁瀚昂
2023-03-14
本文向大家介绍Spring中BeanFactory解析bean详解,包括了Spring中BeanFactory解析bean详解的使用技巧和注意事项,需要的朋友参考一下

在该文中来讲讲Spring框架中BeanFactory解析bean的过程,该文之前在小编原文中有发表过,先来看一个在Spring中一个基本的bean定义与使用。

package bean;
public class TestBean {
  private String beanName = "beanName";
  public String getBeanName() {
    return beanName;
  }
  public void setBeanName(String beanName) {
    this.beanName = beanName;
  }
}

Spring配置文件root.xml定义如下:

<?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-4.1.xsd">

  <bean id="testBean" class="bean.TestBean">
</beans>

下面使用XmlBeanFactory来获取该bean:

public class BeanTest {

  private static final java.util.logging.Logger logger = LoggerFactory.getLogger(BeanTest.class);

  @Test
  public void getBeanTest() {
    BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
    TestBean bean = factory.getBean("testBean");
    logger.info(bean.getBeanName);
  }
}

这个单元测试运行结果就是输出beanName,上面就是Spring最基本的bean的获取操作,这里我用BeanFactory作为容器来获取bean的操作并不多见,在企业开发中一般是使用功能更完善的ApplicationContext,这里先不讨论这个,下面重点讲解使用BeanFactory获取bean的过程。

现在就来分析下上面的测试代码,看看Spring到底为我们做了什么工作,上面代码完成功能的流程不外乎如此:

1. 读取Spring配置文件root.xml;

2. 根据root.xml中的bean配置找到对应的类的配置,并实例化;

3. 调用实例化后的对象输出结果。

先来看看XmlBeanFactory源码:

public class XmlBeanFactory extends DefaultListableBeanFactory {

  private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

  public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
  }

  public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
  }
}

从上面可以看出XmlBeanFactory继承了DefaultListableBeanFactory,DefaultListableBeanFactory是Spring注册加载bean的默认实现,它是整个bean加载的核心部分,XmlBeanFactory与它的不同点就是XmlBeanFactory使用了自定义的XML读取器XmlBeanDefinitionReader,实现了自己的BeanDefinitionReader读取。

XmlBeanFactory加载bean的关键就在于XmlBeanDefinitionReader,下面看看XmlBeanDefinitionReader的源码(只列出部分):

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

  private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

  private ProblemReporter problemReporter = new FailFastProblemReporter();

  private ReaderEventListener eventListener = new EmptyReaderEventListener();

  private SourceExtractor sourceExtractor = new NullSourceExtractor();

  private NamespaceHandlerResolver namespaceHandlerResolver;

  private DocumentLoader documentLoader = new DefaultDocumentLoader();

  private EntityResolver entityResolver;

  private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
}

XmlBeanDefinitionReader继承自AbstractBeanDefinitionReader,下面是AbstractBeanDefinitionReader的源码(只列出部分):

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

  protected final Log logger = LogFactory.getLog(getClass());

  private final BeanDefinitionRegistry registry;

  private ResourceLoader resourceLoader;

  private ClassLoader beanClassLoader;

  private Environment environment;

  private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
}

XmlBeanDefinitionReader主要通过以下三步来加载Spring配置文件中的bean:

1. 通过继承自AbstractBeanDefinitionReader中的方法,使用ResourLoader将资源文件(root.xml)路径转换为对应的Resource文件;

2. 通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Ducument文件;

3. 通过DefaultBeanDefinitionDocumentReader类对Document进行解析,最后再对解析后的Element进行解析。

了解以上基础后,接下来详细分析下一开始例子中的代码:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));

先看看下面XmlBeanFactory初始化的时序图来进一步了解这段代码的执行,

在这里可以看出BeanTest测试类通过向ClassPathResource的构造方法传入spring的配置文件构造一个Resource资源文件的实例对象,再通过这个Resource资源文件来构造我们想要的XmlBeanFactory实例。在前面XmlBeanFactory源码中的构造方法可以看出,

public XmlBeanFactory(Resource resource) throws BeansException {
   this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

this.reader.loadBeanDefinition(resource)就是资源加载真正的实现,时序图中XmlBeanDefinitionReader加载数据就是在这里完成的。

接下来跟进this.reader.loadBeanDefinition(resource)方法里面,

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

  @Override
  public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
  }
}

在loadBeanDefinition(resource)方法里对资源文件resource使用EncodedResource进行编码处理后继续传入loadBeanDefinitions方法,继续跟进loadBeanDefinitions(new EncodedResource(resource))方法源码:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  Assert.notNull(encodedResource, "EncodedResource must not be null");
  if (logger.isInfoEnabled()) {
    logger.info("Loading XML bean definitions from " + encodedResource.getResource());
  }

  // 通过属性记录已加载的资源
  Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
  if (currentResources == null) {
    currentResources = new HashSet<EncodedResource>(4);
    this.resourcesCurrentlyBeingLoaded.set(currentResources);
  }
  if (!currentResources.add(encodedResource)) {
    throw new BeanDefinitionStoreException(
        "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  }
  try {
    // 从resource中获取对应的InputStream,用于下面构造InputSource
    InputStream inputStream = encodedResource.getResource().getInputStream();
    try {
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
        inputSource.setEncoding(encodedResource.getEncoding());
      }
      // 调用doLoadBeanDefinitions方法
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    finally {
      inputStream.close();
    }
  }
  catch (IOException ex) {
    throw new BeanDefinitionStoreException(
        "IOException parsing XML document from " + encodedResource.getResource(), ex);
  }
  finally {
    currentResources.remove(encodedResource);
    if (currentResources.isEmpty()) {
      this.resourcesCurrentlyBeingLoaded.remove();
    }
  }
}

继续跟进doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法,这是整个bean加载过程的核心方法,在这个方法执行bean的加载。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    throws BeanDefinitionStoreException {
  try {
    Document doc = doLoadDocument(inputSource, resource);
    return registerBeanDefinitions(doc, resource);
  }
  /* 省略一堆catch */
}

跟进doLoadDocument(inputSource, resource)源码:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
      getValidationModeForResource(resource), isNamespaceAware());
}

在doLoadDocument(inputSource, resource)方法里就使用到了前面讲的documentLoader加载Document,这里DocumentLoader是个接口,真正调用的是其实现类DefaultDocumentLoader的loadDocument方法,跟进源码:

public class DefaultDocumentLoader implements DocumentLoader {

  @Override
  public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
  }
}

从源码可以看出这里先创建DocumentBuilderFactory,再用它创建DocumentBuilder,进而解析inputSource来返回Document对象。得到Document对象后就可以准备注册我们的Bean信息了。

在上面的doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法中拿到Document对象后下面就是执行registerBeanDefinitions(doc, resource)方法了,看源码:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  documentReader.setEnvironment(getEnvironment());
  // 还没注册bean前的BeanDefinition加载个数
  int countBefore = getRegistry().getBeanDefinitionCount();
  // 加载注册bean
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  // 本次加载注册的BeanDefinition个数
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

这里的doc就是上面的loadDocument方法加载转换来的,从上面可以看出主要工作是交给BeanDefinitionDocumentReader的registerBeanDefinitions()方法实现的,这里BeanDefinitionDocumentReader是个接口,注册bean功能在默认实现类DefaultBeanDefinitionDocumentReader的该方法实现,跟进它的源码:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
  this.readerContext = readerContext;
  logger.debug("Loading bean definitions");
  Element root = doc.getDocumentElement();
  doRegisterBeanDefinitions(root);
}

到这里通过doc.getDocumentElement()获得Element对象后,交给doRegisterBeanDefinitions()方法后就是真正执行XML文档的解析了,跟进doRegisterBeanDefinitions()方法源码:

protected void doRegisterBeanDefinitions(Element root) {
  BeanDefinitionParserDelegate parent = this.delegate;
  this.delegate = createDelegate(getReaderContext(), root, parent);

  if (this.delegate.isDefaultNamespace(root)) {
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    if (StringUtils.hasText(profileSpec)) {
      String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
          profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
        return;
      }
    }
  }

  preProcessXml(root);
  parseBeanDefinitions(root, this.delegate);
  postProcessXml(root);

  this.delegate = parent;
}

到这里处理流程就很清晰了,先是对profile进行处理,之后就通过parseBeanDefinitions()方法进行文档的解析操作,跟进parseBeanDefinitions()方法源码:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  if (delegate.isDefaultNamespace(root)) {
    NodeList nl = root.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      if (node instanceof Element) {
        Element ele = (Element) node;
        // 下面对bean进行处理
        if (delegate.isDefaultNamespace(ele)) {
          parseDefaultElement(ele, delegate);
        }
        else {
          delegate.parseCustomElement(ele);
        }
      }
    }
  }
  else {
    delegate.parseCustomElement(root);
  }
}

上面if-else语句块中的parseDefaultElement(ele, delegate)和delegate.parseCustomElement(ele)就是对Spring配置文件中的默认命名空间和自定义命名空间进行解析用的。在Spring的XML配置中,默认Bean声明就如前面定义的:

<bean id="testBean" class="bean.TestBean">

自定义的Bean声明如:

<tx:annotation-driven />

XmlBeanFactory加载bean的整个过程基本就讲解到这里了。希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 本文向大家介绍Spring BeanFactory和FactoryBean区别解析,包括了Spring BeanFactory和FactoryBean区别解析的使用技巧和注意事项,需要的朋友参考一下 BeanFactory接口: IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作。 其实在容器的初始化的时候,会对BeanFactory

  • 本文向大家介绍spring中的BeanFactory与FactoryBean的讲解,包括了spring中的BeanFactory与FactoryBean的讲解的使用技巧和注意事项,需要的朋友参考一下 1.BeanFactory 1.1Spring提供了IOC容器的两种实现方式 ① BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人

  • 本文向大家介绍spring中bean的生命周期详解,包括了spring中bean的生命周期详解的使用技巧和注意事项,需要的朋友参考一下 1.Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。 2.Spring IOC容器对bean的生命周期进行管理的过程: ① 通过构造器或工厂方法创建bean实例 ② 为bean的属性设置值和对其他

  • 本文向大家介绍JSP 中Spring Bean 的作用域详解,包括了JSP 中Spring Bean 的作用域详解的使用技巧和注意事项,需要的朋友参考一下 JSP 中Spring Bean 的作用域详解 Bean元素有一个scope属性,用于定义Bean的作用域,该属性有如下五个值: 1>singleton: 单例模式,在整个spring IOC容器中,单例模式作用域的Bean都将只生成一个实例。

  • 本文向大家介绍详解Spring中Bean的加载的方法,包括了详解Spring中Bean的加载的方法的使用技巧和注意事项,需要的朋友参考一下 之前写过bean的解析,这篇来讲讲bean的加载,加载要比bean的解析复杂些,从之前的例子开始. Spring中加载一个bean的方式: 来看看getBean(String name)方法源码, 该getBean(String name)方法位于Abstra

  • 这是最简单的容器,为DI提供基本支持,并由org.springframework.beans.factory.BeanFactory接口定义。 BeanFactory和相关的接口,例如BeanFactoryAware,InitializingBean,DisposableBean,仍然存在于Spring中,目的是向后兼容与Spring集成的大量第三方框架。 BeanFactory接口有很多实现,它