你們對於 Spring 的 scope 應該都不會默認。所謂 scope,字面理解就是「做用域」、「範圍」,若是一個 bean 的 scope 配置爲 singleton,則從容器中獲取 bean 返回的對象都是相同的;若是 scope 配置爲prototype,則每次返回的對象都不一樣。java
通常狀況下,Spring 提供的 scope 都能知足平常應用的場景。但若是你的需求極其特殊,則本文所介紹自定義 scope 合適你。git
默認時,全部 Spring bean 都是的單例的,意思是在整個 Spring 應用中,bean的實例只有一個。能夠在 bean 中添加 scope 屬性來修改這個默認值。scope 屬性可用的值以下:github
範圍 | 描述 |
---|---|
singleton | 每一個 Spring 容器一個實例(默認值) |
prototype | 容許 bean 能夠被屢次實例化(使用一次就建立一個實例) |
request | 定義 bean 的 scope 是 HTTP 請求。每一個 HTTP 請求都有本身的實例。只有在使用有 Web 能力的 Spring 上下文時纔有效 |
session | 定義 bean 的 scope 是 HTTP 會話。只有在使用有 Web 能力的 Spring ApplicationContext 纔有效 |
application | 定義了每一個 ServletContext 一個實例 |
websocket | 定義了每一個 WebSocket 一個實例。只有在使用有 Web 能力的 Spring ApplicationContext 纔有效 |
若是上述 scope 仍然不能知足你的需求,Spring 還預留了接口,容許你自定義 scope。web
org.springframework.beans.factory.config.Scope
接口用於定義scope的行爲:spring
package org.springframework.beans.factory.config; import org.springframework.beans.factory.ObjectFactory; import org.springframework.lang.Nullable; public interface Scope { Object get(String name, ObjectFactory<?> objectFactory); @Nullable Object remove(String name); void registerDestructionCallback(String name, Runnable callback); @Nullable Object resolveContextualObject(String key); @Nullable String getConversationId(); }
通常來講,只須要從新 get 和 remove 方法便可。websocket
如今進入實戰環節。咱們要自定義一個Spring沒有的scope,該scope將bean的做用範圍限制在了線程內。即,相同線程內的bean是同個對象,跨線程則是不一樣的對象。session
要自定義一個Spring的scope,只需實現 org.springframework.beans.factory.config.Scope
接口。代碼以下:多線程
package com.waylau.spring.scope; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.Scope; /** * Thread Scope. * * @since 1.0.0 2019年2月13日 * @author <a href="https://waylau.com">Way Lau</a> */ public class ThreadScope implements Scope { private final ThreadLocal<Map<String, Object>> threadLoacal = new ThreadLocal<Map<String, Object>>() { @Override protected Map<String, Object> initialValue() { return new HashMap<String, Object>(); } }; public Object get(String name, ObjectFactory<?> objectFactory) { Map<String, Object> scope = threadLoacal.get(); Object obj = scope.get(name); // 不存在則放入ThreadLocal if (obj == null) { obj = objectFactory.getObject(); scope.put(name, obj); System.out.println("Not exists " + name + "; hashCode: " + obj.hashCode()); } else { System.out.println("Exists " + name + "; hashCode: " + obj.hashCode()); } return obj; } public Object remove(String name) { Map<String, Object> scope = threadLoacal.get(); return scope.remove(name); } public String getConversationId() { return null; } public void registerDestructionCallback(String arg0, Runnable arg1) { } public Object resolveContextualObject(String arg0) { return null; } }
在上述代碼中,threadLoacal用於作線程之間的數據隔離。換言之,threadLoacal實現了相同的線程相同名字的bean是同一個對象;不一樣的線程的相同名字的bean是不一樣的對象。app
同時,咱們將對象的hashCode打印了出來。若是他們是相同的對象,則hashCode是相同的。socket
定義一個AppConfig配置類,將自定義的scope註冊到容器中去。代碼以下:
package com.waylau.spring.scope; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.config.CustomScopeConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * App Config. * * @since 1.0.0 2019年2月13日 * @author <a href="https://waylau.com">Way Lau</a> */ @Configuration @ComponentScan public class AppConfig { @Bean public static CustomScopeConfigurer customScopeConfigurer() { CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer(); Map<String, Object> map = new HashMap<String, Object>(); map.put("threadScope", new ThreadScope()); // 配置scope customScopeConfigurer.setScopes(map); return customScopeConfigurer; } }
「threadScope」就是自定義ThreadScope的名稱。
接下來就根據通常的scope的用法,來使用自定義的scope了。代碼以下:
package com.waylau.spring.scope.service; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; /** * Message Service Impl. * * @since 1.0.0 2019年2月13日 * @author <a href="https://waylau.com">Way Lau</a> */ @Scope("threadScope") @Service public class MessageServiceImpl implements MessageService { public String getMessage() { return "Hello World!"; } }
其中@Scope("threadScope")
中的「threadScope」就是自定義ThreadScope的名稱。
定義Spring應用入口:
package com.waylau.spring.scope; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.waylau.spring.scope.service.MessageService; /** * Application Main. * * @since 1.0.0 2019年2月13日 * @author <a href="https://waylau.com">Way Lau</a> */ public class Application { public static void main(String[] args) { @SuppressWarnings("resource") ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); MessageService messageService = context.getBean(MessageService.class); messageService.getMessage(); MessageService messageService2 = context.getBean(MessageService.class); messageService2.getMessage(); } }
運行應用觀察控制檯輸出以下:
Not exists messageServiceImpl; hashCode: 2146338580 Exists messageServiceImpl; hashCode: 2146338580
輸出的結果也就驗證了ThreadScope「相同的線程相同名字的bean是同一個對象」。
若是想繼續驗證ThreadScope「不一樣的線程的相同名字的bean是不一樣的對象」,則只須要將Application改造爲多線程便可。
package com.waylau.spring.scope; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.waylau.spring.scope.service.MessageService; /** * Application Main. * * @since 1.0.0 2019年2月13日 * @author <a href="https://waylau.com">Way Lau</a> */ public class Application { public static void main(String[] args) throws InterruptedException, ExecutionException { @SuppressWarnings("resource") ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); CompletableFuture<String> task1 = CompletableFuture.supplyAsync(()->{ //模擬執行耗時任務 MessageService messageService = context.getBean(MessageService.class); messageService.getMessage(); MessageService messageService2 = context.getBean(MessageService.class); messageService2.getMessage(); //返回結果 return "result"; }); CompletableFuture<String> task2 = CompletableFuture.supplyAsync(()->{ //模擬執行耗時任務 MessageService messageService = context.getBean(MessageService.class); messageService.getMessage(); MessageService messageService2 = context.getBean(MessageService.class); messageService2.getMessage(); //返回結果 return "result"; }); task1.get(); task2.get(); } }
觀察輸出結果;
Not exists messageServiceImpl; hashCode: 1057328090 Not exists messageServiceImpl; hashCode: 784932540 Exists messageServiceImpl; hashCode: 1057328090 Exists messageServiceImpl; hashCode: 784932540
上述結果驗證ThreadScope「相同的線程相同名字的bean是同一個對象;不一樣的線程的相同名字的bean是不一樣的對象」
見https://github.com/waylau/spring-5-book 的 s5-ch02-custom-scope-annotation
項目。