抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

SpringIOC-准备工作

什么是spring

这个是一个显而易见的问题

​ Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。

​ Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。
​ 然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。

  • 目的:解决企业应用开发的复杂性
  • 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
  • 范围:任何Java应用

​ 它是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用。简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

模块

核心容器

​ 这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。这里最基本的概念是BeanFactory,它是任何Spring应用的核心。BeanFactory是工厂模式的一个实现,它使用IoC将应用配置和依赖说明从实际的应用代码中分离出来。

应用上下文(Context)模块

​ 核心模块的BeanFactory使Spring成为一个容器,而上下文模块使它成为一个框架。这个模块扩展了BeanFactory的概念,增加了对国际化(I18N)消息、事件传播以及验证的支持。

​ 另外,这个模块提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持。

Spring的AOP模块

​ Spring在它的AOP模块中提供了对面向切面编程的丰富支持。这个模块是在Spring应用中实现切面编程的基础。为了确保Spring与其它AOP框架的互用性,Spring的AOP支持基于AOP联盟定义的API。AOP联盟是一个开源项目,它的目标是通过定义一组共同的接口和组件来促进AOP的使用以及不同的AOP实现之间的互用性。通过访问他们的站点,你可以找到关于AOP联盟的更多内容。

​ Spring的AOP模块也将元数据编程引入了Spring。使用Spring的元数据支持,你可以为你的源代码增加注释,指示Spring在何处以及如何应用切面函数。

JDBC抽象和DAO模块

​ 使用JDBC经常导致大量的重复代码,取得连接、创建语句、处理结果集,然后关闭连接。Spring的JDBC和DAO模块抽取了这些重复代码,因此你可以保持你的数据库访问代码干净简洁,并且可以防止因关闭数据库资源失败而引起的问题。

​ 这个模块还在几种数据库服务器给出的错误消息之上建立了一个有意义的异常层。使你不用再试图破译神秘的私有的SQL错误消息!

​ 另外,这个模块还使用了Spring的AOP模块为Spring应用中的对象提供了事务管理服务。

对象/关系映射集成模块

​ 对那些更喜欢使用对象/关系映射工具而不是直接使用JDBC的人,Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射。Spring的事务管理支持这些ORM框架中的每一个也包括JDBC。

为什么阅读源码

  • 提高编程水平:看好的源代码,对于提高自己的编程水平,比自己写源代码的帮助更大。自己写代码的同时,可以从别人写的好的源代码中间学习到更多的编程方法和技巧

  • 提高架构能力:可以提高自己把握大规模源代码的能力。一个比较大型的程序,往往都是经过了很多个版本很长的时间,有很多人参与开发,修正错误,添加功能而发展起来的。所以往往源代码的规模都比较大,少则10-100多k, 多的有好几十个MB. 在阅读大量源代码的时候,能够提高自己对大的软件的把握能力,快速了解脉络,熟悉细节,不仅仅是编程技巧,还能在程序的架构,设计方面提高自己的能力。(设计模式并不是一本教材,不是教你如何去编程序,而是把平时编程中一些固定的模式记录下来,加以不断的测试和改进,分发给广大程序员的一些经验之谈。我在看这本书的时候,有一些地方一些设计方法往往让我有似曾相识的感觉,另外一些则是我以前就常常用到的。而这些经验的获得,一部分得益于自己的编码过程,另外一个很重要的来源就是阅读别人写的源代码。)

  • 扩展思维:就是获得一些好的思想。有很多人在开始一个软件项目之前都喜欢到sourceforge.net上去找一下,是否有人以前做过相同或者相似的软件,如果有,则拿下来读一读,可以使自己对这个软件项目有更多更深的认识。

  • 知其所以然:如果我们需要将一个开源项目用到自己的项目中,那么就必须了解这项项目的优缺点,并深知原理,对部分细节(尤其是项目的优势、feature)进行深入研究。如果是成熟的开源项目,遇到问题也许能google到很多答案;但如果是一个处于快速发展中的开源项目,多了解其架构、核心原理,也能帮助快速定位问题。

怎么阅读源码

看源码的目的很大程度上影响了看源码的方式、需要阅读的代码的范围。比如说,如果是为了修一个线上bug,那么阅读代码的范围就紧紧围绕bug本身;而如果是为了了解某个分布式算法,那就需要按大量的、可能运行在不同节点(进程)上的代码,了解其交互原理、工作流程。

先看文档,整体把握

​ 一般来说,文档是对代码的高度凝练,一个高质量的开源一般会包含tutorial、specification、API reference等documents,通过选择性的略读、精读这些文档,就能大致了解项目的整体架构、设计原则。正确的路线是通过文档去认识这个项目,然乎通过阅读代码去验证文档、深入细节,而不是通过直接啃源码来了解这个项目,以偏概全。

理解代码组织,文件名,类名

​ 当需要看代码的时候,不要找到一个文件就开始,先看看代码组织,粗略看看文件名、类名,基本就能猜测到每一部分。比如redis的源码就组织得很好,基本上看文件名就可以快速定位每一个command的实现位置。

关注一个问题,从问题追踪代码

​ 看源码的目标决定了此时此刻的关注点,不管是解决遇到的bug还是学习某个算法,都让我们聚焦到一个具体的问题,从这个具体的问题去追踪代码,忽略掉当前无需关注的细枝末节,步步深入,直达目标。当然在解决一个问题的时候,有可能会引发新的问题,尤其是学习的时候,此时只需记录新问题(放到收集篮,不要立即发散),待之前追踪的问题解决之后,再来看新发现的问题。

解决一个issue

​ 如果自己没有问题,那么就帮忙解决别人的问题,通常来说,开源项目都有许多待解决的issue,从中选择一个入手即可。

调试

​ 只要可以,一定先让代码编译通过、跑起来,这样不管是加log、打印调用栈还是断点调试都方便很多。不跑起来很难知道到底在干啥。

加注释,做笔记

​ 如果某份源代码的阅读并不是一锤子买卖,日后还可能回顾、重新阅读,那么就一定要做好代码注释和笔记。笔记主要是框架图、类图、流程图,目标是建立索引,方便日后快速回忆。而注释就是阅读代码时的细节,重新阅读的时候看注释(特别是函数的注释)能节省很多时间。

怎么阅读Spring源码

因为Spring的源代码量非常大。如果像阅读小说一样,一行不拉的读,是不现实的。所以,现实,而且有用的做法,是只阅读核心代码。

至于其他边支代码,如果通读,一则不现实,二则没有必要。

但因为Spring复杂,且设计精妙。初学者刚开始阅读,必然会面比较茫然,不知从那里入手。而我的做法,有一下几个关键点:

先掌握Spring的基本用法

​ 在阅读Spring源码之前,首先要掌握Spring的基本用法,否则就找不到基本的出发点。

​ 此时,你肯定不能完全理解Srping的运行机制。但随着使用的熟练,一则积累了经验,二则肯定也会有很多疑问。这不要紧,反而是好事。

​ 随着学习的深入。你要有自己思考:如果某个机制(例如IOC),由你来设计,该会如何实现?

写几个典型的Spring Demo

​ 因为你的目的是阅读Spring源码。所以如果一开始,就阅读相对高阶的SrpingMVC,需要理解的东西太多,必然会茫然不知所措。

​ 因为起始难度太大,缺乏正反馈。对很多人,甚至是直接就吓退了。

​ 所以,准备几个好的Demo,这些Demo一定要层层深入,从而作为自己阅读源码的出发点。

边调试,边阅读

​ 例如对下面这行最普通的Spring代码:

1
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

​ 直接用Maven下载源码,然后在Eclipse中Debug进去。此时,就像你平时阅读同事的源码一样。带着疑问,带着猜测。阅读的过程,边验证,边调整,总重形成体系的思路。

​ 记着,因为Spring源码复杂,且继承体系深,所以一定要边阅读,边记笔记,边画图。例如下面就是我画过的一个图:

​ 很重要的一点。在阅读Spring源码前,一定要先有自己的想法:如果这个东西(例如经典的getBean()方法)由你要设计,会怎么做。然后带着想法,跟(debug)进去。不断验证,不断调整。

不需面面俱到,只抓重点

因为Spring源码巨大。对阅读者来说,也不是都有价值,所以一定要抓住重点。

要时刻记住阅读的初心:不是为了阅读而阅读,而是为了深刻理解Spring,从而自由运用,所谓“无真相不自由”。所以只阅读核心的,对自己有用的(这个因人而异)。

Spring阅读的收益

通过阅读Spring源码,至少能有以下收获:

精通Java

​ 因为 Spirng框架本身,就是对Java最“精妙”的运用。我敢保证,在阅读Spring的过程,除了发现很多Java的“新”特性外。你也能学会“大神”们,是如何正确且高效的编写Java程序。

学习设计

​ Spring框架本身就是一个“设计良好”的典型,其中大量运用到经典的设计模式,例如“观察者模式”、“单例模式”等。因此学习Spring的过程,就是学习设计的过程。

深刻理解Spring用法

​ 所谓“无真相自由”。等你学习了Spring源码后。我敢保证,你回头再来看自己,或别人原来写的Spring程序,肯定会发现诸多“错误”。包括代码的冗余,冲突等。

阅读源码注意事项

​ 我在源码分析的位置会标注重要程度1-5 如果没有标注 说明不重要,标注的重要成都一次递增,5是非常重要的核心流程,阅读源码必须要掌握的。

Spring源码编译

下载地址

https://github.com/spring-projects/spring-framework.git

以 spring-framework-5.1.9.RELEASE 为例。在windows下,使用idea2019,gradle4.10,jdk1.8。

安装gradle

下载,解压后,设置环境变量。

1
2
3
GRADLE_HOME:D:\gradle-4.10

GRADLE_USER_HOME:D:\my_gradle_local_repository

添加Path: %GRADLE_HOME%\bin

cmd执行 gradle -v,检验是否安装成功。

导入工程

选择从git导入Spring源码,因为从github导入可能会持续时间较长,可以使用国内的镜像

为了构建加快速度,给出 vm 的优化参数:-Xmx2048m -XX:MaxPermSize=2048m -XX:MaxHeapSize=2048m 。

导入工程之后,点击gradle的刷新。此时的java源文件和gradle依赖,如果报错,这里暂时不管。

因为需要下载依赖,可能会花费比较长的时间可能会持续数个小时

配置阿里云镜像

因为spring源码需要的依赖文件很多,并且从中央仓库下载很慢所以一般修改build.gradle文件增加阿里云镜像来提高速度

然后主要要重启idea然后就会从阿里云下载镜像

源码编译

点击gradle的Refresh按钮

等待编译完成就可以,等到出现一下提示说明编译成功

源码导入项目

因为我们需要对源码进行调试,以及载源码上加注释所以需要将源码导入到测试项目中

创建一个Spring测试项目

POM依赖文件如下
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>


<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>LATEST</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>

</dependencies>

Spring源码打包

将需要调试的spring-beans,spring-context,spring-core进行打包

我们拿spring-beans举例其他都一样

然后查看打包生成的文件

在测试项目中添加jar包以及源码

将需要调试的spring-beans,spring-context,spring-core添加jar包以及源码

我们还拿spring-beans举例,其他一样

添加生成的jar

选择添加按钮

选择刚刚生成的spring-beans.jar

添加源码

继续点击+ 按钮添加spring-beans源码

点击确定

完成jar以及源码的添加,并删除多余的class

点击确定大功告成

在看下依赖的目录结构

其他的按照以上步骤完成就可以

添加注释

打开ClassPathXmlApplicationContext类 然后先随意写一些注释

注意:添加注释后需要找到相应的spring模块源码进行重新打包后才能进行代码debug

代码调试

重新打包后就可以尽心代码调试了

Spring目录结构

Spring源码目录结构如下

Spring框架是一种分层架构,它包含了一系列的功能,大概由20种模块组成。 这些模块分为核心容器(Core Container), 数据访问/集成(Data Access/Integration), Web, AOP, 工具(Instrumentation), 消息(Messaging), 测试用例(Test).

核心容器(Core Container)

spring-core 和 spring-beans

​ Spring 框架的核心模块,包含了控制反转(Inversion ofControl, IOC)和依赖注入(Dependency Injection, DI)。BeanFactory 接口是 Spring 框架中的核心接口,它是工厂模式的具体实现。BeanFactory 使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。但 BeanFactory 容器实例化后并不会自动实例化 Bean,只有当 Bean 被使用时 BeanFactory 容器才会对该 Bean 进行实例化与依赖关系的装配。

spring-context

​ 核心模块之上,他扩展了 BeanFactory,为她添加了 Bean 生命周期控制、框架事件体系以及资源加载透明化等功能。此外该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,她是 BeanFactory 的超类,与BeanFactory 不同,ApplicationContext 容器实例化后会自动对所有的单实例 Bean 进行实例化与依赖关系的装配,使之处于待用状态。

spring-context-support

​ 对 Spring IOC 容器的扩展支持,以及 IOC 子容器。

spring-context-indexer

​ Spring 的类管理组件和 Classpath 扫描。

spring-expression

​ 统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。它的语法类似于传统 EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。这种语言的特性是基于 Spring 产品的需求而设计,他可以非常方便地同 Spring IOC 进行交互。

AOP 和设备支持

spring-aop

​ Spring 的另一个核心模块,是 AOP 主要的实现模块。作为继 OOP 后,对程序员影响最大的编程思想之一,AOP 极大地开拓了人们对于编程的思路。在 Spring 中,他是以 JV 大专栏 Spring5源码分析(1)设计思想与结构M 的动态代理技术为基础,然后设计出了一系列的 AOP 横切实现,比如前置通知、返回通知、异常通知等,同时,Pointcut 接口来匹配切入点,可以使用现有的切入点来设计横切面,也可以扩展相关方法根据需求进行切入。

spring-aspects

​ 集成自 AspectJ 框架,主要是为 Spring AOP 提供多种 AOP 实现方法

spring-instrument

​ 是基于 JAVA SE 中的”java.lang.instrument”进行设计的,应该算是 AOP的一个支援模块,主要作用是在 JVM 启用时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现 AOP 的功能。在分类里,我把他分在了 AOP 模块下,在 Spring 官方文档里对这个地方也有点含糊不清,这里是纯个人看法。

数据访问与集成

spring-jdbc

​ Spring 提供的 JDBC 抽象框架的主要实现模块,用于简化 Spring JDBC 操作 。主要是提供 JDBC 模板方式、关系数据库对象化方式、SimpleJdbc 方式、事务管理来简化 JDBC 编程,主要实现类是 JdbcTemplate、SimpleJdbcTemplate 以及 NamedParameterJdbcTemplate。

spring-tx

​ Spring JDBC 事务控制实现模块。使用 Spring 框架,它对事务做了很好的封装,通过它的 AOP 配置,可以灵活的配置在任何一层;但是在很多的需求和应用,直接使用 JDBC 事务控制还是有其优势的。其实,事务是以业务逻辑为基础的;一个完整的业务应该对应业务层里的一个方法;如果业务操作失败,则整个事务回滚;所以,事务控制是绝对应该放在业务层的;但是,持久层的设计则应该遵循一个很重要的原则:保证操作的原子性,即持久层里的每个方法都应该是不可以分割的。所以,在使用 Spring JDBC 事务控制时,应该注意其特殊性。

spring-orm

​ ORM 框架支持模块,主要集成 Hibernate, Java Persistence API (JPA) 和Java Data Objects (JDO) 用于资源管理、数据访问对象(DAO)的实现和事务策略。

spring-oxm

​ 主要提供一个抽象层以支撑 OXM(OXM 是 Object-to-XML-Mapping 的缩写,它是一个 O/M-mapper,将 java 对象映射成 XML 数据,或者将 XML 数据映射成 java 对象),例如:JAXB, Castor, XMLBeans, JiBX 和 XStream 等。

spring-jms

​ (Java Messaging Service)能够发送和接收信息,自 Spring Framework 4.1 以后,他还提供了对 spring-messaging 模块的支撑。

Web组件

spring-web

​ Spring 提供了最基础 Web 支持,主要建立于核心容器之上,通过 Servlet 或者 Listeners 来初始化 IOC 容器,也包含一些与 Web 相关的支持。

spring-webmvc

​ 众所周知是一个的 Web-Servlet 模块,实现了 Spring MVC(model-view-Controller)的 Web 应用。spring-websocket 模块主要是与 Web 前端的全双工通讯的协议。

spring-webflux

​ Spring WebFlux是Spring Framework 5.0中引入的新的反应式Web框架。 与Spring MVC不同,它不需要Servlet API,完全异步和非阻塞, 并通过Reactor项目实现Reactive Streams规范。 并且可以在诸如Netty,Undertow和Servlet 3.1+容器的服务器上运行。

spring-websocket

​ WebSocket是HTML5提出的一个用于通信的协议规范,该协议通过一个握手机制,在客户端和服务端之间建立一个类似于TCP的连接,从而方便客户端和服务端之间的通信。

​ WebSocket协议本质上是一个基于TCP的协议,是先通过HTTP/HTTPS协议发起一条特殊的HTTP请求进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。客户端和服务端只需要要做一个握手的动作,在建立连接之后,服务端和客户端之间就可以通过此TCP连接进行实时通信。

​ websocket是建立在物理层上的连接,相比于基于网络层的长连接可以节约资源,相比于AJAX轮训可以降低服务器压力。

​ spring在4.0后将websocket集成进去,要使用spring的websocket的话,spring的版本要在4.0及以上。

通信报文

spring-messaging

​ 从 Spring4 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

集成测试

spring-test

​ 主要为测试提供支持的,毕竟在不需要发布(程序)到你的应用服务器或者连接到其他企业设施的情况下能够执行一些集成测试或者其他测试对于任何企业都是非常重要的。

集成兼容
spring-framework-bom

​ Bill of Materials.解决 Spring 的不同模块依赖版本不同问题

各模块关系图

Spring 版本命名规则

描述方式 说明 含义
Snapshot 快照版 尚不不稳定、尚处于开发中的版本
Release 稳定版 功能相对稳定,可以对外发行,但有时间限制
GA 正式版 代表广泛可用的稳定版(General Availability)
M 里程碑版 (M 是 Milestone 的意思)具有一些全新的功能或是具有里程碑意义的版本。
RC 终测版 Release Candidate(最终测试),即将作为正式版发布。

评论