以通过BeanFactory获取bean为例学习,实际项目中更多是使用ApplicationContext

img

1.读取配置文件 beanFactoryTest.xml

2.通过文件资源获取工厂

3.从工厂中拿bean

读配置

配置文件封装

ClassPathResource类 继承–AbstractFileResolvingResource

​ 继承–AbstractResource

​ 实现–Resource

​ 继承–InputStreamSource接口 唯一方法: getInputStream()

Resource接口提供了一系列方法来封装底层资源,比如 exists() isReadable() getURL() getFile() getDescription() 等等

XmlBeanFactory的实例化

大概步骤

1
2
3
4
5
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);//1.设置父工厂(null)...本例未使用到此
this.reader = new XmlBeanDefinitionReader(this);//2.实例化XmlBeanDefinitionReader, 注册到此工厂
this.reader.loadBeanDefinitions(resource);//3.重要部分,通过resource来加载beanDefinations,bean的一些定义
}

loadBeanDefinitions(resource)方法就是下面的主要要看的

它做了几件事:

  1. 将resource用EncodedResource包装一下,编码要用, 本例没特定的charset和encoding因此都是null
  2. 获取当前正在加载的资源集 Set currentResources ,如果currentResources 是null 就初始化个 new HashSet(4), 然后将我们要读的encodedResource加到这个set里面去

如果返回false说明正在加载这个xml了,报个错,不继续往下读了

当读完这个资源后,在finally里面会从set里移出这个encodedResource,如果set空了,还会remove这个currentResources,

这个set是放ThreadLocal里面的,

private final ThreadLocal> resourcesCurrentlyBeingLoaded;

是线程安全的,功能是防止同时循环加载同一个xml文件

  1. 从encodedResouce里取出文件的inputStream 拿去初始化个InputSource(有enconding的话,给inputSource也设个编码,本例为null)然后 inputSource和encodedResouce里面的resouce一起拿去执行doLoadBeanDefinitions(..)方法,稍后重点讲
1
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  1. 关闭资源, 释放currentResources 等

关键:doLoadBeanDefinitions

将资源转为Document

1
Document doc = this.doLoadDocument(inputSource, resource);
  1. 获取xml的验证模式 , 里面的核心是自动检测,自动检测的核心是拿着inputStream读,判断是否含有”DOCTYPE”,包含就是DTD,没有就是XSD
  2. 拿到EntityResolver , 这个主要是用来给xml验证时找DTD文件获取路径找得快的, 不用全去spring网站拿dtd,略
  3. 解析document, 创建DocumentBuilderFactory,通过此工厂创建DocumentBuilder,进而解析inputSource来返回document对象. 此为通过SAX(Simple API for XML)来解析的常见方法,略过

注意到本方法的参数中又用到resource, 明明inputSource就是从resource里拿inputStream生成出来的啊, 相当于inputSource就是儿子, doLoadDocument方法请了儿子又去请爹,烦不烦?

其实主要是之前设计的时候, 获取XML验证模式的自动检测方法,用的是Resource接口,isOpen()方法因此不得不用

isOpen()方法是Resource接口的方法,其实现返回false(比如AbstractResource),有的返回的是true(InputStreamResource).

注册bean

1
registerBeanDefinitions(doc, resource);

先走流程

1
2
3
4
5
6
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
  1. 创建阅读器documentReader
  2. 记录加载BeanDefinition的个数,
  3. documentReader来加载注册doc和封装resource而生成的Context

继续走流程, 在reader内设置context,拿到root元素,重点在最后解析root

1
2
3
4
5
6
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
}
开始核心部分 doRegisterBeanDefinitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  protected void doRegisterBeanDefinitions(Element root) {
/*
基于原本的detegate,
加上这里的context和root(XML内容)修饰一遍,作为此处要用的解析代理ParserDelegate
方法结束时,会把原来的解析器置换回来
*/
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
//如果命名空间为空或者等于"http://www.springframework.org/schema/beans" 就校验里面的一些属性
if (this.delegate.isDefaultNamespace(root)) {
//如果有profile属性,就要去环境变量中去寻找
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
//如果不是当前环境变量的属性值,就跳过这个xml文件
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}

return;
}
}
}
//解析前的预处理 默认实现方法是空的..预留给子类进行改造的位置~~~
this.preProcessXml(root);
//核心解析
this.parseBeanDefinitions(root, this.delegate);
//解析后的处理 默认实现方法是空的..预留给子类进行改造的位置~~~
this.postProcessXml(root);
//将delegate换回去
this.delegate = parent;
}

this.preProcessXml(root); this.postProcessXml(root);此处的预留 设计模式—模板方法模式

profile

关于profile属性书中讲用途讲的很清楚,从源码中我了解到

  1. profile可以同时指定多个属性,比如 只需当前环境变量里值满足其中一种就OK

  2. 多个属性可以支持多种分隔符 逗号 分号 空格 源码是

    1
    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
  3. 可以用一个感叹号修饰属性 比如 表示”非” , 不可多个感叹号

  4. 读取并设置spring.profiles.active属性时还加了sychronized

  5. spring.profiles.active属性值可以多个 ,用逗号隔开

  6. 判断过程其实是

    1
    return currentActiveProfiles.contains(profile) || currentActiveProfiles.isEmpty() && this.doGetDefaultProfiles().contains(profile);

    spring.profiles.active里包含这个属性, 或者spring.profiles.active为空,但是spring.profiles.default里包含这个属性

ps.这个属性看明白很有成就感,书上没解释源码, 看到这段之前先是自己埋着头进去钻源码,莫名其妙卡了很久,并不知道整段的功能是啥,继续看书看到此处,了解了这个profile 的功能,再回头结合源码,就恍然大悟:)

判断是不是默认命名空间

1
2
3
4
5
6
7
public boolean isDefaultNamespace(String namespaceUri) {
return !StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri);
}

public boolean isDefaultNamespace(Node node) {
return this.isDefaultNamespace(this.getNamespaceURI(node));
}

node获取命名空间方法 getNamespaceURI()主要在 rt.jar里有很多种不同的实现,返回字符串,就不再去深究了

再深一层 parseBeanDefinitions

不贴源码了,这个方法主要是先判断这个节点是不是默认命名空间 ,不是就按自定义的方式解析,

是的话就遍历节点,解析每个节点.

每个节点解析之前都还会去判断一遍是否默认命名空间,

如果不是就按自定义方式解析,如果是就才按默认方式解析

默认标签如

1
<bean id="test" class="test.testBean">

自定义标签如

1
<tx:annotation-driven/>

好,书的第二章到此为止,第三章将继续深入默认标签的解析