二、Spring源码解析:IOC-容器的基本实现

二、IOC容器的基本实现

文章目录

  • 二、IOC容器的基本实现
    • 容器基本用法
    • 功能分析
    • beans模块的结构组成
    • 核心类介绍
      • DefaultListableBeanFactory
      • XmlBeanDefinitonReader
    • 容器的基础XmlBeanFactory
      • 配置文件封装
        • 代码执行过程
      • 加载Bean
        • 代码执行过程
      • 获取XML的验证模式
      • 获取Document
      • 解析及注册BeanDefinitions
      • 解析并注册BeanDefinition

​ 显然,源码分析是一件非常煎熬而且极具挑战性的任务,我们将从这里正式开始分析Spring源代码的分析。本文以及后续文章基于Spring-framework5.2.X并参考《Spring源码深度解析》写成。首先,我们有必要先回顾一下Spring中最简单的用法。

容器基本用法

bean是Spring中最核心的东西,那么我们先看bean的定义:

public class MyTestBean {
   private String testStr = "testStr";

   public String getTestStr() {
      return testStr;
   }

   public void setTestStr(String testStr) {
      this.testStr = testStr;
   }
}

显然,bean并没有什么特别之处,接下来创建配置文件:

<?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="myTestBean" class="bean.MyTestBean"/>
</beans>

这样一来,我们就可以写测试代码了:

public static void main(String[] args) {
   BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("springApplication.xml"));
		MyTestBean myTestBean = xmlBeanFactory.getBean("myTestBean", MyTestBean.class);
		System.out.println(myTestBean.getTestStr());
}

直接使用BeanFactory作为容器对于Spring的使用并不常见,甚至XmlBeanFactory已经被废弃。在企业级开发中都会使用的是ApplicationContext,但是便于理解,我们从BeanFactory开始。

功能分析

虽然我们可能没有看过Spring源码,但是我们肯定都用过Spring或者Springboot框架,我们应该猜的出来,这段测试代码无非就以下几点:

  • 读取配置文件springApplication.xml
  • 根据配置文件中的配置找到对应的类的配置,并实例化
  • 调用实例化后的实例

按照原始的思维方式,整个过程无非如此,但是作为一个风靡世界的源码显然不会这么简单。

beans模块的结构组成

我们打开spring-beans模块的源码可以发现,可以看到Spring用来巨多的代码来完成这个看似简单的功能。

二、Spring源码解析:IOC-容器的基本实现_第1张图片

首先我们需要梳理以下beans模块的结构,显然beans模块中我们只需要关注以下包:

  • src/main/java:Spring的主要逻辑代码
  • src/main/resources:用于存放配置文件
  • src/test/java:用于对主要逻辑进行测试的单元测试包
  • src/test/resources:用于存放测试用的配置文件

核心类介绍

在正式开始分析源码之前,有必要了解Spring中核心的两个类。

DefaultListableBeanFactory

XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册以及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方是在XmlBeanFactory中使用了自定义的XMl读取器XmlBeanDefintionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。下图为相关类图。

二、Spring源码解析:IOC-容器的基本实现_第2张图片

众所周知,UML类图是个好东西,可以帮助我们从全局角度了解某个类的脉络,而且在软件工程导论和软件需求分析中会考(刚考完…)。

首先,我们先简单地了解一下各个类的作用:

  • AliasRegistry:用于管理别名的通用接口。 用作org.springframework.beans.factory.support.BeanDefinitionRegistry的超级接口。
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。
  • SingletonBeanRegistry:定义对单例的注册及获取。
  • BeanFactory:定义获取bean及bean的各种属性的接口。
  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry个函数的实现。
  • HierarchicalBeanFactory:由bean工厂实现的子接口,可以是层次结构的一部分。可以在ConfigurableBeanFactory接口中找到用于bean工厂的相应setParentBeanFactory方法,该方法允许以可配置的方式设置父对象。
  • BeanDefinitionRegistry:定义对Bean Definition的各种增删改操作。这是Spring的bean工厂包中唯一封装了bean定义注册的接口。
  • ConfigurableBeanFactory:提供配置Factory的各种方法。
  • ListableBeanFacoty:依据各种条件获取bean的配置清单。
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
  • AutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现。
  • ConfigurableListableBeanFactory:BeanFactory配置清单,大多数可列出的bean工厂都将实现配置接口。 除了ConfigurableBeanFactory ,它还提供了用于分析和修改Bean定义以及预先实例化单例的工具。
  • DefaultListableBeanFactory:综合上面所有功能,主要是对bean注册后的处理。

XmlBeanFactory对DefaultListableBeanFactory类进行了拓展,主要用于从XML文档中读取BEanDefinition,对于注册及获取bean都是继承的DefaultListableBeanFactory,唯独不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性,主要使用reader对资源文件进行读取和注册。

写到这里不由的想吐槽一下,这类也太多了吧!类名也太长了吧!

XmlBeanDefinitonReader

XML配置文件的读取是Spring中重要的功能(虽然现在已经很少使用XML配置文件了)
二、Spring源码解析:IOC-容器的基本实现_第3张图片

先了解XmlBeanDefinitonReader的脉络,在后续再深入了解。

容器的基础XmlBeanFactory

接下来我们要开始详细探索Spring容器每个步骤的实现,首先要探究

BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("springApplication.xml"));

这行代码背后的秘密,首先画一个时序图(画的好丑啊我。。。。)
二、Spring源码解析:IOC-容器的基本实现_第4张图片

配置文件封装

​ Spring的配置文件读取是通过ClassPathResource进行封装的,比如ClassPathResource(“springApplication.xml”),那么ClassPathResource完成了什么功能呢?

​ 在Java中,将不同来源的资源抽象成URL,通过注册不同的handler(REULStreamHandler)来处理不同来源的资源的读取逻辑。Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源。
二、Spring源码解析:IOC-容器的基本实现_第5张图片

代码执行过程

了解了配置文件封装相关的类以后,我们就可以开始debug看一下代码执行的流程。

BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("springApplication.xml"));

1、我们在第一句上打断点,debug执行,步入ClassPathResource(“springApplication.xml”)

​ 可以看到ClassPathResource只有一个参数的构造方法调用了两个参数的构造方法

public ClassPathResource(String path) {
   this(path, (ClassLoader) null);
}

2、继续步入来到两个参数的构造方法:

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
   Assert.notNull(path, "Path must not be null");
   String pathToUse = StringUtils.cleanPath(path);
   if (pathToUse.startsWith("/")) {
      pathToUse = pathToUse.substring(1);
   }
   this.path = pathToUse;
   this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}

显然这个这一步完成了ClassPathResource和classLoader的初始化,并将path中最前面的/删除。

3、我们步出后进入new XmlBeanFacorty(),可以看到XmlBeanFactory一个参数的构造方法调用了两个参数的构造方法:

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

4、继续步入两个参数的构造方法,可以看到:

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    //parentBeanFactory为父类BeanFactory用于factory合并,可以为空,没太理解这
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

首先我们看第一句super(parentBeanFactory),我们继续步入,来到了父类DefaultListableBeanFactory,父类继续调用了父类的构造方法,来到AbstractAutowireCapableBeanFactory的构造方法,我们可以看到:

public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
   this();
   setParentBeanFactory(parentBeanFactory);
}

AbstractAutowireCapableBeanFactory一个参数的构造方法又调用了无参构造方法,我们步入看一眼无参构造:

public AbstractAutowireCapableBeanFactory() {
   super();
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
   if (IN_NATIVE_IMAGE) {
      this.instantiationStrategy = new SimpleInstantiationStrategy();
   }
   else {
      this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
   }
}

这里有必要提及ignoreDependencyInterface方法,ignoreDependencyInterface主要功能是忽略给定接口的自动装配功能,什么意思呢?举例来说,当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring中提供的一个重要特性。但是,某些情况下,B不会被初始化,其中一种情况就是实现了BeanNameAware接口,Spring是这么说的:忽略给定的依赖接口进行自动装配。
应用程序上下文通常将使用它来注册以其他方式解决的依赖关系,例如通过BeanFactoryAware的BeanFactory或通过ApplicationContextAware的ApplicationContext。
默认情况下,仅BeanFactoryAware接口被忽略。 要忽略其他类型,请为每种类型调用此方法。

5、回到XmlBeanFactory的两个参数的构造方法,显然this.reader.loadBeanDefinitions(resource);才是资源加载的真正实现,也是我们的分析的重点之一。

加载Bean

书接上一段,之前在XmlBeanFactory的构造方法中调用了XmlBeanDefinitionReader类型的reader提供的loadBeanDefinitions方法,显然,这句代码是整个资源加载的切入点。

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

}

这里我又要用我的三脚猫画图技术画一张loadBeanDefinitions方法执行的时序图(时序图永远滴神!)
二、Spring源码解析:IOC-容器的基本实现_第6张图片

画完图感觉自己头都大了,果然分析源码是件痛苦的事!

从上面的时序图中我们尝试梳理整个处理过程:

  • 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
  • 获取输入流。从Resource中获取对应的InputStream并构造InputSource。
  • 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。

代码执行过程

1、首先看一下loadBeanDefinitions方法具体的实现过程。

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

首先把Resource封装成了EncodedResource,那么它的作用是什么呢,通过名称我们可以大致推断,这个类主要是用于对资源文件的编码进行处理。Spring是这么告诉我们的:为给定Resource创建一个新的EncodedResource ,而不指定显式编码或Charset 。在EncodeResource中我们可以看一下getReader方法:

public Reader getReader() throws IOException {
   if (this.charset != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.charset);
   }
   else if (this.encoding != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.encoding);
   }
   else {
      return new InputStreamReader(this.resource.getInputStream());
   }
}

显然,getReader返回了一个有编码的InputStreamReader。

2、当构造好encodedResource后,再次转入了可复用方法 loadBeanDefinitions(同名参数类型不同的构造方法):

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isTraceEnabled()) {
      logger.trace("Loading XML bean definitions from " + encodedResource);
   }
//通过属性来记录已经加载的资源
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }
	//从encodedResource中拿出Resource,再从Resource中拿出InputStream
   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) {
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   }
   finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}

这个方法内部才是真正的数据准备阶段。所以我们在这里可以看出,之所以要对resource进行封装,是因为resource可能存在编码要求的情况。

3、然后我们步入核心部分doLoadBeanDefinitions(inputSource, encodedResource.getResource());

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;
   }
   catch (SAXParseException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
   }
   catch (SAXException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "XML document from " + resource + " is invalid", ex);
   }
   catch (ParserConfigurationException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "Parser configuration exception parsing XML from " + resource, ex);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "IOException parsing XML document from " + resource, ex);
   }
   catch (Throwable ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "Unexpected exception parsing XML document from " + resource, ex);
   }
}

代码是挺长,全是异常处理。。。

顺手步入doLoadDocument,它是这样的:

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

运行到这里,其实只做了三件事:

  • getValidationModeForResource(resource):获取最XML文件的验证模式
  • this.documentLoader.loadDocument():加载XML文件,并得到对应的Document
  • registerBeanDefinitions:根据返回的Document注册Bean信息。

这三个步骤支撑着整个Spring容器部分的实现,尤其事第3步对配置文件的解析,逻辑非常复杂,我们先从XML文件的验证模式看起。今天先看到这,明天继续。

获取XML的验证模式

​ XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。

​ 两种验证模式这里不在做过多介绍,在我们的例子中:

<?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 class="com.qust.test.TestBean" id="testBean"/>
</beans>

使用了XSD验证模式。

在前面的代码中,我们可以看出Spring使用getValidationModeForResource方法来获取对应资源的验证模式。

protected int getValidationModeForResource(Resource resource) {
		int validationModeToUse = getValidationMode();
    //如果手动制定了验证模式则使用指定的验证模式
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
    //如果未指定则自动检测
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		return VALIDATION_XSD;
	}

而自动检测验证模式交给了detectValidationMode来处理:

protected int detectValidationMode(Resource resource) {
   if (resource.isOpen()) {
      throw new BeanDefinitionStoreException(
            "Passed-in Resource [" + resource + "] contains an open stream: " +
            "cannot determine validation mode automatically. Either pass in a Resource " +
            "that is able to create fresh streams, or explicitly specify the validationMode " +
            "on your XmlBeanDefinitionReader instance.");
   }

   InputStream inputStream;
   try {
      inputStream = resource.getInputStream();
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
            "Did you attempt to load directly from a SAX InputSource without specifying the " +
            "validationMode on your XmlBeanDefinitionReader instance?", ex);
   }

   try {
      return this.validationModeDetector.detectValidationMode(inputStream);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
            resource + "]: an error occurred whilst reading from the InputStream.", ex);
   }
}

detectValidationMode:

public int detectValidationMode(InputStream inputStream) throws IOException {
   // Peek into the file to look for DOCTYPE.
   try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
      boolean isDtdValidated = false;
      String content;
      while ((content = reader.readLine()) != null) {
         content = consumeCommentTokens(content);
         if (this.inComment || !StringUtils.hasText(content)) {
            continue;
         }
         if (hasDoctype(content)) {
            isDtdValidated = true;
            break;
         }
         if (hasOpeningTag(content)) {
            // End of meaningful data...
            break;
         }
      }
      return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
   }
   catch (CharConversionException ex) {
      // Choked on some character encoding...
      // Leave the decision up to the caller.
      return VALIDATION_AUTO;
   }
}
private boolean hasDoctype(String content) {
   return content.contains(DOCTYPE);
}

Spring检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。

获取Document

经过了验证模式准备的步骤就可以进行Document加载了,XMLBeanFactoryReader类将文档读取的工作委托给了DocumentLoader来执行,这里的DocumentLoader是个接口,真正调用的是DefaultDocumentLoader,解析代码如下:

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

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

​ Spring在使用SAX解析XML文档的套路并没有什么特殊的地方,首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。

​ 这里有必要提及一下EntityResolver,对于参数entityResolver,传入的是通过getEntityResolver()方法获取的返回值:

//返回要使用的EntityResolver,如果未指定,则构建一个默认的解析器
protected EntityResolver getEntityResolver() {
   if (this.entityResolver == null) {
      // Determine default EntityResolver to use.
      ResourceLoader resourceLoader = getResourceLoader();
      if (resourceLoader != null) {
         this.entityResolver = new ResourceEntityResolver(resourceLoader);
      }
      else {
         this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
      }
   }
   return this.entityResolver;
}

EntityResolver的作用是项目本身提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD生命的过程。

@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
   if (logger.isTraceEnabled()) {
      logger.trace("Trying to resolve XML entity with public ID [" + publicId +
            "] and system ID [" + systemId + "]");
   }

   if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
      int lastPathSeparator = systemId.lastIndexOf('/');
      int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
      if (dtdNameStart != -1) {
         String dtdFile = DTD_NAME + DTD_EXTENSION;
         if (logger.isTraceEnabled()) {
            logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
         }
         try {
            Resource resource = new ClassPathResource(dtdFile, getClass());
            InputSource source = new InputSource(resource.getInputStream());
            source.setPublicId(publicId);
            source.setSystemId(systemId);
            if (logger.isTraceEnabled()) {
               logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
            }
            return source;
         }
         catch (FileNotFoundException ex) {
            if (logger.isDebugEnabled()) {
               logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
            }
         }
      }
   }

   // Fall back to the parser's default behavior.
   return null;
}

解析及注册BeanDefinitions

​ 当把文件转换成Document后,接下来就到了提取及注册bean。当程序已经拥有了XML文档文件的Document实例对象时,会来到这个方法:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //创建BeanDefinitionDocumentReader以用于实际从XML文档读取bean定义。
//默认实现实例化指定的“ documentReaderClass”
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //加载统计前BeanDefinition的加载个数
   int countBefore = getRegistry().getBeanDefinitionCount();
    //加载及注册bean
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    //记录本次加载的BeanDefinition个数
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

经过艰难险阻,我们终于到了核心逻辑的底部:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

我们可以发现这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition注册。如果说以前一直是XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正地进行解析了,我们期待的核心部分真正地开始了(到目前位置,spring源代码与《Spring源码深度解析》一书中已经有了很多差别,但是具体流程没有变化)。

protected void doRegisterBeanDefinitions(Element root) {
    //在递归的时候,跟踪父级delegate,新的递归调用引用上个方法的delegate(没懂什么意思。。。)
   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)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }
	
   //解析前处理,留给子类实现
   preProcessXml(root);
   parseBeanDefinitions(root, this.delegate);
    //解析后处理,留给子类实现
   postProcessXml(root);

   this.delegate = parent;
}

1、创建delegate,创建代理,然后代理首先初始化一些默认的属性,DocumentDefaultsDefinition是存储默认配置的对象。

DEFAULT_LAZY_INIT_ATTRIBUTE = “default-lazy-init”;
DEFAULT_MERGE_ATTRIBUTE = “default-merge”;
DEFAULT_AUTOWIRE_ATTRIBUTE = “default-autowire”;
DEFAULT_DEPENDENCY_CHECK_ATTRIBUTE = “default-dependency-check”;
DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE = “default-autowire-candidates”;
DEFAULT_INIT_METHOD_ATTRIBUTE = “default-init-method”;
DEFAULT_DESTROY_METHOD_ATTRIBUTE = “default-destroy-method”

  • 处理Bean的profile,是否支持,如果不接受的话直接返回,接受不做处理
  • 解析beanDefinitions,解析之前的preProcessXml,postProcessXml方法都为空,方便我们自定义一些扩展。只看解析部分

对profile处理完以后,就开始进行解析,我们发现preProcessXml()和postProcessXml()方法是空的!显然,这是设计模式中的模板方法模式(本人特别喜欢模板方法模式,个人认为扔个空方法留给子类实现是一件很酷的事0.0)。

Profile属性是一个用来配置生产环境和开发环境的属性,这样就可以方便的切换开发、部署环境。

解析并注册BeanDefinition

处理了profile后就可以进行XMl的读取了,进入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;
            if (delegate.isDefaultNamespace(ele)) {
               parseDefaultElement(ele, delegate);
            }
            else {
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

BeanDefinitionParserDelegate(Bean定义解析器委托)

上面的代码看起来逻辑还是很清晰的,因为Spring的XML配置里面由两大类Bean声明,一个是默认的,如:


一种是自定义的,如:


这两种方式的读取及解析差别是非常大的,Spring判断是是哪一种声明,是通过使用node.getNamespaceURI()获取命名空间,并于Spring中固定的命名空间http://www.springframework.org/schema/beans进行对比,如果一致则认为是默认,反之则是自定义。如果是默认声明,则使用parseDefaultElement(ele, delegate)解析,反之则使用delegate.parseCustomElement(root)来解析。对于默认标签与自定义标签解析会在下一篇文章中进行探讨。写到最后想要吐槽一下CSDN,导入markdown文档是真的不好用,图片还得一张张上传,最后的格式两篇文章都乱掉了,不知道是bug还是什么,挺离谱的。

你可能感兴趣的:(spring,java,编程语言,源码,ioc)