大家對于 spring 的 scope 應該都不會默認。所謂 scope,字面理解就是“作用域”、“范圍”,如果一個 bean 的 scope 配置為 singleton,則從容器中獲取 bean 返回的對象都是相同的;如果 scope 配置為prototype,則每次返回的對象都不同。
一般情況下,spring 提供的 scope 都能滿足日常應用的場景。但如果你的需求極其特殊,則本文所介紹自定義 scope 合適你。
spring 內置的 scope
默認時,所有 spring bean 都是的單例的,意思是在整個 spring 應用中,bean的實例只有一個。可以在 bean 中添加 scope 屬性來修改這個默認值。scope 屬性可用的值如下:
范圍 | 描述 |
---|---|
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。
scope 接口
org.springframework.beans.factory.config.scope
接口用于定義scope的行為:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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 方法即可。
自定義線程范圍內的scope
現在進入實戰環節。我們要自定義一個spring沒有的scope,該scope將bean的作用范圍限制在了線程內。即,相同線程內的bean是同個對象,跨線程則是不同的對象。
1. 定義scope
要自定義一個spring的scope,只需實現 org.springframework.beans.factory.config.scope接口。代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >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是不同的對象。
同時,我們將對象的hashcode打印了出來。如果他們是相同的對象,則hashcode是相同的。
2. 注冊scope
定義一個appconfig配置類,將自定義的scope注冊到容器中去。代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >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的名稱。
3. 使用scope
接下來就根據一般的scope的用法,來使用自定義的scope了。代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >way lau</a> */ @scope ( "threadscope" ) @service public class messageserviceimpl implements messageservice { public string getmessage() { return "hello world!" ; } } |
其中@scope("threadscope")中的“threadscope”就是自定義threadscope的名稱。
4. 定義應用入口
定義spring應用入口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >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改造為多線程即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >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是不同的對象”
源碼
見 的 s5-ch02-custom-scope-annotation項目。
以https://github.com/waylau/spring-5-book上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://my.oschina.net/waylau/blog/3009991