容器基本用法
bean是Spring 中最核心的东西,Spring就像是个大水桶,而bean就像是容器中的水。
bean定义
1 | public class MyTestBean { |
bean配置
1 | <xml version="1.0" encoding="UTF-8"?> |
测试代码
1 | "deprecation") ( |
功能分析
- 读取配置文件beanFactoryTest.xml。
- 根据beanFactoryTest.xml中的配置找到对应的类的配置,并实例化。
- 调用实例化后的实例。
- ConfigReader:用于读取及验证配置文件,然后放置在内存中。
- ReflectionUtil:用于根据配置文件中的配置进行反射实例化。
- App:用于完成整个逻辑的串联。
Spring结构组成
beans包的层级结构
- src/main/java用于展现Spring的主要逻辑。
- src/main/resources用于存放系统的配置文件。
- src/test/java用于对主要逻辑进行单元测试。
- src/test/resources用于存放测试用的配置文件。
核心类介绍
DefaultListableBeanFactory
DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现.
XmlBeanFactory
XmlBeanFactory继承自DefaultListableBeanFactory,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现。与父类不同的是,在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,通过XmlBeanDefinitionReader类型的reader属性对资源文件进行读取和注册,实现个性化的BeanDefinitionReader读取。
1 |
|
XmlBeanDefinitionReader
1 | public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { |
1 | public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable { |
1 | public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { |
- 通过继承向AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。
- 通过DocumentLoader对Resource 文件进行转换,将Resource 文件转换为Document文件。
- 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析注册,并使用BeanDefinitionParserDelegate对Element进行解析。
容器实现准备
1 | BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml")); |
首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,后续的资源处理就可以用Resource提供的各种服务来操作了,有了Resource后就可以进行XmlBeanFactory的初始化了。
配置文件封装
Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源。
1 | public interface InputStreamSource { |
1 | public interface Resource extends InputStreamSource { |
对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。
在日常开发工作中,资源文件的加载可以直接使用Spring提供的类:
1 | Resource resource = new ClassPathResource("beanFactoryTest.xml"); |
Resource及其子类为我们提供了诸多特性,方便可以对所有资源文件进行统一处理。但实现非常简单,以getInputStream为例:
ClassPathResource.java
1 | public ClassPathResource(String path) { |
FileSystemResource.java
1 |
|
传入配置资源
XmlBeanFactory.java
1 | public XmlBeanFactory(Resource resource) throws BeansException { |
在加载数据前有一个调用父类构造函数初始化的过程super(parentBeanFactory),跟踪代码到父类DefaultListableBeanFactory的父类AbstractAutowireCapableBeanFactory的构造函数中:
1 | public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) { |
1 | public AbstractAutowireCapableBeanFactory() { |
这里有必要提及ignoreDependencylnterface方法。ignoreDependencyInterface的主要功能是忽略给定接口的向动装配功能。
举例来说,当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring中提供的一个重要特性。但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。
资源转换处理
(1)XmlBeanFactory构造函数中调用XmlBeanDefinitionReader类型的reader属性提供的方法。
1 | this.reader.loadBeanDefinitions(resource);//整个资源加载的切入点 |
(2)封装资源文件。考虑到Resource可能存在编码要求的情况,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
1 |
|
EncodeResource主要用于对资源文件的编码进行处理,主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。
1 | public Reader getReader() throws IOException { |
(3)获取输入流。从Resource中获取对应的InputStream并构造InputSource。
1 | public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { |
容器实现核心
核心处理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())主要做三件事,每一件都必不可少,在三个步骤支撑着整个Spring容器部分的实现,尤其第三步对配置文件的解析:
- 获取对XML文件的验证模式。
- 加载XML文件,并得到对应的Document。
- 根据返回的Document注册Bean信息。
1 | protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { |
获取XML验证模式
比较常用的XML文件的验证模式有两种:DTD和XSD
DTD与XSD区别
DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。通过比较XML文档和DTD文件来看文档是否符合规范。
要使用DTD验证模式时需要在XML文件头部声明,以下是在Spring中使用DTD声明方式的代码:
1 | <?xml version="1.0" encoding="UTF-8"?> |
Spring-beans-2.0.dtd部分如下:
1 | <!ELEMENT beans ( |
XSD(XML Schemas Definition)即XML Schema语言,用来描述XML文档的结构。文档设计者可以通过XML Schema指定XML文档所允许的结构和内容,并可据此检查验证XML文档是否是有效的。XML Schema本身是符合XML语法结构的XML文档,可以用通用的XML解析器解析它。
使用XML Schema文档对XML实例文档进行检查,除了要声明名称空间外(xmlns=http://www.Srpingframework.org/schema/beans),还必须指定该名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一部分是名称空间的URI,另一部分是该名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd")。
1 | <?xml version="1.0" encoding="UTF-8"?> |
Spring-beans-3.0.xsd部分代码如下:
1 | <xsd:schema xmlns="http://www.springframework.org/schema/beans" |
验证模式的读取
1 | //XmlBeanDefinitionReader.java |
1 | //XmlBeanDefinitionReader.java |
1 | //XmlValidationModeDetector.java |
获取Document
1 | private DocumentLoader documentLoader = new DefaultDocumentLoader(); |
获取了XML的验证模式后,就开始进行Document加载,同样XmlBeanFactoryReader类将文档读取的任务委托给了DocumentLoader去执行,这里的DocumentLoader是个接口,真正调用的是DefaultDocumentLoader:
1 | public class DefaultDocumentLoader implements DocumentLoader { |
EntityResolver用法
1 | //XmlBeanDefinitionReader.java |
对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认通过网络(声明的DTD的URI地址)来下载相应的DTD声明进行认证。下载是一个漫长的过程,当网络中断或不可用时,会因为相应DTD声明没有被找到而报错。
EntityResolver的作用是项目本身就可以提供一个寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。
官方说明:如果SAX应用程序需要实现自定义处理外部实体,则必须实现EntityResolver接口并使用setEntityResolver方法向SAX驱动器注册一个实例:
1 | package org.xml.sax; |
resolveEntity方法接收两个参数publicId和systemId,并返回一个inputSource对象。
举例:
1.解析验证模式未XSD的配置文件:
1 | <?xml version="1.0" encoding="UTF-8"?> |
读取到以下两个参数:
- publicId:null
- systemId:http://www.springframework.org/schema/beans/Spring-beans.xsd
2.解析验证模式为DTD的配置文件:
1 | <?xml version="1.0" encoding="UTF-8"?> |
读取到以下两个参数:
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/dtd/Spring-beans-2.0.dtd
Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,将声明URL转换为自己工程里对应的地址文件:
1 | //DelegatingEntityResolver.java |
1 | public class BeansDtdResolver implements EntityResolver { |
解析注册
当把文件转换为Document后,根据返回的Document注册Bean信息:
1 | public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { |
1 | public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader{ |
doRegisterBeanDefinitions是核心逻辑的底部:
1 | public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader{ |
preProcessXml和postProcessXml是空实现,因为一个类要么是面向继承设计的,要么就用final修饰,在DefaultBeanDefinitionDocumentReader中并没有用final修饰,所以它是面向继承而设计的。这两个方法正是为子类而设计的,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,只需重写这两个方法即可(设计模式:模板方法模式)。
profile属性的使用
1 | <beans xmlns="http://www.Springframework.org/schema/beans" |
集成到Web环境中时,在web.xml中加入:
1 | <context-param> |
这个特性,使我们可以在配置文件中部署两套配置来适用于生产环境和开发环境,可以方便的进行切换开发、部署环境、更换不同数据库。
解析注册BeanDefinition
处理了profile后就开始进行XML的读取解析,parseBeanDefinitions(root, this.delegate);
1 | public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { |
1 | public class BeanDefinitionParserDelegate { |
关于默认标签解析与自定义标签解析,重要内容见下篇。