Spring-Bean源码详解(1)

Spring解析注册Bean:

近日,读取了Spring源码解析一书,现写博客记录一下收获。

主要介绍

实例代码

BeanFactory bf = new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”));

MyTestBean bean = (MyTestBean) bf.getBean(“myTestBean”);

 

大致功能

读取配置文件:beanFactoryTest.xml

根据配置文件中的Bean标签的name属性,找到myTestBean,并实例化。

 

所需做的工作(粗略化)

1.读取和验证配置文件,放置内存中。(ConfigReader)

2.根据配置的内容,bean标签的class属性内容,进行反射实例化。(ReflectionUtil)

3.需要一个中间件,完成整个逻辑的串联。(App)

 

相关设计类图

 

配置文件读取流程

Resource接口

为了解决配置文件或者其他文件URL的识别,Spring对自己内部使用到的资源实现了自己的抽象(Resource接口来封装底层资源:FileURLClasspath),比如classpath:的解析。此外,还添加一些其它基本方法(判断资源是否存在exists();当前资源是否可读isReadable();是否已经打开isOpen()等方法,详情见Resource接口源码)。

 

对于不同的来源的资源文件都有对应的Resource接口实现:

  1. 文件(FileSystemResource)
  2. Classpath资源(ClassPathResource)
  3. URL资源(UrlResource)
  4. InputStream资源(InputStreamResource)
  5. Byte数组(ByteArrayResource)等

 

常见用法(题外话)

Resource resource = new ClassPathResource(“beanFactoryTest.xml”);

InputStream inputStream = resource .getInputStream();

通过其实现类,获取到Resource对象,得到InputStream,从而作一系列的操作。当然,也可以通过Resource及其子类所实现的方法来为我们提供其他操作的便利。

 

读取配置文件

当将配置文件封装到Resource对象后,就到配置文件的读取工作了,也就是代码中

BeanFactory bf = new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”));

new XmlBeanFactory() 这一过程。

 

查看XmlBeanFactory 类的构造函数,发现其实际执行代码如下:

super(parentBeanFactory);

this.reader.loadBeanDefinitions(resource);  (readerXmlBeanDefinitionReader的对象

 

ignoreDependencyInterface方法的了解

我们先来了解一下super(parentBeanFactory); ,跟踪到类AbstractAutowrieCapableBeanFactory,所执行代码如下:

public AbstractAutowireCapableBeanFactory() {

         super();

         ignoreDependencyInterface(BeanNameAware.class);

         ignoreDependencyInterface(BeanFactoryAware.class);

         ignoreDependencyInterface(BeanClassLoaderAware.class);

}

其中,ignoreDependencyInterface方法的主要功能就是忽略给定接口的自动装配功能。也就是说有两个嵌套的Bean,A中有B,在默认情况下,Spring在获取A时,若B还未初始化,就会自动初始化B。但是如果B实现了(BeanNameAwareBeanFactoryAwareBeanClassLoaderAware)接口,就会忽略B。

 

XmlBeanDefinitionReader类的讲解(Xml的核心读取类)

具体处理流程如下

先对参数Resource使用EncodedResource类进行封装。

  • EncodedResource类,顾名思义,对资源文件的编码进行处理。
  • 当xml配置文件头部有<?xml version=”1.0” encoding=”UTF-8”?>,即设置了编码属性,Spring会使用相应的编码作为输入流(resource.getInputStream())的编码)。

 

获取输入流。从Resource中获取对应的inputStream来构造InputSource。

  • InputSource并不是Spring的类,全路径:org.xml.sax.InputSource。
  • 通过SAX解析XML文件的方法来构造InputSource。(类似于DOM解析html文件一样)。

 

通过构造的InputSource和Resource实例继续调用doLoadBeanDefinitions方法。

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

 

获取验证模式

XML文件的验证模式

XML文件的验证模式有两种:XSD和DTD,两者区别在于XML文件头部定义和校验文档(DTD文件或XSD文件):

  • XSD

<beans xmlns=http://www.Springframework.org/schema/beans

      xsi:schemaLocation=”http://www.Springframework.org/schema/beans

http://www.Springframework.org/schema/beans/Spring-beans.xsd”

      ……

</beans>

 

  • DTD

<!DOCTYPE beans PUBLIC “-//Spring//DTD BEAN 2.0//EN” http://www.Springframework.org/dtd/Spring-beans-2.0.dtd>

getValidationModeForResource方法解读

  • 如果设定了验证模式则使用设定的验证模式。
  • 若有没有设定,则委托detectValidationMode方法自动检测验证模式。
  • 在detectValidationMode方法中又委托XmlValidationModeDetector类的detectValidationMode的方法进行处理。
  • 最后,XmlValidationModeDetector类的detectValidationMode的方法通过判断文档内容是否包含DOCTYPE来判断是哪种验证模式。

 

获取Document

在获取验证模式后,就到Document对象的获取。在XmlBeanFactoryReader类中,委托了DefaultDocumentLoader类去执行。

  1. 首先,创建DocumentBuilderFactory。
  2. 通过DocumentBuilderFactory创建DocumentBuilder对象。
  3. 解析InputSource来返回Document对象。

其中,值得注意的是,传进来的EntityResolver对象,回过头看XmlBeanDefinitionReader这一类中的getEntityResolver方法。

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的作用

SAX解析XML时,SAX会首先读取XML文档的声明,根据声明寻找DTD(或XSD)定义,以便对文档进行验证。默认寻找规则,是通过声明的URI地址来下载相应的验证文件,进行认证。下载文件是一个不可控的过程,当网络中断或不可用时,就会报错。

EntityResolver的作用就是提供一个寻找DTD(或XSD)声明的方法,由程序来实现寻找DTD(或XSD)声明的过程。比如,我们将验证文件放在项目某处,在实现时直接将此文档读取返回给SAX,即可避免了通过网络来寻找相应的声明。

查看entityResolver接口方法声明

InputSource resolveEntity (publicId, systemId);

 

  • publicId和systemId参数解析

解析验证模式为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”>

         ……

</beans>

读取到参数如下:

publicId: null

systemId: http://www.Springframework.org/schema/beans/Spring-beans.xsd

 

解析验证模式为DTD配置文件:

<?xml version=”1.0” encoding=”UTF-8”?>

<!DOCTYPE beans PUBLIC “-//Spring//DTD BEAN 2.0//EN” http://www.Springframework.org/dtd/Spring-beans-2.0.dtd>

<beans>

……

<beans>

读取到参数如下

publicId: -//Spring//DTD BEAN 2.0//EN

systemId: http://www.Springframework.org/dtd/Spring-beans-2.0.dtd

 

相关具体实现,Spring中使用DelegatingEntityResolver类作为EntityResolver的实现类,代码如下:

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {

                   if (systemId != null) {

                            if (systemId.endsWith(DTD_SUFFIX)) {

                                     return this.dtdResolver.resolveEntity(publicId, systemId);

                            }

                            else if (systemId.endsWith(XSD_SUFFIX)) {

                                     return this.schemaResolver.resolveEntity(publicId, systemId);

                            }

                   }

                   return null;

         }

由此代码可发现,Spring对于不同的验证模式,使用不通的解析器解析。简单描述下其具体执行过程,比如加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemid所对应的XSD文件并加载。

 

  • 解析及注册BeanDefinitions

当把文件转换为Document后,就到解析注册BeanDefinitions的工作了。回到之前的方法registerBeanDefinitions(),其主要工作执行如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

         //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader

         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

         //设置环境变量

         documentReader.setEnvironment(getEnvironment());

         //记录未加载前,已有BeanDefinition的个数

         int countBefore = getRegistry().getBeanDefinitionCount();

         //加载、注册bean

         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

         //返回本次加载的BeanDefinition的个数

         return getRegistry().getBeanDefinitionCount() - countBefore;

}

 

不难看出,加载、注册的核心,委托了BeanDefinitionDocumentReader接口(其具体实现类DefaultBeanDefinitionDocumentReader)来处理。首先进入的是registerBeanDefinitions方法,其具体代码如下:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {

                   this.readerContext = readerContext;

                   logger.debug("Loading bean definitions");

                   Element root = doc.getDocumentElement();

                   doRegisterBeanDefinitions(root);

         }

 

该方法主要提取Element root,交给doRegisterBeanDefinitions方法来处理,具体代码如下:

protected void doRegisterBeanDefinitions(Element root) {

                   //处理profile属性

String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);

                   if (StringUtils.hasText(profileSpec)) {

                            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(

                                               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);

                            if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {

                                     return;

                            }

                   }

                   BeanDefinitionParserDelegate parent = this.delegate;

                   this.delegate = createDelegate(this.readerContext, root, parent);

                   preProcessXml(root);

                   //开始解析处理

                   parseBeanDefinitions(root, this.delegate);

                   postProcessXml(root);

                   this.delegate = parent;

         }

 

其主要工作,先对bean标签的profile属性进行处理,之后开始解析工作。这里,可以留意一下preProcessXml()postProcessXml()两个方法,这两个方法并没有内容,主要便于使用者的拓展,用于Bean解析前处理和Bean解析后处理的操作,通过继承该类来重写这两个方法即可。

 

  • profile属性

一般程序都有了多套配置文件来适用于各种使用环境,而这种配置可通过profile属性来解决。具体使用流程,在配置文件上定义多个<beans>,如下:

<beans xmlns=……>

         <beans profile=”dev”>

         ……

</beans>

         <beans profile=”production”>

         ……

</beans>

</beans>

在配置文件上定义完成后,还需在web.xml声明使用哪一个环境。

<context-param>

<param-name>Spring.profiles.active</param-name>

<param-value>dev</param-value>

</context-param>

这也就对应了前面代码的环境变量设置setEnvironment(getEnvironment());,若beans定义了profile属性就会到环境变量中寻找。

 

  • parseBeanDefinitions方法

解析完profile属性后,就到XML文件的读取,也就是parseBeanDefinitions(root, this delegate)代码的执行,具体代码如下:

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

                   }

         }

这段代码不难理解,无非就是节点属于默认命名空间就调用parseDefaultElement()默认方法来解析,否则,就调用parseCustomElement()方法来解析。而对于默认和自定义的区分,就在于命名空间的对比,若和http://www.Springframework.org/schema/beans一致则为默认,否则,就是自定义标签。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值