Spring系列8:bean的作用域

本文內容

  1. bean定義資訊的意義
  2. 介紹6種bean的作用域

bean定義資訊的意義

Spring中區分下類、類定義資訊,類實例對象的概念?不容易理解,以餐館中點炒飯為例。

類: 相當於你看到菜單上炒飯這個菜品,有這個菜。

類定義資訊:相當於炒飯的烹飪法,烹飪法只有一份

類實例對象: 相當於按照上面烹飪法炒出來的一份炒飯,可以炒分多份出來。

Spring容器中創建了一個類定義資訊的,就可以根據這個定義資訊來創建個類實例對象出來,這個理解了很關鍵。Spring中不僅可以控制 bean 對象中的各種依賴項和配置值,還可以控制 bean 額作用範圍。

介紹6種bean的作用域

Spring Framework 支援6個bean的作用域,其中4個僅在web類型的 ApplicationContext 中可用。詳細見下表

作用域 描述
singleton (默認)將單個 bean 定義限定為每個 Spring IoC 容器的單個對象實例。
prototype 將單個 bean 定義限定為任意數量的對象實例
request 將單個 bean 定義限定為單個 HTTP 請求的生命周期。也就是說,每個 HTTP 請求都有自己的 bean 實例,該實例是在單個 bean 定義的後面創建的。僅在web類型的 ApplicationContext有效。
session 將單個 bean 定義限定為 HTTP 會話的生命周期。僅在web類型的 ApplicationContext有效。
application 將單個 bean 定義限定為 ServletContext 的生命周期。僅在web類型的 ApplicationContext有效。
websocket 將單個 bean 定義限定為 WebSocket 的生命周期。僅在web類型的 ApplicationContext有效。

從 Spring 3.0 開始,執行緒範圍可用,但默認情況下未註冊。SimpleThreadScope感興趣可以詳細看下面自定義作用域。

singleton

只有一個單例 bean 的共享實例被管理,並且所有對具有與該 bean 定義匹配的一個或多個 ID 的 bean 的請求都會導致 Spring 容器返回一個特定的 bean 實例。"蠍子粑粑獨一份"。注意是一個IoC容器內。

singleton

prototype

原型作用域下每次容器都會創建一個新的 bean 實例。通常來說,對有狀態 bean適合使用原型,對無狀態 bean適合使用單例。

prototype

下面的配置節指定了bean的作用域是原型的。

<bean id="accountService" class="x.y.z.AccountService" scope="prototype"/>

與其他範圍相比,Spring 不管理原型 bean 的完整生命周期。容器實例化、配置和以其他方式組裝原型對象並將其傳遞給客戶端,由客戶端去管理。bean生命周期後面介紹。

request, session, application, webSocket

這4個在web的應用上下文中使用的作用域,暫不展開講,留給後面寫spring mvc 專門講。

自定義作用域和使用

如何自定義?

可以通過實現org.springframework.beans.factory.config.Scope自定義作用域。Scope 介面有四種方法可以從作用域中獲取對象,將它們從作用域中移除,並讓它們被銷毀。

package org.springframework.beans.factory.config;
public interface Scope {
    // 從底層範圍返回具有給定名稱的對象
	Object get(String name, ObjectFactory<?> objectFactory);
    // 從底層範圍中刪除具有給定的對象
    Object remove(String name);
    // 註冊要在作用域中指定對象的銷毀時執行的回調
    void registerDestructionCallback(String name, Runnable callback);
	// 省略
}

打鐵要趁熱,直接上SimpleThreadScope的源碼,解析下執行緒級別作用域是如何實現的。

package org.springframework.context.support;

public class SimpleThreadScope implements Scope {

	private static final Log logger = LogFactory.getLog(SimpleThreadScope.class);
	// 1 bean是存放在ThreadLocal中的 綁定了當前執行緒 
	private final ThreadLocal<Map<String, Object>> threadScope =
			new NamedThreadLocal<Map<String, Object>>("SimpleThreadScope") {
				@Override
				protected Map<String, Object> initialValue() {
					return new HashMap<>();
				}
			};

    // 2 查ThreadLocal,有對應bean就直接返回,沒有就創建一個放入ThreadLocal,在返回
	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		Map<String, Object> scope = this.threadScope.get();
		Object scopedObject = scope.get(name);
		if (scopedObject == null) {
			scopedObject = objectFactory.getObject();
			scope.put(name, scopedObject);
		}
		return scopedObject;
	}

    // 3 從ThreadLocal中移除bean
	@Override
	@Nullable
	public Object remove(String name) {
		Map<String, Object> scope = this.threadScope.get();
		return scope.remove(name);
	}

    // 4 銷毀回調
	@Override
	public void registerDestructionCallback(String name, Runnable callback) {
		logger.warn("SimpleThreadScope does not support destruction callbacks. " +
				"Consider using RequestScope in a web environment.");
	}

	@Override
	@Nullable
	public Object resolveContextualObject(String key) {
		return null;
	}

	@Override
	public String getConversationId() {
		return Thread.currentThread().getName();
	}

}

如何使用?

  1. 先將自定義的scope注入到容器中

    編碼的方式註冊: ConfigurableBeanFactory介面提供了registerScope來註冊自定義的scope。

    Scope threadScope = new SimpleThreadScope();
    beanFactory.registerScope("thread", threadScope);
    

    配置文件方式的註冊: 使用 CustomScopeConfigurer 以聲明方式進行註冊

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
        xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="//www.springframework.org/schema/aop"
        xsi:schemaLocation="//www.springframework.org/schema/beans
            //www.springframework.org/schema/beans/spring-beans.xsd
            //www.springframework.org/schema/aop
            //www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
            <property name="scopes">
                <map>
                    <entry key="thread">
                        <bean class="org.springframework.context.support.SimpleThreadScope"/>
                    </entry>
                </map>
            </property>
        </bean>
    </beans>    
    
  2. bean配置中使用

    使用方式普通的scope一樣。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
        xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="//www.springframework.org/schema/aop"
        xsi:schemaLocation="//www.springframework.org/schema/beans
            //www.springframework.org/schema/beans/spring-beans.xsd
            //www.springframework.org/schema/aop
            //www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
            <property name="scopes">
                <map>
                    <entry key="thread">
                        <bean class="org.springframework.context.support.SimpleThreadScope"/>
                    </entry>
                </map>
            </property>
        </bean>
    
        <bean id="thing2" class="x.y.Thing2" scope="thread">
            <property name="name" value="Rick"/>
            <aop:scoped-proxy/>
        </bean>
    
        <bean id="thing1" class="x.y.Thing1">
            <property name="thing2" ref="thing2"/>
        </bean>
    
    </beans>
    

總結

本文Spring中的7種作用域,以及如何自定義作用域並使用。下一篇介紹基於註解的Spring容器配置。

知識分享,轉載請註明出處。學無先後,達者為先!

Tags: