37 谈谈Spring Bean的生命周期和作用域?

在企业应用软件开发中,Java是毫无争议的主流语言,开放的Java EE规范和强大的开源框架功不可没,其中Spring毫无疑问已经成为企业软件开发的事实标准之一。今天这一讲,我将补充Spring相关的典型面试问题,并谈谈其部分设计细节。

今天我要问你的问题是,谈谈Spring Bean的生命周期和作用域?

典型回答

Spring Bean生命周期比较复杂,可以分为创建和销毁两个过程。

首先,创建Bean会经过一系列的步骤,主要包括:

你可以参考下面示意图理解这个具体过程和先后顺序。-

第二,Spring Bean的销毁过程会依次调用DisposableBean的destroy方法和Bean自身定制的destroy方法。

Spring Bean有五个作用域,其中最基础的有下面两种:

从Bean的特点来看,Prototype适合有状态的Bean,而Singleton则更适合无状态的情况。另外,使用Prototype作用域需要经过仔细思考,毕竟频繁创建和销毁Bean是有明显开销的。

如果是Web容器,则支持另外三种作用域:

考点分析

今天我选取的是一个入门性质的高频Spring面试题目,我认为相比于记忆题目典型回答里的细节步骤,理解和思考Bean生命周期所体现出来的Spring设计和机制更有意义。

你能看到,Bean的生命周期是完全被容器所管理的,从属性设置到各种依赖关系,都是容器负责注入,并进行各个阶段其他事宜的处理,Spring容器为应用开发者定义了清晰的生命周期沟通界面。

如果从具体API设计和使用技巧来看,还记得我在[专栏第13讲]提到过的Marker Interface吗,Aware接口就是个典型应用例子,Bean可以实现各种不同Aware的子接口,为容器以Callback形式注入依赖对象提供了统一入口。

言归正传,还是回到Spring的学习和面试。关于Spring,也许一整本书都无法完整涵盖其内容,专栏里我会有限地补充:

知识扩展

首先,我们先来看看Spring的基础机制,至少你需要理解下面两个基本方面。

从Bean创建过程可以看到,它的依赖关系都是由容器负责注入,具体实现方式包括带参数的构造函数、setter方法或者AutoWired方式实现。

第二,Spring到底是指什么?

我前面谈到的Spring,其实是狭义的Spring Framework,其内部包含了依赖注入、事件机制等核心模块,也包括事务、O/R Mapping等功能组成的数据访问模块,以及Spring MVC等Web框架和其他基础组件。

广义上的Spring已经成为了一个庞大的生态系统,例如:

上面的介绍比较笼统,针对这么多内容,如果将目标定得太过宽泛,可能就迷失在Spring生态之中,我建议还是深入你当前使用的模块,如Spring MVC。并且,从整体上把握主要前沿框架(如Spring Cloud)的应用范围和内部设计,至少要了解主要组件和具体用途,毕竟如何构建微服务等,已经逐渐成为Java应用开发面试的热点之一。

第三,我们来探讨一下更多有关Spring AOP自身设计和实现的细节。

先问一下自己,我们为什么需要切面编程呢?

切面编程落实到软件工程其实是为了更好地模块化,而不仅仅是为了减少重复代码。通过AOP等机制,我们可以把横跨多个不同模块的代码抽离出来,让模块本身变得更加内聚,进而业务开发者可以更加专注于业务逻辑本身。从迭代能力上来看,我们可以通过切面的方式进行修改或者新增功能,这种能力不管是在问题诊断还是产品能力扩展中,都非常有用。

在之前的分析中,我们已经分析了AOP Proxy的实现原理,简单回顾一下,它底层是基于JDK动态代理或者cglib字节码操纵等技术,运行时动态生成被调用类型的子类等,并实例化代理对象,实际的方法调用会被代理给相应的代理对象。但是,这并没有解释具体在AOP设计层面,什么是切面,如何定义切入点和切面行为呢?

Spring AOP引入了其他几个关键概念:

Java核心类库中同样存在类似代码,例如Java 9中引入的Flow API就是Reactive Stream规范的最小子集,通过这种方式,可以保证不同产品直接的无缝沟通,促进了良好实践的推广。

具体的Spring Advice结构请参考下面的示意图。-

其中,BeforeAdvice和AfterAdvice包括它们的子接口是最简单的实现。而Interceptor则是所谓的拦截器,用于拦截住方法(也包括构造器)调用事件,进而采取相应动作,所以Interceptor是覆盖住整个方法调用过程的Advice。通常将拦截器类型的Advice叫作Around,在代码中可以使用“@Around”来标记,或者在配置中使用“aop:around”。

如果从时序上来看,则可以参考下图,理解具体发生的时机。

你可以参看下面的示意图,来进一步理解上面这些抽象在逻辑上的意义。

在准备面试时,如果在实践中使用过AOP是最好的,否则你可以选择一个典型的AOP实例,理解具体的实现语法细节,因为在面试考察中也许会问到这些技术细节。

如果你有兴趣深入内部,最好可以结合Bean生命周期,理解Spring如何解析AOP相关的注解或者配置项,何时何地使用到动态代理等机制。为了避免被庞杂的源码弄晕,我建议你可以从比较精简的测试用例作为一个切入点,如CglibProxyTests

另外,Spring框架本身功能点非常多,AOP并不是它所支持的唯一切面技术,它只能利用动态代理进行运行时编织,而不能进行编译期的静态编织或者类加载期编织。例如,在Java平台上,我们可以使用Java Agent技术,在类加载过程中对字节码进行操纵,比如修改或者替换方法实现等。在Spring体系中,如何做到类似功能呢?你可以使用AspectJ,它具有更加全面的能力,当然使用也更加复杂。

今天我从一个常见的Spring面试题开始,浅谈了Spring的基础机制,探讨了Spring生态范围,并且补充分析了部分AOP的设计细节,希望对你有所帮助。

一课一练

关于今天我们讨论的题目你做到心中有数了吗?今天的思考题是,请介绍一下Spring声明式事务的实现机制,可以考虑将具体过程画图。

请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。

你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。