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源码分析(二)- 容器基础的更多相关文章

  1. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  2. SPRING源码分析:IOC容器

    在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求: ...

  3. Spring源码分析(四)容器的基础XmlBeanFactory

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 经过Spring源码分析(二)容器基本用法和Spring源码分析(三)容 ...

  4. 【spring源码分析】IOC容器初始化——查漏补缺(二)

    前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...

  5. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  6. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

  7. Spring源码分析之IOC的三种常见用法及源码实现(二)

    Spring源码分析之IOC的三种常见用法及源码实现(二) 回顾上文 我们研究的是 AnnotationConfigApplicationContext annotationConfigApplica ...

  8. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  9. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  10. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

随机推荐

  1. SQL-Instead of 触发器

    定义及优点 INSTEAD OF触发器指定执行触发器而不是执行触发 的SQL 语句,从而替代触发语句的操作.        在表或视图上,每个 INSERT.UPDATE 或 DELETE 语句最多可 ...

  2. ClientValidationFunction

    CustomValidator.ClientValidationFunction 属性 获取或设置用于验证的自定义客户端脚本函数的名称. 命名空间:   System.Web.UI.WebContro ...

  3. MySQL数据库初体验

    一.数据库的基本概念1.数据(Data) 描述事物的符号记录 包括数字,文字,图形,图像,声音,档案记录等 以"记录"形式按统一的格式进行存储 2.表 将不同的记录组织在一起 用来 ...

  4. 【AGC025B】RGB Color

    [AGC025B]RGB Color 题面描述 Link to Atcoder Link to Luogu Takahashi has a tower which is divided into \( ...

  5. POJ3061——Subsequence(尺取法)

    Subsequence POJ - 3061 给定长度为n的数列整数a0,a1,a2-an-1以及整数S.求出总和不小于S的连续子序列的长度的最小值,如果解不存在输出0. 反复推进区间的开头和末尾,来 ...

  6. Android学习记录(二)——第一次hello world及遇到的gradle安装问题

    开始一个简单的hello world项目,简单了解Android studio的使用方法 第一步,打开Android studio,点击Create New Project 第二步,选择需要的模板 T ...

  7. 用tcping检查网站开放的端口

    麦新杰之前分享过一款小巧玲珑工具软件:tcping,即在tcp层进行端口的ping. tcping可以用来检查和确认我们的网站有哪些端口是开放的,使用很顺手.比如麦新杰这几天在研究如何关闭mysql的 ...

  8. Docker系类(25)- 发布镜像到DockerHub

    # step-1 注册账号 https://hub.docker.com/# step-2 在服务器尚提交我们的镜像[root@localhost WEB-INF]# docker login --h ...

  9. iPhone发布内测程序的方法

    iPhone是封闭系统,不像android手机可以自行安装apk,所以iPhone手机发布内测程序相对来说复杂一些. 越狱安装 如果测试用户的机器已经越狱,那就简单了,直接打包成ipa,用户直接通过9 ...

  10. 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 百篇博客分析OpenHarmony源码 | v4.05

    百篇博客系列篇.本篇为: v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...