Java 中的公共类称之为 Bean 或 Java Bean,而 Spring 中的 Bean 指的是将对象的生命周期,交给 Spring IoC 容器来管理的对象。所以 Spring 中的 Bean 对象在使用时,无需通过 new 来创建对象,只需要通过 DI(依赖注入),从 Spring 中取出要使用的对象即可。 那么 Spring 中,Bean 的生命周期又有哪些呢?接下来,我们一起来看。
1.通过⼀个案例来看 Bean 作⽤域的问题
Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。比如 singleton 单例作用域,就表示 Bean 在整个 Spring 中只有一份,它是全局共享的,当有人修改了这个值之后,那么另一个人读取到的就是被修改后的值。 举个例子,比如我们在 Spring 中定义了一个单例的 Bean 对象 user(默认作用域为单例),具体实现代码如下:
然后,在 A 类中使用并修改了 user 对象,具体实现代码如下:
最后,在 B 类中也使用了 user 对象,具体实现代码如下:
此时我们访问 B 对象中的 getUser 方法,就会发现此时的用户名为 A 类中修改的“李四”,而非原来的“张三”,这就说明 Bean 对象 user 默认就是单例的作用域。如果有任何地方修改了这个单例对象,那么其他类再调用就会得到一个修改后的值。
2.作用域分类
在 Spring 中,Bean 的常见作用域有以下 5 种:
- singleton:单例作用域;
- prototype:原型作用域(多例作用域);
- request:请求作用域;
- session:会话作用域;
- application:全局作用域。
注意:后 3 种作用域,只适用于 Spring MVC 框架。
2.1 singleton
-
官方说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
-
描述:该作用域下的 Bean 在 IoC 容器中只存在一个实例:获取 Bean(即通过 applicationContext.getBean等方法获取)及装配 Bean(即通过 @Autowired 注入)都是同一个对象。
-
场景:通常无状态的 Bean 使用该作用域。无状态表示 Bean 对象的属性状态不需要更新。
-
备注:Spring 默认选择该作用域。
2.2 prototype
-
官方说明:Scopes a single bean definition to any number of object instances.
-
描述:每次对该作用域下的 Bean 的请求都会创建新的实例:获取 Bean(即通过 applicationContext.getBean 等方法获取)及装配 Bean(即通过 @Autowired 注入)都是新的对象实例。
-
场景:通常有状态的 Bean 使用该作用域。
2.3 request
-
官方说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
-
描述:每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
-
场景:一次 Http 的请求和响应的共享 Bean。
-
备注:限定 Spring MVC 框架中使用。
2.4 session
-
官方说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
-
描述:在一个 Http Session 中,定义一个 Bean 实例。
-
场景:用户会话的共享 Bean, 比如:记录一个用户的登陆信息。
-
备注:限定 Spring MVC 框架中使用。
2.5 application
-
官方说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
-
描述:在一个 Http Servlet Context 中,定义一个 Bean 实例。
-
场景:Web 应用的上下文信息,比如:记录一个应用的共享信息。
-
备注:限定 Spring MVC 框架中使用。
3.作用域设置
- 直接设置作用域的具体值,如:@Scope("prototype");
- 设置 ConfigurableBeanFactory 和 WebApplicationContext 提供的 SCOPE_XXX 变量,如 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)。
具体设置代码如下:
然后我们再来看看运行结果:
4.Spring 执⾏流程
5.Bean 生命周期
Spring 中 Bean 的生命周期是指:Bean 在 Spring(IoC)中从创建到销毁的整个过程。 Spring 中 Bean 的生命周期主要包含以下 5 部分:
- 实例化:为 Bean 分配内存空间;
- 设置属性:将当前类依赖的 Bean 属性,进行注入和装配;
- 初始化:
- 执行各种通知;
- 执行初始化的前置方法;
- 执行初始化方法;
- 执行初始化的后置方法。
- 使用 Bean:在程序中使用 Bean 对象;
- 销毁 Bean:将 Bean 对象进行销毁操作。
以上生命周期中,需要注意的是:“实例化”和“初始化”是两个完全不同的过程,千万不要搞混,实例化只是给 Bean 分配了内存空间,而初始化则是将程序的执行权,从系统级别转换到用户级别,并开始执行用户添加的业务代码。
5.1 代码演示
最后,在 Spring 的启动类中获取 Bean,具体实现代码如下
从上面的执行结果可以看出,代码执行顺序符合 Bean 生命周期的执行顺序:
- 实例化:为 Bean 分配内存空间;
- 设置属性:将当前类依赖的 Bean 属性,进行注入和装配;
- 初始化:
- 执行各种通知;
- 执行初始化的前置方法;
- 执行初始化方法;
- 执行初始化的后置方法。
- 使用 Bean:在程序中使用 Bean 对象;
- 销毁 Bean:将 Bean 对象进行销毁操作。
那么问题来了,能不能先执行初始化再执行设置属性呢?也就是将生命周期中的步骤 2 和步骤 3 的执行顺序交换一下? 答案是否定的。看以下代码:
此时如果先执行步骤 2,先将 UserService 注入到当前类,再调用步骤 3 执行初始化,那么程序的执行是正常的。然而如果将交互步骤 2 和步骤 3 的执行顺序,那么程序执行就会报错(空指针异常),所以 Bean 的生命周期的顺序必须是:
1.实例化:为 Bean 分配内存空间; 2.设置属性:将当前类依赖的 Bean 属性,进行注入和装配; 3.初始化:
- 执行各种通知;
- 执行初始化的前置方法;
- 执行初始化方法;
- 执行初始化的后置方法。 4.使用 Bean:在程序中使用 Bean 对象; 5.销毁 Bean:将 Bean 对象进行销毁操作。
总结
Bean 的生命周期指的是 Bean 在 Spring(IoC)中从创建到销毁的整个过程。Bean 的生命周期主要包含以下 5 个流程: 1.实例化:为 Bean 分配内存空间; 2.设置属性:将当前类依赖的 Bean 属性,进行注入和装配; 3.初始化:
- 执行各种通知;
- 执行初始化的前置方法;
- 执行初始化方法;
- 执行初始化的后置方法。 4.使用 Bean:在程序中使用 Bean 对象; 5.销毁 Bean:将 Bean 对象进行销毁操作。