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

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)方法就是下面的主要要看的
它做了几件事:
- 将resource用EncodedResource包装一下,编码要用, 本例没特定的charset和encoding因此都是null
- 获取当前正在加载的资源集 Set currentResources ,如果currentResources 是null 就初始化个 new HashSet(4), 然后将我们要读的encodedResource加到这个set里面去
如果返回false说明正在加载这个xml了,报个错,不继续往下读了
当读完这个资源后,在finally里面会从set里移出这个encodedResource,如果set空了,还会remove这个currentResources,
这个set是放ThreadLocal里面的,
private final ThreadLocal<Set> resourcesCurrentlyBeingLoaded;
是线程安全的,功能是防止同时循环加载同一个xml文件
- 从encodedResouce里取出文件的inputStream 拿去初始化个InputSource(有enconding的话,给inputSource也设个编码,本例为null)然后 inputSource和encodedResouce里面的resouce一起拿去执行doLoadBeanDefinitions(..)方法,稍后重点讲
1
| var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
|
- 关闭资源, 释放currentResources 等
#
关键:doLoadBeanDefinitions
#
将资源转为Document
1
| Document doc = this.doLoadDocument(inputSource, resource);
|
- 获取xml的验证模式 , 里面的核心是自动检测,自动检测的核心是拿着inputStream读,判断是否含有"DOCTYPE",包含就是DTD,没有就是XSD
- 拿到EntityResolver , 这个主要是用来给xml验证时找DTD文件获取路径找得快的, 不用全去spring网站拿dtd,略
- 解析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;
}
|
- 创建阅读器documentReader
- 记录加载BeanDefinition的个数,
- 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属性书中讲用途讲的很清楚,从源码中我了解到
profile可以同时指定多个属性,比如 只需当前环境变量里值满足其中一种就OK
多个属性可以支持多种分隔符 逗号 分号 空格 源码是
1
| String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
|
可以用一个感叹号修饰属性 比如 表示"非" , 不可多个感叹号
读取并设置spring.profiles.active属性时还加了sychronized
spring.profiles.active属性值可以多个 ,用逗号隔开
判断过程其实是
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/>
|
好,书的第二章到此为止,第三章将继续深入默认标签的解析