天天看點

【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期

(目錄)

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());
        
   }
}
           

執⾏結果如下:

【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期

原因分析

操作以上問題的原因是因為

Bean 預設情況下是單例狀态(singleton)

,也就是所有⼈的使⽤的都是同⼀個對象,之前我們學單例模式的時候都知道,使⽤單例可以很⼤程度上提⾼性能,是以在 Spring 中Bean 的作⽤域預設也是 singleton 單例模式。

作用域定義

限定程式中變量的可⽤範圍叫做作⽤域,或者說在源代碼中定義變量的某個區域就叫做作⽤域。

⽽ Bean 的作⽤域是指 Bean 在 Spring 整個架構中的某種⾏為模式。

⽐如 singleton 單例作⽤域,就表 示 Bean 在整個 Spring 中隻有⼀份,它是全局共享的,那麼當其他⼈修改了這個值之後,那麼另⼀個 ⼈讀取到的就是被修改的值。

Bean 的 6 種作用域

Spring 容器在初始化⼀個 Bean 的執行個體時,同時會指定該執行個體的作⽤域。

Spring有 6 種作⽤域,

最後四種是基于 Spring MVC ⽣效的

  1. singleton:單例作⽤域
  2. prototype:原型作⽤域(多例作⽤域)
  3. request:請求作⽤域
  4. session:回話作⽤域
  5. application:全局作⽤域
  6. websocket:HTTP WebSocket 作⽤域
【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期
作用域 描述
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在IoC容器中隻存在⼀個執行個體

    :擷取Bean(即通過applicationContext.getBean等⽅法擷取)及裝配Bean(即通過@Autowired注⼊)都是同⼀ 個對象。
  • 場景:

    通常⽆狀态的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的請求都會建立新的執行個體

    :擷取Bean(即通過applicationContext.getBean等⽅法擷取)及裝配Bean(即通過@Autowired注⼊)都是新的 對象執行個體
  • 場景:

    通常有狀态的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上添加注解:

  1. 直接設定值:@Scope("prototype")
  2. 使用枚舉設定:@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期
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 執行流程

【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期

Bean 執⾏流程(Spring 執⾏流程):

啟動 Spring 容器 -> 執行個體化 Bean(配置設定記憶體空間,從⽆到有)-> Bean 注冊到 Spring 中(存操作) -> 将 Bean 裝配到需要的類中(取操作)。

【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期

Bean ⽣命周期

Bean的生命周期

指一個Bean對象從**“出生”到“銷毀”的過程**,可以表達為:

Bean的定義——>Bean的初始化——>Bean的使用——>Bean的銷毀.

具體過程如下:

  1. 執行個體化Bean,為Bean配置設定一些記憶體空間。
  2. 設定屬性,注入或者裝配Bean
  3. 進行初始化:
    • 實作各種通知(Aware)方法,如BeanNameAware、BeanFactoryAware、BeanFactoryAware、ApplicationContextAware等
    • 執行

      BeanPostProcessor

      初始化前置方法
    • 執行@PostConstruct初始化方法,注入依賴後執行
    • 執行自定義的inti-method方法(可有可無)
    • 執行

      BeanPostProcessor

      初始化後置方法。
  4. 使用Bean
  5. 銷毀Bean

銷毀容器的各種⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method。

【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期

執⾏流程如下圖所示:

【Spring】詳解Bean的六種作用域、執行流程、生命周期Bean 作⽤域和⽣命周期通過⼀個案例來看 Bean 作⽤域的問題作用域定義Bean 的 6 種作用域如何設定 Bean 作用域Bean 執行流程Bean ⽣命周期

1. 執行個體化和初始化的差別

執行個體化和屬性設定是 Java 級别的系統“事件”,其操作過程不可⼈⼯⼲預和修改

初始化是給 開發者提供的,可以在執行個體化之後,類加載完成之前進⾏⾃定義“事件”處理。

2. ⽣命流程的“故事”

Bean 的⽣命流程看似繁瑣,但咱們可以以⽣活中的場景來了解它。

⽐如我們現在需要買⼀棟房⼦,那 麼我們的流程是這樣的:

  1. 先買房(執行個體化,從⽆到有)
  2. 裝修(設定屬性)
  3. 買家電,如洗⾐機、冰箱、電視、空調等([各種]初始化)
  4. ⼊住(使⽤ Bean)
  5. 賣出去(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(); // 銷毀方法

    }
}