<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用spring的時候,我們一般都是使用@Component實現bean的注入,這個時候我們的bean如果不指定@Scope,預設是單例模式,另外還有很多模式可用,用的最多的就是多例模式了,顧名思義就是每次使用都會建立一個新的物件,比較適用於寫一些job,比如在多執行緒環境下可以使用全域性變數之類的
建立一個測試任務,這裡在網上看到大部分都是直接@Scope(“prototype”),這裡測試是不生效的,再加上proxyMode才行,程式碼如下
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS) public class TestAsyncClient { private int a = 0; @Async public void test(int a) { this.a = a; CommonAsyncJobs.list.add(this.a + ""); } }
測試
import cn.hutool.core.collection.CollectionUtil; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.test.context.junit4.SpringRunner; import java.util.Set; import java.util.Vector; @Slf4j @EnableAsync @SpringBootTest @RunWith(SpringRunner.class) public class CommonAsyncJobs { @Autowired private TestAsyncClient testAsyncClient; // 多執行緒環境下,普通的list.add不適用,用Vector處理就行了,效率低,但無所謂反正測試的 public static Vector<String> list = new Vector<>(); @Test public void testAsync() throws Exception { // 迴圈裡面非同步處理 int a = 100000; for (int i = 0; i < a; i++) { testAsyncClient.test(i); } System.out.println("多執行緒結果:" + list.size()); System.out.println("單執行緒結果:" + a); Set<String> set = CollectionUtil.newHashSet(); Set<String> exist = CollectionUtil.newHashSet(); for (String s : list) { if (set.contains(s)) { exist.add(s); } else { set.add(s); } } // 沒重複的值,說明多執行緒環境下,全域性變數沒有問題 System.out.println("重複的值:" + exist.size()); System.out.println("重複的值:" + exist); // 單元測試內主執行緒結束會終止子執行緒任務 Thread.sleep(Long.MAX_VALUE); } }
結果
沒重複的值,說明多執行緒環境下,全域性變數沒有問題
在使用了Spring的web工程中,除非特殊情況,我們都會選擇使用Spring的IOC功能來管理Bean,而不是用到時去new一個。
Spring管理的Bean預設是單例的(即Spring建立好Bean,需要時就拿來用,而不是每次用到時都去new,又快效能又好),但有時候單例並不滿足要求(比如Bean中不全是方法,有成員,使用單例會有執行緒安全問題,可以搜尋執行緒安全與執行緒不安全的相關文章),你上網可以很容易找到解決辦法,即使用@Scope("prototype")註解,可以通知Spring把被註解的Bean變成多例
如下所示:
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/testScope") public class TestScope { private String name; @RequestMapping(value = "/{username}",method = RequestMethod.GET) public void userProfile(@PathVariable("username") String username) { name = username; try { for(int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getId() + "name:" + name); Thread.sleep(2000); } } catch (Exception e) { e.printStackTrace(); } return; } }
分別傳送請求http://localhost:8043/testScope/aaa和http://localhost:8043/testScope/bbb,控制檯輸出:
34name:aaa
34name:aaa
35name:bbb
34name:bbb
(34和35是兩個執行緒的ID,每次執行都可能不同,但是兩個請求使用的執行緒的ID肯定不一樣,可以用來區分兩個請求。)可以看到第二個請求bbb開始後,將name的內容改為了“bbb”,第一個請求的name也從“aaa”改為了“bbb”。要想避免這種情況,可以使用@Scope("prototype"),註解加在TestScope這個類上。加完註解後重覆上面的請求,發現第一個請求一直輸出“aaa”,第二個請求一直輸出“bbb”,成功。
多個Bean的依賴鏈中,有一個需要多例
第一節中是一個很簡單的情況,真實的Spring Web工程起碼有Controller、Service、Dao三層,假如Controller層是單例,Service層需要多例,這時候應該怎麼辦呢?
2.1一次失敗的嘗試
首先我們想到的是在Service層加註解@Scope("prototype"),如下所示:
controller類程式碼
import com.example.test.service.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/testScope") public class TestScope { @Autowired private Order order; private String name; @RequestMapping(value = "/{username}", method = RequestMethod.GET) public void userProfile(@PathVariable("username") String username) { name = username; order.setOrderNum(name); try { for (int i = 0; i < 100; i++) { System.out.println( Thread.currentThread().getId() + "name:" + name + "--order:" + order.getOrderNum()); Thread.sleep(2000); } } catch (Exception e) { e.printStackTrace(); } return; } }
Service類程式碼
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; @Service @Scope("prototype") public class Order { private String orderNum; public String getOrderNum() { return orderNum; } public void setOrderNum(String orderNum) { this.orderNum = orderNum; } @Override public String toString() { return "Order{" + "orderNum='" + orderNum + ''' + '}'; } }
分別傳送請求http://localhost:8043/testScope/aaa和http://localhost:8043/testScope/bbb,控制檯輸出:
32name:aaa--order:aaa
32name:aaa--order:aaa
34name:bbb--order:bbb
32name:bbb--order:bbb
可以看到Controller的name和Service的orderNum都被第二個請求從“aaa”改成了“bbb”,Service並不是多例,失敗。
2.2 一次成功的嘗試
我們再次嘗試,在Controller和Service都加上@Scope("prototype"),結果成功,這裡不重複貼程式碼,讀者可以自己試試。
2.3 成功的原因(對2.1、2.2的理解)
Spring定義了多種作用域,可以基於這些作用域建立bean,包括:
對於以上說明,我們可以這樣理解:雖然Service是多例的,但是Controller是單例的。如果給一個元件加上@Scope("prototype")註解,每次請求它的範例,spring的確會給返回一個新的。問題是這個多例物件Service是被單例物件Controller依賴的。而單例服務Controller初始化的時候,多例物件Service就已經注入了;當你去使用Controller的時候,Service也不會被再次建立了(注入時建立,而注入只有一次)。
2.4 另一種成功的嘗試(基於2.3的猜想)
為了驗證2.3的猜想,我們在Controller鍾每次去請求獲取Service範例,而不是使用@Autowired注入,程式碼如下:
Controller類
import com.example.test.service.Order; import com.example.test.utils.SpringBeanUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/testScope") public class TestScope { private String name; @RequestMapping(value = "/{username}", method = RequestMethod.GET) public void userProfile(@PathVariable("username") String username) { name = username; Order order = SpringBeanUtil.getBean(Order.class); order.setOrderNum(name); try { for (int i = 0; i < 100; i++) { System.out.println( Thread.currentThread().getId() + "name:" + name + "--order:" + order.getOrderNum()); Thread.sleep(2000); } } catch (Exception e) { e.printStackTrace(); } return; } }
用於獲取Spring管理的Bean的類
package com.example.test.utils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringBeanUtil implements ApplicationContextAware { /** * 上下文物件範例 */ private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * 獲取applicationContext * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 通過name獲取 Bean. * * @param name * @return */ public static Object getBean(String name) { return getApplicationContext().getBean(name); } /** * 通過class獲取Bean. * * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } /** * 通過name,以及Clazz返回指定的Bean * * @param name * @param clazz * @param <T> * @return */ public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } }
Order的程式碼不變。
分別傳送請求http://localhost:8043/testScope/aaa和http://localhost:8043/testScope/bbb,控制檯輸出:
31name:aaa--order:aaa
33name:bbb--order:bbb
31name:bbb--order:aaa
33name:bbb--order:bbb
可以看到,第二次請求的不會改變第一次請求的name和orderNum。問題解決。我們在2.3節中給出的的理解是對的。
雖然第二節解決了問題,但是有兩個問題:
Spring作為一個優秀的、用途廣、發展時間長的框架,一定有成熟的解決辦法。經過一番搜尋,我們發現,註解@Scope("prototype")(這個註解實際上也可以寫成@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,使用常數比手打字串不容易出錯)還有很多用法。
首先value就分為四類:
ConfigurableBeanFactory.SCOPE_PROTOTYPE
,即“prototype”ConfigurableBeanFactory.SCOPE_SINGLETON
,即“singleton”WebApplicationContext.SCOPE_REQUEST
,即“request”WebApplicationContext.SCOPE_SESSION
,即“session”他們的含義是:
singleton
和prototype分別代表單例和多例;request
表示請求,即在一次http請求中,被註解的Bean都是同一個Bean,不同的請求是不同的Bean;session
表示對談,即在同一個對談中,被註解的Bean都是使用的同一個Bean,不同的對談使用不同的Bean。使用session和request產生了一個新問題,生成controller的時候需要service作為controller的成員,但是service只在收到請求(可能是request也可能是session)時才會被範例化,controller拿不到service範例。為了解決這個問題,@Scope註解新增了一個proxyMode的屬性,有兩個值ScopedProxyMode.INTERFACES和ScopedProxyMode.TARGET_CLASS,前一個表示表示Service是一個介面,後一個表示Service是一個類。
本文遇到的問題中,將@Scope註解改成@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)就可以了,這裡就不重複貼程式碼了。
問題解決。以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45