- 什么是 Spring
- Spring 的整体架构
- 什么是 IoC
- Bean 的概念
- Spring 的基本使用
- Spring 的两种 IoC 容器
- Spring 容器的基本工作原理
- Spring Bean 的注册与装配
- Spring Bean 的作用域与生命周期
什么是 Spring
Spring 是一个轻量级的企业级应用开发框架,它于2004年发布第一个版本,其目的是用于简化企业级应用程序的开发。
在传统应用程序开发中,一个完整的应用是由一组相互协作的对象组成,开发一个应用除了要开发业务逻辑之外,更多的是关注如何使这些对象协作来完成所需功能,而且要高内聚,低耦合。虽然一些设计模式可以帮我们达到这个目的,可是这又徒增了我们的负担。如果能通过配置的方式来创建对象,管理对象之间依赖关系,那么就能够减少许多工作,提高开发效率。Spring 框架主要就是来完成这个功能的。
Spring 框架除了帮我们管理对象及其依赖关系之外,还提供了面向切面编程的能力,在此基础上,允许我们对一些通用任务如日志记录、安全控制、异常处理等进行集中式管理,还能帮我们管理最头疼的数据库事务。此外,它还提供了粘合其他框架的能力,使我们可以方便地与各种第三方框架进行集成,而且不管是 Java SE 应用程序还是 JavaEE 应用程序都可以使用这个平台进行开发。
Spring 是基于 IoC 和 AOP 两大思想理论衍生而来的,可以说,Spring是一个同时实现了 IoC 和 AOP 的框架。
Spring 的整体架构
Spring 的整体架构如图所示:
核心模块只有3个:Beans、Core、和 Context ,它们构建起了整个 Spring 的骨架,没有它们就不可能有 AOP、Web 等上层的特性功能。如果在它们3个中选出一个最核心的模块的话,那就非 Beans 模块莫属了,其实 Spring 就是面向 Bean 的编程(BOP, Bean Oriented Programming),Bean 在 Spring 中才是真正的主角。关于 Bean 的观念,会在后面进行介绍。
什么是 IoC
IoC(Inverse Of Control,控制反转)是一种设计思想,目标是实现解耦。所谓控制反转,是指对资源的控制方式反转了。这里说的资源主要指我们的业务对象,对象之间往往会存在某种依赖关系,当一个对象依赖另一个对象时,传统的做法是在它内部直接 new 一个出来,即由对象自己负责创建并管理它所依赖的资源,这是传统的对资源的控制方式。IoC 就是将其颠倒过来,对象由主动控制所需资源变成了被动接受,由第三方(IoC 容器)对资源进行集中管理,对象需要什么资源就从IoC容器中取,或者让容器主动将所需资源注入进来。
IoC 之后,对象与所需资源之间不再具有强耦合性,资源可以被直接替换,而无需改动需求方的代码。举个例子,董事长需要一个秘书,传统的做法是,董事长自己去指定一个秘书,控制权在他自己手上,但是这会导致他与秘书之间的耦合性较强,一旦想换秘书了,就得修改自己的代码。IoC 的做法是,董事长声明自己需要一个秘书,由IoC 容器为他指定一个秘书,至于是哪个秘书,男的还是女的,一切由容器说了算,如果要换秘书,也是修改容器的配置文件,与董事长无关,这样就实现了两者间的解耦。
IoC 的两种实现方式:
- DI(Dependency Injection,依赖注入)。所谓依赖注入,是指对象所依赖的资源通过被动注入的方式得到,换言之,容器会主动地根据预先配置的依赖关系将资源注入进来。
- DL(Dependency Lookup,依赖查找)。依赖查找是早先的一种 IoC 实现方式,现已过时。对象需要调用容器的API查找它所依赖的资源。
Bean 的概念
- 在 Java 中,“Bean”是对用Java语言编写的“可重用组件”的惯用叫法。可以从字面意思去理解,Java 原本指爪哇咖啡,bean 指咖啡豆,而咖啡的“可重用组件”就是咖啡豆嘛。官方并没有说明所谓的“组件”具体指的是什么,因为“组件”本身就是一个抽象的概念,是对软件组成部分的抽象,因此,Bean作为可重用组件的代称,既可指类,也可指对象。
- 在 Spring 中,Bean 的概念同上,有时也称 Component。 由 Spring 的 IoC容器所管理的 Bean 称作 Spring Bean。
- 扩展:
Java Bean 的概念不同于 Bean,Java Bean 是指符合 JavaBeans 规范的一种特殊的 Bean,即:所有属性均为 private,提供 getter 和 setter,提供默认构造方法。JavaBean 也可以认为是遵循特定约定的POJO。
POJO(Plain Ordinary Java Object)是指简单和普通的 Java 对象。严格来说,它不继承类,不实现接口,不处理业务逻辑,仅用于封装数据。
Spring 的基本使用
首先配置 Bean 信息,向 Spring 的 IoC 容器(或简称 Spring 容器)中注册 Bean。以 XML 方式为例,如下配置了两个 Bean,其中第一个依赖第二个:
然后创建 Spring 容器,同时绑定配置文件。如下:
ApplicationContext container = new ClassPathXmlApplicationContext("bean-config.xml");
然后通过容器的 getBean 方法即可得到我们在配置文件中所配置的 Bean 的实例。如下:
Person John = container.getBean("John");
Spring 的两种 IoC 容器
Spring 提供了两种 IoC 容器: BeanFactory 和 ApplicationContext 。
- BeanFactory 提供基本的 IoC 服务支持。
- ApplicationContext 对 BeanFactory 进行了扩展与增强,除了拥有 BeanFactory 的所有能力外,还提供了许多高级特性,如事件发布、资源加载、国际化消息等。ApplicationContext 接口继承了 BeanFactory 接口,它的实现类中也是直接复用了 BeanFactory,因此可以说,ApplicationContext 是 BeanFactory 的增强版。
两者在核心功能上的区别主要是默认的加载策略不同,这点区别几乎可以忽略不计,通常情况下,我们总是使用更为强大的 ApplicationContext,很少会直接使用 BeanFactory。
以下是几个最常用的 ApplicationContext 实现类:
- ClassPathXmlApplicationContext
- AnnotationConfigApplicationContext
- AnnotationConfigWebApplicationContext
Spring 容器的基本工作原理
既然是容器,那它最底层肯定是一个数据结构。通过跟踪 getBean 方法,我们发现它是从一个叫作 singletonObjects 的 Map 集合中获取 Bean 实例的。singletonObjects 的定义如下:
可以断定,它就是 Spring 容器的核心,凡是作用域为单例的 Bean 的实例都保存在该 Map 集合中,我把它称之为单例池。
那么 getBean 方法做了哪些事情呢?
getBean 方法首先会从单例池中获取 Bean 实例,如果取到了就直接返回,否则,如果有父容器,尝试从父容器中获取,如果也没获取到,则创建实例。创建实例之前先确保该 Bean 所依赖的 Bean 全部初始化,然后,如果是原型 Bean,创建好实例后直接返回,如果是单例 Bean,创建好实例后将其放进单例池,然后再从单例池中获取并返回。
当 Spring 容器被创建时,它又是如何完成初始化的呢?
以 ClassPathXmlApplicationContext
为例,它的构造方法主要做的事情就是调用 refresh() 方法。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备好自己
prepareRefresh();
// 创建并初始化BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备好要使用的BeanFactory
prepareBeanFactory(beanFactory);
try {
// 对BeanFactory进行后置处理
postProcessBeanFactory(beanFactory);
// 调用BeanFactory的后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册Bean的后置处理器
registerBeanPostProcessors(beanFactory);
// 初始化消息源
initMessageSource();
// 初始化事件多播器
initApplicationEventMulticaster();
// 初始化其他特殊的bean
onRefresh();
// 检测并注册监听器Bean
registerListeners();
// 实例化其余所有(非懒加载)的单例Bean
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布相应的事件
finishRefresh();
refresh() 方法的主要执行流程:
- 调用
refreshBeanFactory()
方法,该方法会首先新建或重建 BeanFactory 对象,然后使用相应的BeanDefinitionReader
读取并解析 Bean 定义信息,将每个 Bean 定义信息依次封装成BeanDefinition
对象,并将这些BeanDefinition
对象注册到BeanDefinitionRegistery
。这一步,完成了 BeanFactory 的创建,以及 Bean 定义信息的加载。 - 配置 BeanFactory,对 BeanFactory 做一些后置处理,注册 Bean 的后置处理器,初始化消息源和事件多播器,注册监听器等。这一步,完成了 Spring 容器的配置工作。
- 调用
finishBeanFactoryInitialization()
方法,该方法会遍历之前注册到BeanDefinitionRegistery
中的所有BeanDefinition
,依次实例化那些非抽象非懒加载的单例 Bean,并将其加入单例池。这一步,完成了 Bean 的实例化。
Spring 容器的几个核心类:
DefaultListableBeanFactory
是一个通用的BeanFactory
实现类,它还同时实现了BeanDefinitionRegistry
接口。从 ApplicationContext 实现类的源码中可以看到,在它内部维护着一个DefaultListableBeanFactory
的实例,所有的 IoC 服务都委托给该 BeanFactory 实例来执行。BeanDefinitionRegistry
负责维护BeanDefinition
实例,该接口主要定义了registerBeanDefinition()
、getBeanDefinition()
等方法,用于注册和获取 Bean 信息。BeanDefinition
用于封装 Bean 的信息,包括类名、是否是单例Bean、构造方法参数等信息。每一个 Spring Bean 都会有一个BeanDefinition
的实例与之相对应。BeanDefinitionReader
负责读取相应配置文件中的内容并将其映射到BeanDefinition
,然后将映射后的BeanDefinition
实例注册到BeanDefinitionRegistry
,由BeanDefinitionRegistry
来保管它们。
Spring Bean 的注册与装配
个人理解,注册与装配是不同的两个过程。注册指的是将 Bean 纳入 IoC 容器。装配指的是建立 Bean 之间的依赖关系。
Bean 的注册方式有以下三种:
- 在 XML文件中配置
- 在 JavaConfig 中配置
- 使用
@ComponentScan
、@Component
等注解进行配置
Bean 的装配分为手动装配和自动装配。
手动装配同样有三种方式:
- 在 XML文件 中配置
- 在 JavaConfig 中配置
- 使用
@Resource
等注解来配置。- 这种方式既可以算作手动装配,也可以算作自动装配。当我们在 @Resource 注解中明确指定注入哪一个 Bean 时,我们称这是手动装配,而当我们不进行指定时,则算作自动装配。
自动装配也称自动注入,有两种开启方式:
- 开启粗粒度的自动装配,即开启 Bean 的默认自动装配。在
标签中配置default-autowire
属性,或在@Bean
注解中配置autowire
属性。开启了默认自动装配的 Bean,Spring 会对它的全部属性都尝试注入值,这种方式不安全,因此很少使用。 - 开启细粒度的自动装配,即在组件类中使用
@Autowired
等注解对单个属性开启自动装配。
Spring 支持以下四种用于自动装配的注解:
- Spring 自带的
@Autowired
注解 - JSR-330 的
@Inject
注解 - JSR-250 的
@Resource
注解 - Spring 新增的
@Value
注解,用于装配 String 和基本类型的值。@Value
注解经常配合 SpEL 表达式一起使用。
SpEL 表达式的主要语法:
${}
,表示从 Properties 文件中读取相应属性的值。通常需要同@PropertySource
注解配合使用,该注解用于指定从哪个 Properties 文件中读取属性。#{}
,表示从 Spring Bean 中读取相应属性的值。如,#{user1.name}表示从名称为 user1 的 Bean 中 读取 name 属性值。- 冒号
:
用于指定默认值,如${server.port:80}。
Spring Bean 的作用域与生命周期
Bean 的作用域主要有两种:
- singleton:Bean 的默认作用域是 singleton,一个 singleton 的 Bean 被创建出来之后,它就会加入到单例池中缓存起来,然后在缓存中一直存在,由于 getBean 方法会先查缓存,只要缓存中有,就不会再创建了,因此,它在整个 Spring 容器的生命周期中只会有一个实例。
- prototype:这种作用域的 Bean 不会被缓存起来,那么也就意味着,每次 getBean 时,查缓存都不命中,然后每次都会创建一个新的实例返回给我们。
Bean 的生命周期如下:
- 实例化;
- 填充属性(注入依赖);
- 回调 Aware 系列接口,包括 BeanNameAware、BeanFactoryAware 等等;
- 应用 Bean 的后置处理器,依次调用它们的 postProcessBeforeInitialization() 方法,这些后置处理器包括;
- 回调 InitializingBean 接口;
- 调用 init 方法,包括 @PostConstruct 注解所标注的方法以及 init-method 属性所指定的方法。
- 应用 Bean 的后置处理器,依次调用它们的 postProcessAfterInitialization() 方法;
- 如果作用域为单例,则加入到单例池中,之后便会一直存在,直到 Spring 容器被销毁。
- 当 Spring 容器被销毁时,回调 DisposableBean 接口,然后调用 destroy 方法,包括 @PreDestroy 注解所标注的方法以及 destroy-method 属性所指定的方法。