(目錄)
Bean 作⽤域和⽣命周期
從前⾯的部落格我們可以看出 Spring 是⽤來讀取和存儲 Bean,是以在 Spring 中 Bean 是最核⼼的操作資源,是以接下來我們深⼊學習⼀下 Bean 對象
通過⼀個案例來看 Bean 作⽤域的問題
假設現在有⼀個公共的 Bean,提供給 A ⽤戶和 B ⽤戶使⽤,然⽽在使⽤的途中 A ⽤戶卻 悄悄 地修改了公共 Bean 的資料,導緻 B ⽤戶在使⽤時發⽣了預期之外的邏輯錯誤。
我們預期的結果是,公共 Bean 可以在各⾃的類中被修改,但不能影響到其他類。
被修改的 Bean 案例
公共 Bean:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java"); // 【重點:名稱是 Java】
return user;
}
}
A ⽤戶使⽤時,進⾏了修改操作:
@Controller
public class BeanScopesController {
@Autowired
private User user1;
public User getUser1() {
User user = user1;
System.out.println("Bean 原 Name:" + user.getName());
user.setName("悟空"); // 【重點:進⾏了修改操作】
return user;
}
}
B ⽤戶再去使⽤公共 Bean 的時候:
@Controller
public class BeanScopesController2 {
@Autowired
private user1;
public User getUser1() {
User user = user1;
return user;
}
}
列印 A ⽤戶和 B ⽤戶公共 Bean 的值:
public class BeanScopesTest {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("spring-config.xml");
BeanScopesController beanScopesController =
context.getBean(BeanScopesController.class);
System.out.println("A 對象修改之後 Name:" +
beanScopesController.getUser1().toString());
BeanScopesController2 beanScopesController2 =
context.getBean(BeanScopesController2.class);
System.out.println("B 對象讀取到的 Name:" +
beanScopesController2.getUser1().toString());
}
}
執⾏結果如下:

原因分析
操作以上問題的原因是因為 Bean 預設情況下是單例狀态(singleton)
,也就是所有⼈的使⽤的都是同⼀個對象,之前我們學單例模式的時候都知道,使⽤單例可以很⼤程度上提⾼性能,是以在 Spring 中Bean 的作⽤域預設也是 singleton 單例模式。
作用域定義
限定程式中變量的可⽤範圍叫做作⽤域,或者說在源代碼中定義變量的某個區域就叫做作⽤域。
⽽ Bean 的作⽤域是指 Bean 在 Spring 整個架構中的某種⾏為模式。
⽐如 singleton 單例作⽤域,就表 示 Bean 在整個 Spring 中隻有⼀份,它是全局共享的,那麼當其他⼈修改了這個值之後,那麼另⼀個 ⼈讀取到的就是被修改的值。
Bean 的 6 種作用域
Spring 容器在初始化⼀個 Bean 的執行個體時,同時會指定該執行個體的作⽤域。
Spring有 6 種作⽤域,
最後四種是基于 Spring MVC ⽣效的
:
- singleton:單例作⽤域
- prototype:原型作⽤域(多例作⽤域)
- request:請求作⽤域
- session:回話作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
作用域 | 描述 |
---|---|
singleton | 單例作用域,在整個Spring IoC容器僅存在一個Bean執行個體,Bean以單例方式存在 |
prototype | 原型作用域,每次從容器中調用Bean時,都傳回一個新的執行個體。即每次調用getBean時,相當于執行newXxxBean方法 |
request | 請求作用域,每次Http請求都會建立一個新的Bean |
session | 會話作用域,同一個Http Session共享一個Bean,不同Session使用不同的Bean |
application | 全局作用域,在一個Http Servlet Context中,定義一個Bean. |
websocket | 在一個WebSocket的生命周期中,定義一個Bean執行個體 |
注意後 4 種狀态是 Spring MVC 中的值,在普通的 Spring 項⽬中隻有前兩種。
後四種是spring MVC中的值,僅适用于web的Spring
環境中。
WebApplicationContext
1. 普通 Spring 環境:
① singleton
- 官⽅說明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
- 描述:
:擷取Bean(即通過applicationContext.getBean等⽅法擷取)及裝配Bean(即通過@Autowired注⼊)都是同⼀ 個對象。
該作⽤域下的Bean在IoC容器中隻存在⼀個執行個體
- 場景:
。⽆狀态表示Bean對象的屬性狀态不需要更新
通常⽆狀态的Bean使⽤該作⽤域
- 備注:Spring預設選擇該作⽤域
- Singleton是單例模式,就是在
,無論是否使用,它都已經存在了,每次擷取到的都是同一個對象。**而且singleton是spring的預設作用域(預設作用域)**。
建立容器時就自動的建立了這個對象
代碼舉例:
建立TestSingleton類作為bean:
package com;
import org.springframework.stereotype.Component;
@Component
public class TestSingleton {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
在測試類中同時嘗試擷取兩個bean,看輸出資訊是否相同,相同即同一個執行個體:
import com.Controller.UserController;
import com.TestSingleton;
import com.User.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("a.xml");
TestSingleton testSingleton_A=(TestSingleton)context.getBean("testSingleton");
System.out.println("下面是第一個bean的資訊:");
testSingleton_A.setMessage("This is A!");
testSingleton_A.getMessage();
TestSingleton testSingleton_B=(TestSingleton)context.getBean("testSingleton");
System.out.println("下面是第二個bean的資訊:");
testSingleton_B.getMessage();
}
}
輸出:
下面是第一個bean的資訊:
Your Message : This is A!
下面是第二個bean的資訊:
Your Message : This is A!
② prototype
- 官⽅說明:Scopes a single bean definition to any number of object instances.
- 描述:
:擷取Bean(即通過applicationContext.getBean等⽅法擷取)及裝配Bean(即通過@Autowired注⼊)都是新的 對象執行個體
每次對該作⽤域下的Bean的請求都會建立新的執行個體
- 場景:
通常有狀态的Bean使⽤該作⽤域
- prototype是原型類型,在建立容器的時候并不會執行個體化對象,而是
,而且我們每次擷取到的對象都不是同一個對象。
當我們嘗試去擷取一個bean時才會去建立一個對象
代碼舉例:
建立TestPrototype類作為bean,
通過注解Scope将作用域類型設定為prototype
:
package com;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype") // 注解生效
public class TestPrototype {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
在測試類中擷取兩個bean,看輸出資訊是否相同,相同即是同一個執行個體,不同則是不同的執行個體:
import com.TestPrototype;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("a.xml");
TestPrototype testSingleton_A=(TestPrototype)context.getBean("testPrototype");
System.out.println("下面是第一個bean的資訊:");
testSingleton_A.setMessage("This is A!");
testSingleton_A.getMessage();
TestPrototype testSingleton_B=(TestPrototype)context.getBean("testPrototype");
System.out.println("下面是第二個bean的資訊:");
testSingleton_B.getMessage();
}
}
輸出:
下面是第一個bean的資訊:
Your Message : This is A!
下面是第二個bean的資訊:
Your Message : null
2. Spring Web 環境:
、
request
、
session
這三個作用域是
application
基于Spring WebApplicationContext
實作的,隻有在web環境下才能使用(比如XmlWebApplicationContext中)
如果在普通的spring環境下,即正常的IOC容器(比如ClassPathXmlApplicationContext)中使用這些作用域,那麼程式會抛出一個IllegalStateException異常,來表明使用了未知的作用域。
而在web環境下,比如使用Spring中的WebApplicationContext時,我們不僅可以使用singleton和prototype這兩個作用域,還可以使用三個獨有的作用域:request、session、application.
在使用Web環境相關的作用域時,必須在Web容器中進行一些額外的配置:
- 如果使用的是Spring MVC架構,就不需這些配置,因為每一次http請求都會通過
來處理,而DispatcherServlet
中已經配置好了相關狀态。DispatcherServlet
- 如果是低版本的web容器,需要在配置的XML檔案中添加如下聲明即可:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
還可以使用Spring中的RequestContextFilter或者ServletRequestListener和其他方式,這裡不再詳細介紹。
③ request
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
- 備注:限定SpringMVC中使⽤
在進行使用時,也是在scope屬性中聲明:
<bean id="" class="" scope="request"/>
假設這個bean的id是loginAction,
Spring容器每一次用loginAction來處理Http請求時都會建立一個新的執行個體
,即loginAction 這個bean的作用域是Http Request級别的。
當Http請求調用作用域級别為Request的bean時,每增加一個Http請求,Spring就會建立一個新的bean,當請求處理完成之後變回銷毀這個bean,開發者可以任意的改變一個執行個體的狀态,因為每一個bean之間都是根據獨立的請求來的,其他執行個體根本看不到其他被開發者改變的執行個體的狀态。
④ session
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, ⽐如:記錄⼀個⽤戶的登陸資訊
- 備注:限定SpringMVC中使⽤
同一個Http Session(會話)之間共享一個bean,而不同的會話使用不同的bean.
在使用時,可以在scope屬性中聲明:
<bean id="user" class="" scope="session"/>
假設bean的id是user,Spring容器每次調用user的時候,都會在一個會話中建立一個執行個體,即這個bean是Http Session級别的。
Session中的所有http請求共享一個Bean,每次用Session級别的bean處理會話時,每增加一個會話,都會建立一個新的執行個體。與上面的request一樣,開發者可以随意修改bean的狀态,因為每個bean都是根據單獨的會話session來建立的,其他的bean無法看到這個bean的狀态。
⑤ application
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應⽤的上下⽂資訊,⽐如:記錄⼀個應⽤的共享資訊
備注:限定SpringMVC中使⽤
使用方式:
<bean id="app" class="" scope="application"/>
假設bean的id為app,Spring容器會在整個web應用範圍使用到app的時候建立一個新的執行個體。也就是說,appBean是在ServletContext級别的.。
單例作⽤域(singleton)和全局作⽤域(application)差別
- singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
- singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。
如何設定 Bean 作用域
1. 通過xml配置檔案
在xml配置檔案中,直接設定bean元素的scope屬性即可。
<?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">
//scope屬性
<bean id="SingletonBean" class="com.spring.demo.SingletonBean" scope="prototype"> </bean>
</beans>
2. 通過注解
在bean上添加注解:
- 直接設定值:@Scope("prototype")
- 使用枚舉設定:@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
package com.spring.demo;
import org.springframework.context.annotation.Scope;;
import org.springframework.stereotype.Component;
@Component("SingletonBean")
@Scope("prototype")
//@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
public class SingletonBean {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
@Component(“SingletonBean”)辨別這個類是一個bean,@Scope(“prototype”)辨別這個bean的作用域為prototype類型。
Bean 執行流程
Bean 執⾏流程(Spring 執⾏流程):
啟動 Spring 容器 -> 執行個體化 Bean(配置設定記憶體空間,從⽆到有)-> Bean 注冊到 Spring 中(存操作) -> 将 Bean 裝配到需要的類中(取操作)。
Bean ⽣命周期
Bean的生命周期
指一個Bean對象從**“出生”到“銷毀”的過程**,可以表達為:
Bean的定義——>Bean的初始化——>Bean的使用——>Bean的銷毀.
具體過程如下:
- 執行個體化Bean,為Bean配置設定一些記憶體空間。
- 設定屬性,注入或者裝配Bean
- 進行初始化:
- 實作各種通知(Aware)方法,如BeanNameAware、BeanFactoryAware、BeanFactoryAware、ApplicationContextAware等
- 執行
初始化前置方法
BeanPostProcessor
- 執行@PostConstruct初始化方法,注入依賴後執行
- 執行自定義的inti-method方法(可有可無)
- 執行
初始化後置方法。
BeanPostProcessor
- 使用Bean
- 銷毀Bean
銷毀容器的各種⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method。
執⾏流程如下圖所示:
1. 執行個體化和初始化的差別
執行個體化和屬性設定是 Java 級别的系統“事件”,其操作過程不可⼈⼯⼲預和修改
初始化是給 開發者提供的,可以在執行個體化之後,類加載完成之前進⾏⾃定義“事件”處理。
2. ⽣命流程的“故事”
Bean 的⽣命流程看似繁瑣,但咱們可以以⽣活中的場景來了解它。
⽐如我們現在需要買⼀棟房⼦,那 麼我們的流程是這樣的:
- 先買房(執行個體化,從⽆到有)
- 裝修(設定屬性)
- 買家電,如洗⾐機、冰箱、電視、空調等([各種]初始化)
- ⼊住(使⽤ Bean)
- 賣出去(Bean 銷毀)
⽣命周期示範
import com.bit.service.UserService;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class UserController10 implements BeanNameAware {
@Autowired
private UserService userService;
@Override
public void setBeanName(String s) {
System.out.println("執行通知:" + s);
}
// 初始化方法
@PostConstruct
public void postConstruct() {
System.out.println("執行 @PostConstruct 方法");
}
public void init() { // init() 方法名字可以随便起,隻需要和 xml 中的名稱保持一緻即可
System.out.println("執行 init 方法");
}
@PreDestroy
public void preDestroy() {
System.out.println("執行 @PreDestroy");
}
public void destroy() {
System.out.println("執行 destroy 方法");
}
}
xml 配置如下:
<?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:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置 Spring 掃描的根路徑(此根路徑下的所有 Spring 存對象的注解才能生效) -->
<content:component-scan base-package="com.bit"></content:component-scan>
<beans>
<bean id="userController10"
class="com.bit.controller.UserController10"
init-method="init" destroy-method="destroy"></bean>
</beans>
</beans>
/**
* Spring 啟動類(非必須,為了友善示範 Spring 的功能而建立)
*/
public class App {
public static void main(String[] args) {
// 1.先擷取對象的
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserController10 userController10 = context.getBean("userController10", UserController10.class);
context.destroy(); // 銷毀方法
}
}