最近在读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. angular+webpack(二)

    上篇文章Angular2开发基础之TSC编译 解决如何使用TSC来编译ng2项目,以及如何解决出现的error.这些点是新手容易忽视的内容, 要熟悉ng开发的工具链,还是需要掌握其中的重点.本篇文章是 ...

  2. swapper_pg_dir主内核页表、init和kthreadd、do_fork时新建子进程页表、vmalloc与kmalloc

    都是以前看到一个点扯出的很多东西,当时做的总结,有问题欢迎讨论,现在来源难寻,侵删! 1.Init_task.idle.init和kthreadd的区别和联系 idle进程其pid=0,其前身是系统创 ...

  3. linux 安装nginx+php+mysql

    http://www.cnblogs.com/kyuang/p/6801942.htmlnginx安装 本文是介绍使用源码编译安装,包括具体的编译参数信息. 正式开始前,编译环境gcc g++ 开发库 ...

  4. Linux NFS挂载

    Linux NFS挂载 一.NFS挂载 192.25.10.101/home/sharedata/azkaban/ODS_HS08 挂载到 192.25.10.102/home/data_azkaba ...

  5. jQuery 自执行函数

    jQuery 自执行函数 // 为了避免三方名冲突可将全局变量封装在自执行函数内 (function (arg) { var status = 1; arg.extend({ 'xsk': funct ...

  6. Python RabbitMQ消息队列

    python内的队列queue 线程 queue:不同线程交互,不能夸进程 进程 queue:只能用于父进程与子进程,或者同一父进程下的多个子进程,进行交互 注:不同的两个独立进程是不能交互的.   ...

  7. 2019年1月6日 没有nainai吃 习题1

    1列举布尔值是False的所有值 0,False,'',[],{},(),None 2根据范围获取其中3和7整除的所有数的和,并返回调用者:符合条件的数字个数以及符合条件的数字的总和 def func ...

  8. iOS开发 -------- Block技术中的weak - strong

    一 Block是什么? 我们使用^运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通变量一样,后面要加; 声明Block变量 int (^block)(int) = NULL ...

  9. P4726 【模板】多项式指数函数

    思路 按照式子计算即可 \[ F(x)=F_0(x)(1-\ln F_0(x) +A(x)) \] 代码 // luogu-judger-enable-o2 #include <cstdio&g ...

  10. P4312 [COCI 2009] OTOCI / 极地旅行社

    思路 LCT维护和的板子 注意findroot的时候要先access一下,修改点权之前要先splay到根 代码 #include <cstdio> #include <algorit ...