spring源码分析(二)- 容器基础
1.基本用法
用过Spring的都知道,bean是Spring中最基础也是最核心的。首先看一个简单的例子。
一个类和一个配置文件
package bean;
public class MyBean {
private String name = "test"; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}}
这就是实现Spring的bean最基本代码。然后写个测试类测试下
public class BeanFactoryTest {
@Test
public void test(){
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("application.xml"));
MyBean bean = (MyBean) bf.getBean("myBean");
Assert.assertEquals("test", bean.getName());
}
}
最后出现绿条。当然,这是个非常简单的例子,只有几行代码,整个流程分为3部:读取配置文件,根据配置实例化类,调用实例。但是在Spring中执行了很多的逻辑代码,我学习的是源码而不是去怎么使用它,相信有工作经验的人都会使用Spring。BeanFactory作为容器的话在企业级的应用中还是比较少见,一般都用ApplicationContext,之后再去学习他们的区别。
2.XmlBeanFactory
接下来详细分析下每个步骤的实现,深入的分析下功能代码实现:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("application.xml"));
很明显首先调用ClassPathResource的构造函数构造了一个Resource资源文件的对象,后续资源处理就可以用Resource提供各种的服务才操作,然后传入XmlBeanFactory中进行初始化。
封装Resource资源到底做了哪些操作?
2.1 配置文件封装
Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。
public interface InputStreamSource {
InputStream getInputStream() throws IOException; } public interface Resource extends InputStreamSource {
//存在
boolean exists();
//可读性
boolean isReadable();
//是否打开状态
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
//创建相对资源
Resource createRelative(String relativePath) throws IOException;
String getFilename();
//错误处理打印信息
String getDescription();<span style="white-space:pre"> </span> }
对于不同来源的资源都有相应的Resource实现:文件(FileSystemResource),ClassPath资源(ClassPathResource),URL资源(URLResource),InputStream资源(InputStreamResource)等。
以getInputStream为例,ClassPathResource中的实现方式通过class或者classLoader提供的底层方法进行调用,对于FileSystemResource直接用FileInputStream对文件进行实例化。
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
对配置文件进行封装成Resource后,就进行XMLBeanFactory的初始化了。查看构造函数:
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
this.reader.loadBeanDefinitions(resource);这句代码是资源加载的真正实现。首先先看super(parentBeanFactory);跟踪到
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能。引用别人的解释:
2.2加载bean
this.reader.loadBeanDefinitions(resource); 其中reader是XMLBeanFactory中的XmlBeanDefinitionReader对象,实现了个性化的BeanDefinitionReader读取。
主要功能就是对资源文件读取,解析和注册。进入loadBeanDefinitions方法:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));//对资源文件的编码进行处理,设置编码属性
}
EncodedResource的作用是对资源文件的编码进行处理。继续深入loadBeanDefinitions:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//通过属性来记录已经加载的资源
Set<encodedresource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<encodedresource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//核心逻辑部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}</encodedresource></encodedresource>
首先对传入的Resource参数做封装,目的是考虑对Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理方法doLoadBeanDefinitions。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);//获取对XML文件的验证模式
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());//加载XML文件,并得到对应的Document
return registerBeanDefinitions(doc, resource);//根据返回的Document注册Bean信息
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
无视掉异常代码,其实就只有三行是核心代码,做了三件事:
1.获取XML文件的验证模式
2.加载XML,并得到对应的Document
3.根据得到的Document注册Bean信息
3.XML的验证模式
包括2种:DTD和XSD。
DTD即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。
DTD 是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。 一个 DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
XML Schema语言也就是XSD。XML Schema描述了XML文档的结构。
可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML
Schema指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的。XML
Schema本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。
一个XML Schema会定义:文档中出现的元素、文档中出现的属性、子元素、子元素的数量、子元素的顺序、元素是否为空、元素和属性的数据类型、元素或属性的默认 和固定值。
XSD是DTD替代者的原因,一是据将来的条件可扩展,二是比DTD丰富和有用,三是用XML书写,四是支持数据类型,五是支持命名空间。
DTD和XSD相比:DTD 是使用非 XML 语法编写的。
DTD 不可扩展,不支持命名空间,只提供非常有限的数据类型 .
验证模式的读取:
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
自动检测:
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
//如果读取的行是空或者是注释则略过
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
//读取到DOCTYPR就是DTD,否则就是XSD
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//读取到<开始符号,验证模式一定会在开始符号之前
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
原理就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
4.获取Document
在loadDocument 方法中包含一个EntityResolver参数。
验证文件默认加载方式是通过URL进行网络下载获取,这样有延迟,用户体验不好,一般做法就是把验证文件放置在自己的工程里,问题是如何把URL转换为自己工程下对应的文件呢。
DelegatingEntityResolver.java
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
//如果是dtd 从这里解析
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
//通过调用META-inf/Spring.schemas 解析
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
对于不同的验证模式,Spring使用了不同的解析器解析。方式都比较简单,DTD直接截取systemId的最后xx.dtd,然后去当前路径下寻找,XSD默认到META-INF/Spring.schemas文件中找到systemid所对应的XSD文件并加载。
4.解析及注册BeanDefinitions
现在已经拿到Document。
XmlBeanDefinitionReader.java
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;
}
注意,createBeanDefinitionDocumentReader方法后documentReader已经是DefaultBeanDefinitionDocumentReader类型。
DefaultBeanDefinitionDocumentReader.java
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;
}
首先是对profile的出力,然后才开始解析。它的作用主要是可以同时在配置文件中部署两套配置来适用于生产和开发环境,方便切换环境,最常用的就是更换不同的数据库,具体百度。继续查看parseBeanDefinitions:
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);
}
}
代码看起来非常清晰,在Spring的XML配置中有两大类Bean声明,一个是默认的: 另一类就是自定义,例如:
这两种方式的读取和解析差别很大,默认的话有默认的方式,自定义的话需要用户实现一些接口和配置。具体详细的之后再分析。
转载于 https://www.2cto.com/kf/201605/510664.html
spring源码分析(二)- 容器基础的更多相关文章
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- SPRING源码分析:IOC容器
在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求: ...
- Spring源码分析(四)容器的基础XmlBeanFactory
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 经过Spring源码分析(二)容器基本用法和Spring源码分析(三)容 ...
- 【spring源码分析】IOC容器初始化——查漏补缺(二)
前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...
- 【spring源码分析】IOC容器初始化(二)
前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...
- 【spring源码分析】IOC容器初始化(三)
前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...
- Spring源码分析之IOC的三种常见用法及源码实现(二)
Spring源码分析之IOC的三种常见用法及源码实现(二) 回顾上文 我们研究的是 AnnotationConfigApplicationContext annotationConfigApplica ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- Spring源码分析——BeanFactory体系之抽象类、类分析(二)
上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...
- 【spring源码分析】IOC容器初始化(总结)
前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...
随机推荐
- 编写一个应用程序,利用数组或者集合, 求出"HELLO",“JAVA”,“PROGRAM”,“EXCEPTION”四个字符串的平均长度以及字符出现重复次数最多的字符串。
public class Number { public static void main(String[] args) { String[] arr = { "HELLO", & ...
- Selenium系列5-XPath路径表达式
Xpath介绍 XPath 使用路径表达式在 XML 文档中进行导航 XPath 使用路径表达式来选取 XML 文档中的节点或者节点集.这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似. ...
- 剑指offer计划19( 搜索与回溯算法中等)---java
1.1.题目1 剑指 Offer 64. 求1+2+-+n 1.2.解法 这题看评论区真的绝了,都是人才,各个说话都好听,我看到个还有用异常来结束的就离谱. 这题用了&&当左边为fal ...
- 小学生都能读懂的网络协议之:WebSocket
目录 简介 webSocket vs HTTP HTTP upgrade header websocket的优点 webScoket的应用 websocket的握手流程 WebSocket API 总 ...
- vue 学习资料
自学资料地址: https://zhuanlan.zhihu.com/p/26535530项目UI部分1.pc站 UI:(1)考虑自己写成本高,需要花费不少时间,好处是可以自己控制维护!(2)引入第三 ...
- javascript/html 禁止图片缓存
更新图片, 如果图片的url没有改变, 刷新页面之后图片会使用缓存的图片 Solutions: * js改变图片链接 (添加get参数) // 假设当前这个图片的dom对象为img img.src + ...
- python学习笔记(一)-基础知识
O.解释型语言和编译型语言 编译型语言就是先把写好的程序翻译成计算机语言然后执行,就是所谓的一次编译到处运行,比如c.c++就是编译型语言,这样的语言特点是运行速度快,但是需要事先把程序编译好才可以. ...
- appium和selenium不同与相同之处
原文来自: https://www.cnblogs.com/zhengshuheng/p/6370398.html selenium是web端的自动化,appium是app端的自动化,它继承了webd ...
- Linux下实现高可用软件-Keepalived基础知识梳理
Keepalived介绍 Keepalived软件起初是专门为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能.因此,Keepali ...
- CF1039D-You Are Given a Tree【根号分治,贪心】
正题 题目链接:https://www.luogu.com.cn/problem/CF1039D 题目大意 给出\(n\)个点的一棵树,然后对于\(k\in[1,n]\)求每次使用一条长度为\(k\) ...