環境:Spring5.3.23
Spring Scope Bean是Spring用于管理Bean的作用域的一種機制。它定義了容器中Bean的生命周期和實例化策略,即如何創建Bean實例。
在Spring中,Bean的作用域包括單例(singleton)、原型(prototype)、請求(request)、會話(session)等。每個作用域都有其特定的使用場景和行為:
此外,Spring還提供了其他一些作用域應用(Application)、WebSocket,以滿足不同場景的需求。
通過合理地選擇Bean的作用域,可以優化應用的性能和資源利用率。例如,對于需要頻繁創建和銷毀實例的Bean,使用原型作用域會更高效;而對于需要在多個請求或會話之間共享狀態的Bean,則可以選擇單例或會話作用域。附官方圖:
圖片
接下來將分別介紹每一種作用域bean。
static class Person { @Override public String toString() { return super.toString() + " - " + this.hashCode() + "" ; }}
默認使用@Bean,@Service,@Controller注解標注的注解都是單例的。也可以同@Scope注解指定作用域為單例
@Bean// 不指定@Scope默認就是單例@Scope(value = "singleton")public Person person() { return new Person() ;}
測試
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { context.registerBean(Config.class) ; context.refresh() ; System.out.println(context.getBean(Person.class)) ; System.out.println(context.getBean(Person.class)) ;}
控制臺輸出
com.pack.main.scope.ScopeMain5$Person@5e0e82ae - 1578009262com.pack.main.scope.ScopeMain5$Person@5e0e82ae - 1578009262
每次獲取的都是同一個實例。
原理
public abstract class AbstractBeanFactory { protected <T> T doGetBean(...) { // ... // 判斷是否是單例 if (mbd.isSingleton()) { // 先從單例池中查找是否已經存在,不存在則調用createBean創建, // 然后存入單例池中 sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } }); } // ... }}
每次從容器中請求Bean時,都會創建一個新的Bean實例。
@Bean@Scope(value = "prototype")public Person person() { return new Person() ;}
控制臺輸出
com.pack.main.scope.ScopeMain5$Person@fa4c865 - 262457445com.pack.main.scope.ScopeMain5$Person@3bd82cf5 - 1004023029
每次獲取都是不同的對象。
原理
public abstract class AbstractBeanFactory { protected <T> T doGetBean(...) { // ... // 判斷是否是單例 if (mbd.isSingleton()) { // ... } // 判斷是否是原型 else if (mbd.isPrototype()) { Object prototypeInstance = null; try { // 不存在什么緩存池,直接創建bean實例返回 prototypeInstance = createBean(beanName, mbd, args); } } // ... }}
這里考慮一個問題,如何在單例bean中正確的注入原型bean?
接下來都是與web環境相關了,所以這里演示的示例會以SpringBoot3.0.5環境演示。
基礎類
@Component@Scope(value = "request")public class Person {}
測試類
@RestController@RequestMapping("/scopes")public class ScopeController { @Resource private Person person ; @Resource private PersonService ps ; @GetMapping("/request") public Person request() { System.out.println("ScopeController: " + person) ; ps.query() ; return person ; }}
Service
@Servicepublic class PersonService { @Resource private Person person ; public void query() { System.out.println("PersonService: " + person) ; }}
如果上面這樣配置,啟動服務將會報錯:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-6.0.7.jar:6.0.7]
該錯誤的原因就是你在一個單例bean中注入一個request作用域的bean,而request作用域bean的生命周期是在一個web請求開始創建的,所以這里你當然是沒法注入的。
解決辦法:
@Component@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)public class Person {}
測試結果
ScopeController: com.pack.scopes.Person@106a9684 - 275420804PersonService: com.pack.scopes.Person@106a9684 - 275420804ScopeController: com.pack.scopes.Person@64396678 - 1681483384PersonService: com.pack.scopes.Person@64396678 - 1681483384
每次請求接口都獲取的不是同一個實例。并且在一個完整的請求中獲取的Person都是同一個。
該注解原理與上面其實一致的
@Scope(WebApplicationContext.SCOPE_REQUEST)public @interface RequestScope { @AliasFor(annotation = Scope.class) // 設置好了使用代理 ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}
@Component@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)// 與request一樣,必須設置代理模式或者使用下面這個注解// @SessionScopepublic class Person {}
測試
ScopeController: com.pack.scopes.Person@2b56038d - 727057293PersonService: com.pack.scopes.Person@2b56038d - 727057293ScopeController: com.pack.scopes.Person@2b56038d - 727057293PersonService: com.pack.scopes.Person@2b56038d - 727057293
多次訪問都是同一個session;你再換個瀏覽器訪問
ScopeController: com.pack.scopes.Person@1aa201fd - 446824957PersonService: com.pack.scopes.Person@1aa201fd - 446824957ScopeController: com.pack.scopes.Person@1aa201fd - 446824957PersonService: com.pack.scopes.Person@1aa201fd - 446824957
此時對象就是一個新的了,不同的瀏覽器訪問當然不是同一個session了。
@Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS)// @ApplicationScope// 都是web環境,所以情況都一樣public class Person {}
測試
360瀏覽器
ScopeController: com.pack.scopes.Person@6371b4b6 - 1668396214PersonService: com.pack.scopes.Person@6371b4b6 - 1668396214
Chrome瀏覽器
ScopeController: com.pack.scopes.Person@6371b4b6 - 1668396214PersonService: com.pack.scopes.Person@6371b4b6 - 1668396214
他們是同一個對象,application作用域生命周期與整個應用一樣,只有你關閉了服務器,在啟動后才會是再重新創建的bean對象。
public abstract class AbstractApplicationContext { public void refresh() { postProcessBeanFactory(beanFactory); }}public class AnnotationConfigServletWebServerApplicationContext { protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { super.postProcessBeanFactory(beanFactory); }}public class ServletWebServerApplicationContext { protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // ... registerWebApplicationScopes(); } private void registerWebApplicationScopes() { WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory()); }}public abstract class WebApplicationContextUtils { public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { registerWebApplicationScopes(beanFactory, null); } public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, @Nullable ServletContext sc) { // 注冊作用域 beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope()); if (sc != null) { ServletContextScope appScope = new ServletContextScope(sc); beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); } }}
這里每一種web作用域都有一個對應的Scope實現RequestScope,SessionScope,ServletContextScope。
3.2 查找web作用域bean
public abstract class AbstractBeanFactory { protected <T> T doGetBean(...) { // ... // 判斷是否是單例 if (mbd.isSingleton()) { // ... } // 判斷是否是原型 else if (mbd.isPrototype()) { Object prototypeInstance = null; try { // 不存在什么緩存池,直接創建bean實例返回 prototypeInstance = createBean(beanName, mbd, args); } } // 其它作用域bean,如上面的web作用域 else { String scopeName = mbd.getScope(); Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // 通過具體Scope的實現類獲取bean對象 Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { // 首次都還是會創建 return createBean(beanName, mbd, args); } }); } } } // ... }}
總結:Spring Scope Bean是Spring框架中用于管理Bean的作用域的機制,它定義了Bean的生命周期和實例化策略。通過合理地選擇Bean的作用域,可以優化應用的性能和資源利用率。
本文鏈接:http://www.www897cc.com/showinfo-26-57916-0.html玩轉Spring各種作用域Bean Scope及源碼分析
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: MongoDB 大量數據插入時的性能影響及解決方法
下一篇: 2023年最火的前端項目出爐,竟然是它?