什么是 Spring
Spring 是一个轻量级的企业级应用开发框架,于 2004 年诞生第一个版本,其初衷是简化企业级应用的开发。
在传统应用开发中,一个完整的应用由一组相互协作的对象组成,开发一个应用除了要开发业务逻辑之外,更多的是关注如何使这些对象协作来完成所需功能,而且要高内聚,低耦合。虽然一些设计模式可以达到这个目的,可是这又大大增加了开发成本。如果能够通过配置或声明的方式管理对象及其依赖关系,那么就可以减少很多不必要的工作,同时降低对象之间的耦合度。Spring 框架主要就是做这个事情的。
Spring 框架除了帮我们管理对象及其依赖关系之外,还提供了面向切面编程的能力,并在此基础上,允许我们对一些通用任务如日志记录、安全控制、异常处理等进行集中式管理,还能帮我们管理最头疼的数据库事务。此外,它还提供了粘合其他框架的能力,使我们可以方便地与各种第三方框架进行集成。
Spring 是基于 IoC 和 AOP 两大思想理论衍生而来的,从技术角度来说,Spring 是一个同时实现了 IoC 和 AOP 的开发框架。
Spring 的整体架构
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,控制反转)是一种设计思想,所谓控制反转,指的是对资源的控制方式进行反转。这里说的资源主要指我们的 Java 对象。在 OOP 中,对象之间往往存在某种依赖关系,当一个对象依赖另一个对象时,传统 OOP 的做法是在它内部直接 new 一个出来,这种做法是由对象自己主动创建并管理依赖资源。IoC 就是要将其颠倒过来,使得对象不再主动控制依赖资源,而是被动接受资源,IoC 要求将资源的控制权下放给第三方容器,这个第三方容器也称 Ioc 容器,它将对资源进行集中管理,对象需要什么资源就从容器中取,或者让容器主动将资源注入进来。
在 IoC 之后,对象与依赖资源之间不再具有强耦合性,资源可以被直接替换,而无需改动需求方的代码。举个不恰当的例子,董事长需要一个秘书,传统的做法是,董事长自己去指定一个秘书,控制权在他自己手上,但是这会导致他与秘书之间的耦合性较强,一旦他想换秘书了,就得修改自己的代码。IoC 的做法是,董事长声明自己的需求,然后等待 IoC 容器为他分配一个秘书,至于是哪个秘书,由容器说了算,如果需要换秘书,也是修改容器的配置文件,与董事长无关,这样就实现了两者间的解耦。
IoC 的两种实现方式:
DI(Dependency Injection,依赖注入)。所谓依赖注入,是指对象所依赖的资源将通过被动注入的方式得到,换言之,容器会主动地根据预先配置的依赖关系将资源注入进来。 DL(Dependency Lookup,依赖查找)。依赖查找是早先提出的一种 IoC 实现方式,它要求对象主动查找依赖资源,这种方式已经不再使用。
Bean 的概念
在 Java 中,“Bean”是对“可重用组件”的惯用叫法(个人理解,Java 一词原本代指爪哇咖啡,bean 表示咖啡豆,那么咖啡豆不就是咖啡的“可重用组件”嘛)。组件本身就是一个抽象概念,Bean 作为其代称,也是一个抽象概念,当我们将一个类或一个对象作为组件来考虑时,就可以称它为 Bean。
在 Spring 中,Bean 的概念同上,它有时也被称为 Component。由 Spring 容器管理的 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,其中第一个依赖第二个:
<bean id="John" class="Person">
<property name="lover">
<ref bean="Mary"/>
</property>
</bean>
<bean id="Mary" class="Person"/>
然后创建 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 的定义如下:
这个 Map 集合保存了所有单例作用域的 Bean 实例,它是构成 Spring 容器的核心,我把它称之为单例池。
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 对象,然后读取并解析 Bean 定义信息,同时将这些 Bean 定义信息封装成一个个BeanDefinition
对象,在BeanDefinitionRegistery
中进行注册。这一步,主要完成了 BeanFactory 的创建,以及 Bean 定义信息的加载。完成 Spring 容器的基本配置工作,包括:配置 BeanFactory,对 BeanFactory 做一些后置处理,注册 Bean 的后置处理器,初始化消息源和事件多播器,注册监听器等等。 调用 finishBeanFactoryInitialization()
方法,该方法会遍历所有的BeanDefinition
,依次实例化每个非抽象非懒加载的单例 Bean,并将其加入单例池。这一步,主要完成了 Bean 的实例化。
Spring 容器的几个核心类:
DefaultListableBeanFactory
是一个通用的BeanFactory
实现类,也是 ApplicationContext 的 IoC 服务提供者,此外,它还实现了BeanDefinitionRegistry
接口,同时扮演着 Bean 定义注册表的角色。BeanDefinitionRegistry
表示 Bean 定义注册表,负责维护和管理 BeanDefinition 实例。BeanDefinition
用于封装 Bean 定义信息,包括类名、构造方法参数、是否为单例 Bean等信息。
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 属性所指定的方法。