最近在读Spring的源码,参考的是郝佳的《Spring源码深度解析》,这里把一些学习心得分享一下,总结的地方可能还有一些不完善,希望大家指教

IoC(控制反转)是Spring的特性之一,在传统的程序中,对象的生成往往是有开发者自己来完成的,而在Spring中,这一工作交由了Spring框架完成。Spring容器可以帮助我们管理所有的bean。这样的好处是减少了对象之间的耦合。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id = "myFirstBean" class="com.wuzhe.bean.TestBean" />
</beans>

在上述代码中我们定义了对于一个简单的bean的声明。Spring为我们提供了丰富的属性来支持我们各种业务的需求,但只要我们声明成这样就足够满足我们大多数的应用了。

为了能在程序中使用这个在xml声明的bean,我们可以通过如下方式:

 BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));
TestBean bean = (TestBean) bf.getBean("myFirstBean");

当然在我们的程序中直接使用BeanFactory作为容器其实并不多见,我们绝大多数情况下会使用ApplicationContext作为Spring的容器,这不妨碍我们分析它的原理

这段代码实现的功能实际上就是如下三点:

1、读取配置文件

2、根据配置文件中的信息,找到对应的类的配置,并实例化

3、调用实例化后的类

对于源码的阅读,书的作者提供了一个好的方法,就是配合UML图去分析,我觉得这个方法挺好。

1、我们先从读取配置文件来看。

Spring的配置文件是通过ClassPathResource进行封装的。

inputStreamSource 封装了所有能返回InputStream的类,Resource接口则抽象了所有资源的一些公共特性,提供了如下的接口

    boolean exists();

    default boolean isReadable() {
return true;
} default boolean isOpen() {
return false;
} default boolean isFile() {
return false;
} URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
} long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String var1) throws IOException; @Nullable
String getFilename(); String getDescription();

对于不同来源的资源文件都有相应的Resource实现。我觉得在Spring框架的源码中对于已有类进行封装特别的常见。这实际上也是符合我们面对对象编程的对修改封闭,对扩展开放的这个原则。同时通过一层层的封装,把原本复杂的逻辑分摊到每一层去实现,这样更符合单一职责的设计理念,提高了可读性。

封装好了配置信息,XmlBeanFactory首先会对封装后的ClassPathResource进行读取

 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}

配置文件的读取工作交给了XmlBeanDefinitionReader来处理,通过调用XmlBeanDefinitionReader的loadBeanDefinitions(Resource resource)方法来加载资源

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}

在这里对于Resource又进行了一层EncodedResource封装,主要是设置了Resource资源的编码和字符集

在看loadBeanDefinitions(EncodedResource encodedResource)的代码

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
} Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
} if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var5;
try {
InputStream inputStream = encodedResource.getResource().getInputStream(); try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
} var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
} } return var5;
}
}

在XmlBeanDefinitionReader中维护了一套当前线程中已加载资源的集合resourcesCurrentlyBeingLoaded,真正的核心处理部分在doLoadBeanDefinitions方法中

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);
} catch (BeanDefinitionStoreException var4) {
throw var4;
} catch (SAXParseException var5) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
} catch (SAXException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
} catch (ParserConfigurationException var7) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
} catch (IOException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
} catch (Throwable var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
}
}

在doLoadDocument方法中使用了DefaultDocumentLoader来加载资源,对生成的Document进行校验和解析。

拿到了转换后的Document后,接下来spring会对注册对应的BeanDefinition

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

在上面这段代码中,spring将处理Document的逻辑委托给了BeanDefinitionDocumentReader来处理,具体逻辑在 registerBeanDefinitions中

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
}

这里获取了Document的根节点,开头的配置文件中对应的也就是beans节点。继续深入

protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if(this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if(StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
} this.preProcessXml(root);
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root);
this.delegate = parent;
}

这里首先对根节点的profile属性做了处理,profile属性实际上对应了我们的开发环境,通常企业的应用会有多种环境,比如生产环境,测试环境,如果想在同一套配置中维护多套环境,就可以用到这个参数,最常用的就是更换不同环境的数据库。在解析时会判断当前的环境和profile属性是否一致,若不一致则不用去解析当前的节点。

处理完profile属性后,到了真正解析BeanDefinition的阶段了,在这里用到了设计模式的模板方法,将整个解析过程抽象成了三个阶段解析前,解析中,解析后。这样做的好处是方便扩展,如果你想要做扩展的话,只需要继承当前类,并覆盖一下模板方法即可。

我们继续跟踪代码到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)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
} }

spring的配置文件中总共有两种标签,一种是默认标签,还有一种是自定义标签。对于两种标签,spring的解析方式是不同的,默认标签采用的是parseDefaultElement方法,而自定义标签采用的是parseCustomElement方法。

spring判断标签是否默认是通过标签的命名空间来判断的。对于默认标签来说,它的命名空间就是"http://www.springframework.org/schema/beans"。

关于默认标签的解析会在后续继续介绍。

Spring 源码学习(1)—— 容器的基本实现的更多相关文章

  1. 创建ApplicationContext与BeanFactory时的区别-Spring源码学习之容器的基本实现

    传送门 可以加载XML两种方法 使用 BeanFactory 加载 XML BeanFactory bf = new XmlBeanFactory(new ClassPathResource(&quo ...

  2. spring源码学习之容器的基本实现

    最近想拿出一部分时间来学习一下spring的源码,还特意买了一本书结合来看,当然主要是学习并跟着作者的思路来踏上学习spring的源码的道路,特意在此记录一下,<spring源码深度解析> ...

  3. Spring源码学习之容器的基本实现(一)

    前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...

  4. spring源码学习之容器的扩展(二)

    六 BeanFactory的后处理BeanFactory作为spring容器的基础,用于存放所有已经加载的bean,为了保证程序上的高扩展性,spring针对BeanFactory做了大量的扩展,比如 ...

  5. spring源码学习之容器的扩展(一)

    在前面的章节,我们一直以BeanFactory接口以及它的默认实现XmlBeanFactory为例进行解析,但是,spring还提供了另一个接口ApplicationContext,用于扩展BeanF ...

  6. Spring 源码学习(一)-容器的基础结构

    关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料 展示的代码摘取了一些核心方法,去掉一些默认设置和日志输出,还有大多数错误异常也去掉了,小伙伴想看详细代码 ...

  7. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  8. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  9. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

  10. Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

    写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...

随机推荐

  1. Android Studio 添加已经移除的Module

    Android Studio 删除Module时,需要先在Project Structure中点击“-”来移除,此时小手机图标消失,但是这个时候Module并没有在硬盘中删除,只是和这个Project ...

  2. Apriori

    基本概念 项与项集:设itemset={item1, item_2, …, item_m}是所有项的集合,其中,item_k(k=1,2,…,m)成为项.项的集合称为项集(itemset),包含k个项 ...

  3. 零基础快速入门web学习路线(含视频教程)

    下面小编专门为广大web学习爱好者汇总了一条完整的自学线路:零基础快速入门web学习路线(含视频教程)(绝对纯干货)适合初学者的最新WEB前端学习路线汇总! 在当下来说web前端开发工程师可谓是高福利 ...

  4. 鼠标右键vsCode打开

    有时候在安装vscode不会出现鼠标右键用vscode打开的情况: 最终要实现的样子: 解决办法如下步骤: 1.安装vscode: 2.新建一个“.reg”为后缀的文件: 3.把下面内容复制到文件中: ...

  5. vs2017 exe在Linux上运行

    1:将vs .netcore控制台项目发布打包(比如文件名为:demo2core.zip,以下会用到) 2:使用XShell软件连接Linux a.在linux上使用命令  id addr找出ip地址 ...

  6. Redis学习-set数据结构

    set 是无序集合,最大可以包含(2 的 32 次方-1)个元素.set 的是通过 hash table 实现的, 所以添加,删除,查找的复杂度都是 O(1) sadd key member 添加一个 ...

  7. Docker Kubernetes YAML文件常用指令

    YAML文件常用指令 配置文件说明: 定义配置时,指定最新稳定版API(当前为v1). 配置文件应该存储在集群之外的版本控制仓库中.如果需要,可以快速回滚配置.重新创建和恢复. 应该使用YAML格式编 ...

  8. HADOOP HA 踩坑 - org.apache.hadoop.hdfs.qjournal.protocol.JournalNotFormattedException: Journal Storage Directory /mnt/data1/hadoop/dfs/journal/hdfscluster not formatted

    报错:在journalnode的log中: org.apache.hadoop.hdfs.qjournal.protocol.JournalNotFormattedException: Journal ...

  9. linux使用代理进行apt安装 以 nord 为例

    我的环境:(不必完全一样,只是提一下)----------- linux系统:kali 桌面:xface ----------------------------------------------- ...

  10. Windows 用bat脚本带配置启动redis,并用vb脚本使其在后台运行。

    最近,在Windows上用开发PHP程序,需要用到Redis,每天要打开一个运行redis-server.exe的窗口这样比较烦,因为窗口就一直打开着,不能关闭. 所以就想着通过写脚本的方式,让他在后 ...