Spring 简介

图片描述

前言

“Spring 真的那么有用吗?”

首先 Spring 可谓是大名鼎鼎,如雷贯耳。而关于 Spring 的文章、书籍、教程更是数不胜数。可以说 Spring 贯穿我们的整个职业生涯,是框架界的常青树。那么我们这个专题专门来聊一聊: 到底 Spring 是什么,它的特点优势是什么,我们项目的开发为什么选择 Spring,它能帮助我们解决哪些问题?

带着疑问,开始我们的专题。

Spring 概述

先搞清楚 Spring 的概念:
官网定义

Spring 框架为任何类型的部署平台上的基于 Java 的现代企业应用程序提供了全面的编程和配置模型。

Spring 的一个关键元素是在应用程序级别的基础架构支持:Spring 专注于企业应用程序的 “管道”,以便团队可以专注于应用程序级别的业务逻辑,而不必与特定的部署环境建立不必要的联系。

通俗解释

简单来说:Spring 是一个免费开源框架,为了简化企业级项目开发,提供全面的开发部署解决方案。

疑问导出

看到这儿,我们明白了一件事:Spring 是帮助我们开发项目的,使用起来很方便。

那么问题来了:Spring 为了简化项目开发到底做了哪些事情?

知识入门

Spring 核心功能

Spring 到底如何简化我们的项目开发呢?首先,来了解下 Spring 的体系结构。

Spring 的体系结构介绍

图片描述
结构图阐释

  1. 左上角勾画出负责持久层的部分,是 Spring 对数据持久化,事务管理,支持的功能框架。大家听过的 SpringDataJpa 就是其中的一种;
  2. 右上角勾画出是负责表现层的部分,是 Spring 对于表现层数据的处理部分的支持,比如:大家听说过的 SpirngMVC 就是其中的一种;
  3. 最底部的负责测试的部分 是 Spring 对于项目的测试 提供了完整的一个测试环境支持;
  4. 而中间的两部分 是我们大家常常俗称的 Spring 框架。

疑问导出

看到这里大家可能会明白一点, Spring 其实是一个 “大家族”。从表现层、业务层、持久层,它都有对应的支持,而我们在框架学习的部分其实主要是使用了它中间的两个部分的核心功能。

那么,Spring 核心功能到底是什么呢?

Spring 的核心功能

大家对于使用 Spring 框架开发项目已经司空见惯了… 但是对于它的功能或者作用,描述出来总是差点什么,那么现在咱们详细聊一聊它的核心功能。

核心功能:

  1. 控制反转(IoC): 简单理解 IoC 是一种设计模式,将实例化对象的控制权 由手动的 new 变成了 Spring 框架通过反射机制实例化;
  2. 依赖注入(DI): 首先理解依赖,程序运行的需要可以称之为依赖。由于 Spring 框架通过反射技术实例化了对象,并将对象的实例存入在容器进行管理。那么如果一个类中的属性为某个其余的类,属性无需手动赋值,通过 spring 的配置文件,或者 Spring 提供的注解,通过 spring 框架可以实现直接注入属性;
  3. 面向切面编程 (AOP): 何谓切面,切面是数学中的一个概念,表示只有一个点接触到球体的一个平面称呼为切面,而接触点称呼为切点。那么在 Spring 中,切面编程指的就是在程序运行某个方法的时候,不修改原始执行代码逻辑,由程序动态地执行某些额外的功能,对原有的方法做增强,这就叫做面向切面编程,那个被监测的执行方法,称呼为切入点。

知识小结

Spring 是分层的 Java SE/EE 应用 轻量级开源框架,以 IoC(Inverse of Control:控制反转)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库, 是使用最多的 Java EE 企业应用开源框架。

使用 Spring 的意义在于:对于 bean 对象的实例管理更加方便,代码编写更加优雅,降低代码的耦合性,提升代码的扩展性。

Spring 的优势

Spring 的概念和功能了解以后,下面谈谈它的优势在哪

  • Spring 简化项目开发 : Spring 灵活全面的扩展功能,使我们开发项目如鱼得水 。通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用;
  • Spring 的面向切面编程 :Spirng 框架的 AOP 面向切面编程,极大地提高了程序的扩展性,支持开发人员实现对程序的自定义增强。同时可以方便地使用 Spring 提供的事务管理;
  • 面向接口编程: 面向接口编程 降低代码的耦合性,同时也提高了代码的扩展性;
  • 测试方便:对于测试的支持 有很多的组件实现;
  • 方便集成第三方框架 Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。

小结

本节主要对于 Spring 框架做了入门介绍,通过本节的学习,我们应该知道以下几点:

  1. Spring 框架的概念;
  2. Spring 框架的意义;
  3. Spring 框架的体系结构;
  4. Spring 框架的核心功能;
  5. Spring 框架的优势;

Spring 工程的搭建

前言

“Spring 的工程如何创建?”

在上一节中我们通过 Spring 的简介,了解了 Spring 的概念、体系结构、与它的核心功能。那么本章带你体验一下 Spring 的项目开发和我们之前搭建过的开发项目有哪些不同。

Spring 框架版本介绍与依赖引入

版本历史
Spring 诞生到现在经历太多的历史版本,有的已经消失在历史长河中了… 我们选择最新的版本给大家进行案例讲解。

  • 5.2.x 是最新的生产线(通常于 2019 年 9 月下旬提供);
  • 5.1.x 是之前的生产线(自 2018 年 9 月以来),一直得到积极支持,直到 2020 年底;
  • 5.0.x 于 2019 年第二季度进入 EOL 阶段。出于对 5.0.x 用户的礼貌,我们将在 2020 年 1 月之前提供主动维护,并在 2020 年底之前提供安全补丁(如果需要);
  • 4.3.x 是第四代的最后一个功能分支。它具有延长的维护期限,直到 2020 年 1 月,并且安全补丁甚至超过了这一点。4.3.x 将于 2020 年 12 月 31 日达到其正式停产(停产);
  • 截至 2016 年 12 月 31 日,3.2.x 属于产品停产(寿命终止)。该产品线中没有计划进一步的维护版本和安全补丁。请尽快迁移到 4.3 或 5.x。

我们建议从 Maven Central 升级到最新的 Spring Framework 5.2.x 版本。

以上是官网列出 Spring 的历史版本介绍,我们采用的是 5.2.2 版本,对应的 jdk 最少是 jdk 1.8 ,我相信大家的 jdk 一般来讲都是满足要求的。

Spring 框架源码下载

下载方式

  1. 下载源码文件 。
    Spring 的源码下载地址 :
    https://github.com/spring-projects/spring-framework/releases
  2. 第二种是使用 maven 的坐标方式 。
    maven 的 pom 文件坐标。
1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>

工程创建

准备好依赖之后 废话不多说,我们开始撸代码

使用 IDEA 创建 Web 工程

开发工具选择 idea ,创建 Maven 的 jar 工程即可。因为涉及不到浏览器的请求,所以无需创建 web 工程。

创建 Maven 工程 。

补全坐标信息。

继续下一步 finish 完成创建即可。

引入项目使用的坐标依赖

将准备好的坐标信息粘贴到工程下 pom 文件中 。 看下图:

编写 Spring 框架使用的配置文件

坐标有了之后,说明我们的工程中已经引入了 Spring 框架的依赖。点开左侧的 External Libraries 查看一下 。

那么看到上面的 jar 包列表,表示 Spring 框架中的基本依赖我们已经成功引入。接下来:既然我们使用的是框架,框架是一个半成品,已经封装好了很多功能提供我们使用,而我们如何让他们工作呢? 这里需要一个和 Spirng 框架通信的桥梁 —Spring 框架的核心配置文件。

小提示
文件的名称可以随便起,一般习惯使用 applicationContext.xml
文件的位置放在哪里呢? maven 工程需要放在 src 下面的 resources 下面,如下图:

那么配置文件是空的,不要着急。到底应该配置什么,不是自己臆想猜测的。
如果你已经下载了源码,那么解压缩它,打开 docs\spring-framework-reference 目录,打开 core.html 查看官方文档,
下图:
图片描述
将上面的实例配置信息拷贝到我们的配置文件中,它只是给了最基本的配置头信息,内容部分 针对 bean 做初始化的部分 需要我们自行填充 。

1
2
3
4
5
6
7
8
<?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">


</beans>

编写代码测试

准备好工程后,编写我们的代码。

编写接口和接口的实现类

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//接口的代码
public interface UserService {

public void saveUser();
}
//实现类的代码
public class UserServiceImpl implements UserService {

public void saveUser() {
System.out.println("service的save方法执行了");
}
}

补充 Spring 的配置文件

配置文件的目的是将我们自定义的实现类交给 Spring 的容器管理。因为 Spring 框架核心功能之一就是 IoC 控制反转,目的是将对象实例化的动作交给容器。还记得第一节介绍的吗?不记得了?走你,剩下的我们继续。最终 Spring 的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

<!-- 此标签的作用 是实例化UserServiceImpl类的实例 交给 Spring 容器 -->
<bean id="userService" class="com.wyan.service.impl.UserServiceImpl"></bean>
</beans>

测试结果

从容器中获取对象实例,调用提供的方法

1
2
3
4
5
6
7
8
9
public class DemoTest {

public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.saveUser();
}
}

解释:

  1. ApplicationContext 是 Spring 框架提供的一个接口,目前只需要知道它是作为存储实例化 bean 对象的容器即可。下一节我们会细讲。
  2. context.getBean () 方法是通过配置文件中声明的 bean 标签 id 属性获取容器内的实例。

最终结果如下:
图片描述
可以看到控制台打印输出 证明确实从容器中获取到了 userService 的实例。入门就是如此简单…

小结

技术之路很简单 一是思路步骤清晰,二就是代码的熟练度。
先理清入门示例的步骤 :

  1. 创建 Maven 工程;
  2. 导入 Spring 的依赖;
  3. 编写 Spring 的配置文件;
  4. 编写测试的代码。

Spring 工程执行过程

前言

Spring 框架是如何工作的?

本节目的在于帮助大家理解 Spring 框架底层干了什么事情。

在上一节中我们通过一个入门工程简单地体验了一把 Spring 的使用。

我们发现,通过构造一个 ClassPathXmlApplicationContext 对象,加载项目的 applicationContext.xml 文件,确实可以实例化对象。

疑问导出

而脑海中不禁有一个想法… Spring 如何初始化对象的实例的?我们又如何从容器中获取得到对象的实例的呢?

带着疑问… 开启本节的源码和原理之旅。

容器初始化

回顾代码:

1
2
3
4
5
6
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.saveUser();
}

在上面的代码中可以得知 Spring 的容器是 ApplicationContext,那么它到底是什么东西呢?先跟我一起追踪一下它的角色。
官方文档
图片描述
通俗解释

简单翻译过来就是 ApplicationContext 是一个接口,是 BeanFactory 这个接口的子接口,它扩展了 BeanFactory 这个接口,提供了额外附加的功能。
BeanFactory 是管理 bean 对象的容器的根接口,大家了解下就好,我们是针对它的子接口 ClassPathXmlApplicationContext 做的实例化,目的是加载项目中的 Spring 的配置文件,使 Spring 来管理我们定义的 bean 对象。

疑问导出
那么我们的问题是…ClassPathXmlApplicationContext 对象实例化之后,干了哪些事情呢?

容器初始化执行动作

applicationContext 实例化执行代码逻辑
我们追踪下源码,发现 ClassPathXmlApplicationContext 初始化的时候,它做了一系列的事情。源码如下:

图片描述

代码解释:

  1. 是初始化 ClassPathXmlApplicationContext 对象执行的有参构造;
  2. 加载项目下的 xml 配置文件;
  3. 调用 refresh 刷新容器的方法 bean 的实例化就在这个方法中。

继续跟踪:

容器初始化 bean 对象动作

下面是从源码中粘贴的部分代码

图片描述
步骤阐述:

  1. 1 的位置:是准备刷新,那么 Spring 只是设置刷新的标记,加载了外部的 properties 属性文件;
  2. 2 的位置:是准备 bean 工厂对象;
  3. 3 的位置:这一步骤就加载了配置文件中的所有 bean 标签,但是并没有对他们进行实例化;
  4. 4 的位置:完成此上下文的 bean 工厂的初始化,初始化所有剩余的单例 bean。(Spring 中默认加载的 bean 就是单例模式后面生命周期会讲)
  5. 最后的位置:完成容器的刷新,也就是所有的 bean 初始化完成。
1
2
3
4
5
6
7
8
//这里粘贴一部分初始化代码的逻辑 帮助大家理解
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
// Trigger initialization of all non-lazy singleton beans...
//所有非懒加载的单例bean的触发器初始化。。。
for (String beanName : beanNames) {
...//省略循环的代码
}

OK 上面就是加载配置文件后 Spring 框架做的所有事情,当然实际底层涉及的东西 更多,但是我们没有必要深究,毕竟我们是理解过程,不是追求实现。

疑问导出:

我们整理了 Spring 初始化 bean 对象的过程,那么如果容器中确实存在了 bean 的实例,我们是如何获取得到的呢?

容器中获取对象的过程

还是先看下我们获取容器对象的代码:

1
2
3
4
5
6
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.saveUser();
}

代码分析

context.getBean 的方法是通过 bean 标签里的 id 来从容器中获取,那么我们看下源码 :
在父类 AbstractApplicationContext 中有对 getBean 方法的实现。

1
2
3
4
5
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}

追踪父类方法
最终通过我们层层追踪,我们在 AbstractAutowireCapableBeanFactory 中发现这样的一段代码:

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
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
//...
//省略大量方法内部代码
//...
// Initialize the bean instance.
Object exposedObject = bean;
try {
//给实例中的属性赋值
populateBean(beanName, mbd, instanceWrapper);
//真实实例化对象
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//...
//继续省略大量方法
//...
// Register bean as disposable.
try {
//将实例化后的对象放入容器中
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
//返回实例化后的对象实例
return exposedObject;
}

上面源码中我们可以看到: 对象实例的获取好像是在获取的时候执行的 doCreateBean,那么之前记载的 xml 文件不是实例过了吗?稍微解释下:加载文件时候的实例化操作,其实是实例化了一个 Spring 框架提供的对象,作用是对于我们 bean 对象做描述,这里才是真实的实例化动作。我们再看看 registerDisposableBeanIfNecessary 这个方法做的是什么。

1
2
3
4
5
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}

图片描述

结论
一切真相大白。它其实就是一个 map 集合 ,这个 map 集合的 key 就是我们定义的 bean 的 id 或者 bean 的 name ,那么值就是对象的实例。

小结

本节带着大家梳理了一下 Spring 初始化 bean 和获取 bean 的流程:

  1. Spring 框架通过 ResourceLoader 加载项目的 xml 配置文件;
  2. 读取 xml 的配置信息 变成对象存储,但未实例化;
  3. 通过 bean 工厂处理器对 bean 做实例化,存储到一个 map 集合中默认是单例;
  4. 获取对象 通过 xml 文件中 bean 的 id 从 map 集合中通过 get (key) 获取。