XmlBeanFactory
- xmlBeanFactory被表明为Deprecated. 推荐使用DefaultListableBeanFactory和XmlBeanDefinitionReader替换。
- XmlBeanFactory继承自DefaultListableBeanFactory,扩展了从xml文档中读取bean definition的能力。XmlBeanFactory是硬编码(hard coded)的方式,不利与扩展。从本质上讲,XmlBeanFactory等同于DefaultListableBeanFactory+XmlBeanDefinitionReader ,如果有更好的需求,可以考虑使用DefaultListableBeanFactory+XmlBeanDefinitionReader方案,因为该方案可以从多个xml文件读取资源,并且在解析xml上具有更灵活的可配置性。
1 | public void testXmlBeanFactory(){ |
2 | Resource resource = new ClassPathResource("com.zbcn.test/BeanFactoryTest.xml"); |
3 | BeanFactory beanFactory = new XmlBeanFactory(resource); |
4 | TestBean testBean = beanFactory.getBean("test", TestBean.class); |
5 | Assert.notNull(testBean); |
6 | } |
Resource 配置文件
spring 的配置文件是通过ClassPathResource 进行封装的。ClasspathResource 完成的共 能:
- 将不同的资源抽象成不同的url,通过注册不同的handler(UrlStreamHandler) 来处理不同来源的资源的读取逻辑。
- handler 的类型使用不同的前缀(协议,protocol)来识别。如:“file:”;“http:”;“jar:” 等。
- url 没有默认定义相对 classpath 或者ServletContext 等资源的handler,虽然可以注册自己的URLStreamHandler 来解析特定的url 前缀。比如:“classpath:”,然而,这要了解URL 的实现机制。而且URL也没有提供一些基本的方法。如:检查资源是否存在,检查资源是否可读等。因此,spring对其内部使用到的资源,实现了自己的抽象结构:Resource 接口,开封装底层的资源。
1 | public interface Resource extends InputStreamSource { |
2 | // 存在性 |
3 | boolean exists(); |
4 | //可读性 |
5 | default boolean isReadable() { |
6 | return exists(); |
7 | } |
8 | // 是否处于打开状态 |
9 | default boolean isOpen() { |
10 | return false; |
11 | } |
12 | default boolean isFile() { |
13 | return false; |
14 | } |
15 | |
16 | URL getURL() throws IOException; |
17 | |
18 | URI getURI() throws IOException; |
19 | |
20 | File getFile() throws IOException; |
21 | |
22 | default ReadableByteChannel readableChannel() throws IOException { |
23 | return Channels.newChannel(getInputStream()); |
24 | } |
25 | long contentLength() throws IOException; |
26 | // 最后修改时间 |
27 | long lastModified() throws IOException; |
28 | //创建相对资源 |
29 | Resource createRelative(String relativePath) throws IOException; |
30 | // 不带路径的文件名 |
31 | |
32 | String getFilename(); |
33 | // 在错误处理中的打印信息 |
34 | String getDescription(); |
35 | |
36 | } |
在日常开发中如果想加载资源文件,可以用:
1 | Resource resource = new ClassPathResource("BeanFactoryTest.xml"); |
2 | InputStream inputStream = resource.getInputStream(); |
得到 inputstream
后,正常操作即可。
实现原理:
- ClassPathResource 是通过class 或者classLoader 提供的底层方法来实现的
1
//ClassPathResource
2
3
public InputStream getInputStream() throws IOException {
4
InputStream is;
5
if (this.clazz != null) {
6
is = this.clazz.getResourceAsStream(this.path);
7
}
8
else if (this.classLoader != null) {
9
is = this.classLoader.getResourceAsStream(this.path);
10
}
11
else {
12
is = ClassLoader.getSystemResourceAsStream(this.path);
13
}
14
if (is == null) {
15
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
16
}
17
return is;
18
}
- FileSystemResource 是通过 调用FileInputStream 来实现的。
1
2
public InputStream getInputStream() throws IOException {
3
try {
4
return Files.newInputStream(this.filePath);
5
}
6
catch (NoSuchFileException ex) {
7
throw new FileNotFoundException(ex.getMessage());
8
}
9
}
XmlBeanDefinitionReader
从Resource 完成了配置文件的封装和获取后,文件的读取工作就完全交给 XmlBeanDefinitionReader了。
- XmlBeanFactory 方法分析:
1
public class XmlBeanFactory extends DefaultListableBeanFactory {
2
3
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
4
/**
5
* Create a new XmlBeanFactory with the given resource,
6
* which must be parsable using DOM.
7
* @param resource the XML resource to load bean definitions from
8
* @throws BeansException in case of loading or parsing errors
9
*/
10
public XmlBeanFactory(Resource resource) throws BeansException {
11
this(resource, null);
12
}
13
/**
14
* Create a new XmlBeanFactory with the given input stream,
15
* which must be parsable using DOM.
16
* @param resource the XML resource to load bean definitions from
17
* @param parentBeanFactory parent bean factory 用于父类合并,可以为空
18
* @throws BeansException in case of loading or parsing errors
19
*/
20
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
21
super(parentBeanFactory);
22
this.reader.loadBeanDefinitions(resource);
23
}
24
}
super(parentBeanFactory);
以上代码调用如下1
class AbstractAutowireCapableBeanFactory{
2
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
3
this();
4
setParentBeanFactory(parentBeanFactory);
5
}
6
public AbstractAutowireCapableBeanFactory() {
7
super();
8
// 忽略给定接口的自动装配功能
9
ignoreDependencyInterface(BeanNameAware.class);
10
ignoreDependencyInterface(BeanFactoryAware.class);
11
ignoreDependencyInterface(BeanClassLoaderAware.class);
12
}
13
}
ignoreDependencyInterface
方法的作用是: 忽略给定接口的自动装配功能。
目的: 自动装配时,忽略给定的依赖接口,典型的应用是通过其他方式解析Application上下文注册依赖。类似与BeanFactory 通过BeanFactoryAware 进行注入或者ApplicationContext 通过 ApplicatonContextAware 进行注入。
eg:当A 中有属性B,当Spring 在获取A 的bean 时,如果B还没有初始化,则先初始化B,但是在某些情况下B也是不会被初始化的。其中一种情况是B实现了BeanNameAware 接口。
this.reader.loadBeanDefinitions(resource);
资源加载的真正实现
加载Bean
对
this.reader.loadBeanDefinitions(resource)
分析
资源加载及准备
解析document,regisitBeanDefinition以上是XmlBeanDefinitionReader解析.xml 文件之前啊的准备工作。
- 封装资源: 使用EncodeResource
- 从Resource 获取输入流InputStream,并获取对应的InputResource。
- 通过Resource 实例和InputResource实例来调用doLoadDefinition 方法。
数据准备阶段:
- 对传入的resource 资源进行封装(EncodedResource),目的是考虑到Resource可能存在编码要求的情况。
- 通过SAX读取XML文件的方式来准备InputResource对象
- 将钟被的数据通过参数传入真正的核心处理部分 doLoadBeanDefinitions(inputResource,EncodedResource.getResource())
对 doLoadBeanDefinitions 分析
1
//XmlBeanDefinitionReader extends AbstractBeanDefinitionReader
2
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource){
3
Document doc = doLoadDocument(inputSource, resource);
4
return registerBeanDefinitions(doc, resource);
5
6
}
7
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
8
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
9
getValidationModeForResource(resource), isNamespaceAware());
10
}
11
// 通过指定的resource获取校验模型
12
protected int getValidationModeForResource(Resource resource) {
13
int validationModeToUse = getValidationMode();
14
if (validationModeToUse != VALIDATION_AUTO) {
15
return validationModeToUse;
16
}
17
int detectedMode = detectValidationMode(resource);
18
if (detectedMode != VALIDATION_AUTO) {
19
return detectedMode;
20
}
21
// Hmm, we didn't get a clear indication... Let's assume XSD,
22
// since apparently no DTD declaration has been found up until
23
// detection stopped (before finding the document's root tag).
24
return VALIDATION_XSD;
25
}
上面代码中均删除了异常,主要功能:
- 获取对xml文件的验证模式
- 加载xml文件,并得到对应的document
- 根据document 注册bean信息(BeanDefinitions)
xml的验证模式
xml 的验证模式可以保证xml文件的正确性。比较常用的验证模式有两种:DTD 和XSD.
DTD
- DTD (Document Type Definition) 文档类型定义。是一种XML约束模式语言,是xml文件的验证机制,属于xml文件组成的一部分。
- DTD 是一种保证XML文件格式正确性的有效办法,可以比较XML文档和DTD文件来看文档是否符合规范,元素和标签是否使用正确。
- DTD 文档包含:元素的定义规则,元素间关系的定义规则,元素的可使用属性,可使用的实体符合规则。
- 要使用DTD 验证模式的时候需要在XML 文件的头部声明。
spring 配置文件DTD头部声明:
1 |
|
2 |
|
3 | <beans> |
4 | </beans> |
XSD
(XML Schemas Definition)XML Schema 语言。XML Schema 描述了XML 文档的结构。可以用xml schema来验证某个xml文档,以检查某个xml文件是否符合要求。
- 文档设计者可以通过 xml schame 指定一个xml 文档所允许的结构和内容。并据此检查文档是否符合其要求。
- XML schema 本身十一个xml文档,它符合xml语法结构,可以通过xml解析器来解析它
- 使用xml schema对文档进行检查的要求:
xmlns
:申明命名空间xmlns="http://www.springframework.org/schema/beans"
- 命名空间: 我们可以为元素定义一个命名空间, 将一个很长的, 可以保证全局唯一性的字符串与该元素关联起来。这样就可以避免命名冲突了
xmlns:xsi
: 在不同的 xml 文档中似乎都会出现。 这是因为 xsi 已经成为了一个业界默认的用于 XSD((XML Schema Definition) 文件的命名空间。 而 XSD 文件(也常常称为 Schema 文件)是用来定义 xml 文档结构的。xsi:schemaLocation
: 指定 命名空间所对应的xml schame文档的存储位置 :包含两部分:1
xsi:schemaLocation="http://www.springframework.org/schema/beans
2
http://www.springframework.org/schema/beans/spring-beans.2.5.xsd"
- 名称空间的uri
- 该名称空间所标识的xml Schema 文件标识的存储位置或者url地址
spring 配置文件XSD头部声明:
1 |
|
2 | <beans xmlns="http://www.springframework.org/schema/beans" |
3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
4 | xsi:schemaLocation="http://www.springframework.org/schema/beans |
5 | http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> |
6 | </beans> |
验证模式获取:
spring 通过 getValidationModeForResource
方法来获取验证模式
1 | //XmlBeanDefinitionReader extends AbstractBeanDefinitionReader |
2 | // 通过指定的resource获取校验模型 |
3 | protected int getValidationModeForResource(Resource resource) { |
4 | int validationModeToUse = getValidationMode(); |
5 | // 如果手动指定了验证模式,则使用手动指定 |
6 | if (validationModeToUse != VALIDATION_AUTO) { |
7 | return validationModeToUse; |
8 | } |
9 | // 为指定则自动检测 |
10 | int detectedMode = detectValidationMode(resource); |
11 | if (detectedMode != VALIDATION_AUTO) { |
12 | return detectedMode; |
13 | } |
14 | // Hmm, we didn't get a clear indication... Let's assume XSD, |
15 | // since apparently no DTD declaration has been found up until |
16 | // detection stopped (before finding the document's root tag). |
17 | return VALIDATION_XSD; |
18 | } |
获取Document
最后一步准备工作是Document加载
1 | //XmlBeanDefinitionReader extends AbstractBeanDefinitionReader |
2 | protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource){ |
3 | Document doc = doLoadDocument(inputSource, resource); |
4 | return registerBeanDefinitions(doc, resource); |
5 | } |
6 | // DefaultDocumentLoader implements DocumentLoader |
7 |
|
8 | public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, |
9 | ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { |
10 | |
11 | DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); |
12 | if (logger.isDebugEnabled()) { |
13 | logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); |
14 | } |
15 | DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); |
16 | return builder.parse(inputSource); |
17 | } |
EntityResolver
- 如果SAX需要实现自定义外部实体,则必须实现此接口使用setEntityResolver 方法想SAX驱动器注册一个实例。
- 对解析一个xml,SAX 首先对取该xml文档上的声明,根据申明取寻找响应的DTD定义,以便于对文档进行验证。默认的规则是通过网络(实现上就是通过声明DTD的url地址)来下载一个DTD申明,并进行认证。当网络不通,这里会报错,原因是DTD申明没有被找到。
- EntityResolver 的作用就是项目本身就可以提供一个寻找DTD申明的方法,由程序来实现寻找DTD申明的过程,我们可以将dtd 文件本地的某个项目中,实现本地读取DTD文件直接返回给SAX。
1 | public interface EntityResolver { |
2 | public abstract InputSource resolveEntity (String publicId, |
3 | String systemId) |
4 | throws SAXException, IOException; |
5 | |
6 | } |
- 声明XSD 验证模式:可以取到的参数:
1
2
<beans xmlns="http://www.springframework.org/schema/beans"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://www.springframework.org/schema/beans
5
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
6
</beans>
- publicId:null
- systemId:http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- 声明DTD 验证模式:可以取到的参数:
1
<?xml version="1.0" encoding="UTF-8"?>
2
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
3
<beans>
4
</beans>
- publicId:-//SPRING//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/dtd/spring-beans-2.0.dtd
- spring 中的验证文件都是放置在自己的工程中的,具体的实现如下:
1
//DelegatingEntityResolver
2
3
4
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
5
if (systemId != null) {
6
if (systemId.endsWith(DTD_SUFFIX)) {
7
return this.dtdResolver.resolveEntity(publicId, systemId);
8
}
9
else if (systemId.endsWith(XSD_SUFFIX)) {
10
return this.schemaResolver.resolveEntity(publicId, systemId);
11
}
12
}
13
return null;
14
}
解析及注册BeanDefinitions
1
// XmlBeanDefinitionReader
2
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
3
//获取DefautBeanDefinitionDocumentReader
4
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
5
//统计之前注册的BeanDefinition的数量
6
int countBefore = getRegistry().getBeanDefinitionCount();
7
// 加载及注册BeanDefinition的核心方法
8
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
9
// 获取本次加载的BeanDefinition的数量
10
return getRegistry().getBeanDefinitionCount() - countBefore;
11
}
12
//DefautBeanDefinitionDocumentReader
13
14
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
15
this.readerContext = readerContext;
16
logger.debug("Loading bean definitions");
17
Element root = doc.getDocumentElement();
18
doRegisterBeanDefinitions(root);
19
}
20
/**
21
* Register each bean definition within the given root {@code <beans/>} element.
22
*/
23
"deprecation") // for Environment.acceptsProfiles(String...) (
24
protected void doRegisterBeanDefinitions(Element root) {
25
// Any nested <beans> elements will cause recursion in this method. In
26
// order to propagate and preserve <beans> default-* attributes correctly,
27
// keep track of the current (parent) delegate, which may be null. Create
28
// the new (child) delegate with a reference to the parent for fallback purposes,
29
// then ultimately reset this.delegate back to its original (parent) reference.
30
// this behavior emulates a stack of delegates without actually necessitating one.
31
// 专门处理解析
32
BeanDefinitionParserDelegate parent = this.delegate;
33
this.delegate = createDelegate(getReaderContext(), root, parent);
34
35
if (this.delegate.isDefaultNamespace(root)) {
36
//处理profile属性
37
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
38
if (StringUtils.hasText(profileSpec)) {
39
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
40
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
41
// We cannot use Profiles.of(...) since profile expressions are not supported
42
// in XML config. See SPR-12458 for details.
43
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
44
if (logger.isInfoEnabled()) {
45
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
46
"] not matching: " + getReaderContext().getResource());
47
}
48
return;
49
}
50
}
51
}
52
//空方法,解析xml前处理,面向继承设计。
53
preProcessXml(root);
54
parseBeanDefinitions(root, this.delegate);
55
//空方法,解析xml后处理,面向继承设计。
56
postProcessXml(root);
57
this.delegate = parent;
58
}
解析并注册 BeanDefinition
1 | //DefaultBeanDefinitionDocumentReader |
2 | /** |
3 | * Parse the elements at the root level in the document: |
4 | * "import", "alias", "bean". |
5 | * @param root the DOM root element of the document |
6 | */ |
7 | protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { |
8 | if (delegate.isDefaultNamespace(root)) { |
9 | NodeList nl = root.getChildNodes(); |
10 | for (int i = 0; i < nl.getLength(); i++) { |
11 | Node node = nl.item(i); |
12 | if (node instanceof Element) { |
13 | Element ele = (Element) node; |
14 | if (delegate.isDefaultNamespace(ele)) { |
15 | parseDefaultElement(ele, delegate); |
16 | } |
17 | else { |
18 | delegate.parseCustomElement(ele); |
19 | } |
20 | } |
21 | } |
22 | } |
23 | else { |
24 | delegate.parseCustomElement(root); |
25 | } |
26 | } |
- spring 对xml 的解析分为两类
- spring默认的:
<bean id="test" class = "test.TestBean"\>
parseDefaultElement(ele, delegate);
- 另一类就是自定义的, 如:
<tx:annotation-driver/>
delegate.parseCustomElement(ele);
- spring默认的:
- 两种解析方式差别特别大。 如果是spirng默认配置,spirng 解析方式内置,如果是用户自定医的,就需要用户实现一些接口及配置了。