<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      解析Spring內置作用域及其在實踐中的應用

      摘要:本文詳細解析了Spring的內置作用域,包括Singleton、Prototype、Request、Session、Application和WebSocket作用域,并通過實例講解了它們在實際開發中的應用。

      本文分享自華為云社區《Spring高手之路4——深度解析Spring內置作用域及其在實踐中的應用》,作者:磚業洋__ 。

      本文詳細解析了Spring的內置作用域,包括Singleton、Prototype、Request、Session、Application和WebSocket作用域,并通過實例講解了它們在實際開發中的應用。特別是Singleton和Prototype作用域,我們深入討論了它們的定義、用途以及如何處理相關的線程安全問題。通過閱讀本文,讀者可以更深入地理解Spring作用域,并在實際開發中更有效地使用

      1. Spring的內置作用域

      我們來看看Spring內置的作用域類型。在5.x版本中,Spring內置了六種作用域:

      • singleton:在IOC容器中,對應的Bean只有一個實例,所有對它的引用都指向同一個對象。這種作用域非常適合對于無狀態的Bean,比如工具類或服務類。
      • prototype:每次請求都會創建一個新的Bean實例,適合對于需要維護狀態的Bean。
      • request:在Web應用中,為每個HTTP請求創建一個Bean實例。適合在一個請求中需要維護狀態的場景,如跟蹤用戶行為信息。
      • session:在Web應用中,為每個HTTP會話創建一個Bean實例。適合需要在多個請求之間維護狀態的場景,如用戶會話。
      • application:在整個Web應用期間,創建一個Bean實例。適合存儲全局的配置數據等。
      • websocket:在每個WebSocket會話中創建一個Bean實例。適合WebSocket通信場景。

      我們需要重點學習兩種作用域:singleton和prototype。在大多數情況下singleton和prototype這兩種作用域已經足夠滿足需求。

      2. singleton作用域

      2.1 singleton作用域的定義和用途

      Singleton是Spring的默認作用域。在這個作用域中,Spring容器只會創建一個實例,所有對該bean的請求都將返回這個唯一的實例。

      例如,我們定義一個名為Plaything的類,并將其作為一個bean:

      @Component
      public class Plaything {
       public Plaything() {
       System.out.println("Plaything constructor run ...");
       }
      }

      在這個例子中,Plaything是一個singleton作用域的bean。無論我們在應用中的哪個地方請求這個bean,Spring都會返回同一個Plaything實例。

      下面的例子展示了如何創建一個單實例的Bean:

      package com.example.demo.bean;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      @Component
      public class Kid {
       private Plaything plaything;
       @Autowired
       public void setPlaything(Plaything plaything) {
       this.plaything = plaything;
       }
       public Plaything getPlaything() {
       return plaything;
       }
      }
      package com.example.demo.bean;
      import org.springframework.stereotype.Component;
      @Component
      public class Plaything {
       public Plaything() {
       System.out.println("Plaything constructor run ...");
       }
      }

      這里可以在Plaything類加上@Scope(BeanDefinition.SCOPE_SINGLETON),但是因為是默認作用域是Singleton,所以沒必要加。

      package com.example.demo.configuration;
      import com.example.demo.bean.Kid;
      import com.example.demo.bean.Plaything;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      @Configuration
      public class BeanScopeConfiguration {
       @Bean
       public Kid kid1(Plaything plaything1) {
       Kid kid = new Kid();
       kid.setPlaything(plaything1);
       return kid;
       }
       @Bean
       public Kid kid2(Plaything plaything2) {
       Kid kid = new Kid();
       kid.setPlaything(plaything2);
       return kid;
       }
      }
      package com.example.demo.application;
      import com.example.demo.bean.Kid;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      import org.springframework.context.annotation.ComponentScan;
      @SpringBootApplication
      @ComponentScan("com.example")
      public class DemoApplication {
       public static void main(String[] args) {
       ApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
       context.getBeansOfType(Kid.class).forEach((name, kid) -> {
       System.out.println(name + " : " + kid.getPlaything());
       });
       }
      }

      在Spring IoC容器的工作中,掃描過程只會創建bean的定義,真正的bean實例是在需要注入或者通過getBean方法獲取時才會創建。這個過程被稱為bean的初始化。

      這里運行 ctx.getBeansOfType(Kid.class).forEach((name, kid) -> System.out.println(name + " : " + kid.getPlaything())); 時,Spring IoC容器會查找所有的Kid類型的bean定義,然后為每一個找到的bean定義創建實例(如果這個bean定義還沒有對應的實例),并注入相應的依賴。

      運行結果:

      三個 Kid 的 Plaything bean是相同的,說明默認情況下 Plaything 是一個單例bean,整個Spring應用中只有一個 Plaything bean被創建。

      為什么會有3個kid?

      1. Kid: 這個是通過在Kid類上標注的@Component注解自動創建的。Spring在掃描時發現這個注解,就會自動在IOC容器中注冊這個bean。這個Bean的名字默認是將類名的首字母小寫kid。
      2. kid1: 在 BeanScopeConfiguration 中定義,通過kid1(Plaything plaything1)方法創建,并且注入了plaything1。
      3. kid2: 在 BeanScopeConfiguration 中定義,通過kid2(Plaything plaything2)方法創建,并且注入了plaything2。

      2.2 singleton作用域線程安全問題

      需要注意的是,雖然singleton Bean只會有一個實例,但Spring并不會解決其線程安全問題,開發者需要根據實際場景自行處理。

      我們通過一個代碼示例來說明在多線程環境中出現singleton Bean的線程安全問題。

      首先,我們創建一個名為Counter的singleton Bean,這個Bean有一個count變量,提供increment方法來增加count的值:

      package com.example.demo.bean;
      import org.springframework.stereotype.Component;
      @Component
      public class Counter {
       private int count = 0;
       public int increment() {
       return ++count;
       }
      }

      然后,我們創建一個名為CounterService的singleton Bean,這個Bean依賴于Counter,在increaseCount方法中,我們調用counter.increment方法:

      package com.example.demo.service;
      import com.example.demo.bean.Counter;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      @Service
      public class CounterService {
      @Autowired
       private final Counter counter;
       public void increaseCount() {
       counter.increment();
       }
      }
      我們在多線程環境中調用counterService.increaseCount方法時,就可能出現線程安全問題。因為counter.increment方法并非線程安全,多個線程同時調用此方法可能會導致count值出現預期外的結果。

      要解決這個問題,我們需要使counter.increment方法線程安全。

      這里可以使用原子變量,在Counter類中,我們可以使用AtomicInteger來代替int類型的count,因為AtomicInteger類中的方法是線程安全的,且其性能通常優于synchronized關鍵字。

      package com.example.demo.bean;
      import org.springframework.stereotype.Component;
      import java.util.concurrent.atomic.AtomicInteger;
      @Component
      public class Counter {
       private AtomicInteger count = new AtomicInteger(0);
       public int increment() {
       return count.incrementAndGet();
       }
      }

      盡管優化后已經使Counter類線程安全,但在設計Bean時,我們應該盡可能地減少可變狀態。這是因為可變狀態使得并發編程變得復雜,而無狀態的Bean通常更容易理解和測試。

      什么是無狀態的Bean呢? 如果一個Bean不持有任何狀態信息,也就是說,同樣的輸入總是會得到同樣的輸出,那么這個Bean就是無狀態的。反之,則是有狀態的Bean。

      3. prototype作用域

      3.1 prototype作用域的定義和用途

      在prototype作用域中,Spring容器會為每個請求創建一個新的bean實例。

      例如,我們定義一個名為Plaything的類,并將其作用域設置為prototype:

      package com.example.demo.bean;
      import org.springframework.beans.factory.config.BeanDefinition;
      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Component;
      @Component
      @Scope(BeanDefinition.SCOPE_PROTOTYPE)
      public class Plaything {
       public Plaything() {
       System.out.println("Plaything constructor run ...");
       }
      }

      在這個例子中,Plaything是一個prototype作用域的bean。每次我們請求這個bean,Spring都會創建一個新的Plaything實例。

      我們只需要修改上面的Plaything類,其他的類不用動。

      打印結果:

      這個@Scope(BeanDefinition.SCOPE_PROTOTYPE)可以寫成@Scope("prototype"),按照規范,還是利用已有的常量比較好。

      3.2 prototype作用域在開發中的例子

      以我個人來說,我在excel多線程上傳的時候用到過這個,當時是EasyExcel框架,我給一部分關鍵代碼展示一下如何在Spring中使用prototype作用域來處理多線程環境下的任務(實際業務會更復雜),大家可以對比,如果用prototype作用域和使用new對象的形式在實際開發中有什么區別。

      使用prototype作用域的例子

      @Resource
      private ApplicationContext context;
      @PostMapping("/user/upload")
      public ResultModel upload(@RequestParam("multipartFile") MultipartFile multipartFile) {
      ......
      ExecutorService es = new ThreadPoolExceutor(10, 16, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(2000), new ThreadPoolExecutor.CallerRunPolicy());
      ......
      EasyExcel.read(multipartFile.getInputStream(), UserDataUploadVO.class, 
      new PageReadListener<UserDataUploadVO>(dataList ->{
      ......
      // 多線程處理上傳excel數據
      Future<?> future = es.submit(context.getBean(AsyncUploadHandler.class, user, dataList, errorCount));
      ......
      })).sheet().doRead();
      ......
      }

      AsyncUploadHandler.java

      @Component
      @Scope(BeanDefinition.SCOPE_PROTOTYPE)
      public class AsyncUploadHandler implements Runnable {
      private User user;
      
      private List<UserDataUploadVO> dataList;
      
      private AtomicInteger errorCount;
      
      @Resource
      private RedisService redisService;
      
      ......
      
      @Resource
      private CompanyManagementMapper companyManagementMapper;
      public AsyncUploadHandler(user, List<UserDataUploadVO> dataList, AtomicInteger errorCount) {
      this.user = user;
      this.dataList = dataList;
      this.errorCount = errorCount;
      }
      @Override
      public void run() {
      ......
      }
      
      ......
      }
      AsyncUploadHandler類是一個prototype作用域的bean,它被用來處理上傳的Excel數據。由于并發上傳的每個任務可能需要處理不同的數據,并且可能需要在不同的用戶上下文中執行,因此每個任務都需要有自己的AsyncUploadHandler bean。這就是為什么需要將AsyncUploadHandler定義為prototype作用域的原因。

      由于AsyncUploadHandler是由Spring管理的,我們可以直接使用@Resource注解來注入其他的bean,例如RedisService和CompanyManagementMapper。

      把AsyncUploadHandler交給Spring容器管理,里面依賴的容器對象可以直接用@Resource注解注入。如果采用new出來的對象,那么這些對象只能從外面注入好了再傳入進去。

      不使用prototype作用域改用new對象的例子

      @PostMapping("/user/upload")
      public ResultModel upload(@RequestParam("multipartFile") MultipartFile multipartFile) {
      ......
      ExecutorService es = new ThreadPoolExceutor(10, 16, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(2000), new ThreadPoolExecutor.CallerRunPolicy());
      ......
      EasyExcel.read(multipartFile.getInputStream(), UserDataUploadVO.class, 
      new PageReadListener<UserDataUploadVO>(dataList ->{
      ......
      // 多線程處理上傳excel數據
      Future<?> future = es.submit(new AsyncUploadHandler(user, dataList, errorCount, redisService, companyManagementMapper));
      ......
      })).sheet().doRead();
      ......
      }

      AsyncUploadHandler.java

      public class AsyncUploadHandler implements Runnable {
      private User user;
      
      private List<UserDataUploadVO> dataList;
      
      private AtomicInteger errorCount;
      
      private RedisService redisService;
      
      private CompanyManagementMapper companyManagementMapper;
      
      ......
      public AsyncUploadHandler(user, List<UserDataUploadVO> dataList, AtomicInteger errorCount, 
      RedisService redisService, CompanyManagementMapper companyManagementMapper) {
      this.user = user;
      this.dataList = dataList;
      this.errorCount = errorCount;
      this.redisService = redisService;
      this.companyManagementMapper = companyManagementMapper;
      }
      @Override
      public void run() {
      ......
      }
      
      ......
      }

      如果直接新建AsyncUploadHandler對象,則需要手動傳入所有的依賴,這會使代碼變得更復雜更難以管理,而且還需要手動管理AsyncUploadHandler的生命周期。

      4. request作用域(了解)

      request作用域:Bean在一個HTTP請求內有效。當請求開始時,Spring容器會為每個新的HTTP請求創建一個新的Bean實例,這個Bean在當前HTTP請求內是有效的,請求結束后,Bean就會被銷毀。如果在同一個請求中多次獲取該Bean,就會得到同一個實例,但是在不同的請求中獲取的實例將會不同。

      @Component
      @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
      public class RequestScopedBean {
       // 在一次Http請求內共享的數據
       private String requestData;
       public void setRequestData(String requestData) {
       this.requestData = requestData;
       }
       public String getRequestData() {
       return this.requestData;
       }
      }

      上述Bean在一個HTTP請求的生命周期內是一個單例,每個新的HTTP請求都會創建一個新的Bean實例。

      5. session作用域(了解)

      session作用域:Bean是在同一個HTTP會話(Session)中是單例的。也就是說,從用戶登錄開始,到用戶退出登錄(或者Session超時)結束,這個過程中,不管用戶進行了多少次HTTP請求,只要是在同一個會話中,都會使用同一個Bean實例。

      @Component
      @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
      public class SessionScopedBean {
       // 在一個Http會話內共享的數據
       private String sessionData;
       public void setSessionData(String sessionData) {
       this.sessionData = sessionData;
       }
       public String getSessionData() {
       return this.sessionData;
       }
      }

      這樣的設計對于存儲和管理會話級別的數據非常有用,例如用戶的登錄信息、購物車信息等。因為它們是在同一個會話中保持一致的,所以使用session作用域的Bean可以很好地解決這個問題。

      但是實際開發中沒人這么干,會話id都會存在數據庫,根據會話id就能在各種表中獲取數據,避免頻繁查庫也是把關鍵信息序列化后存在Redis。

      6. application作用域(了解)

      application作用域:在整個Web應用的生命周期內,Spring容器只會創建一個Bean實例。這個Bean在Web應用的生命周期內都是有效的,當Web應用停止后,Bean就會被銷毀。

      @Component
      @Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
      public class ApplicationScopedBean {
       // 在整個Web應用的生命周期內共享的數據
       private String applicationData;
       public void setApplicationData(String applicationData) {
       this.applicationData = applicationData;
       }
       public String getApplicationData() {
       return this.applicationData;
       }
      }

      如果在一個application作用域的Bean上調用setter方法,那么這個變更將對所有用戶和會話可見。后續對這個Bean的所有調用(包括getter和setter)都將影響到同一個Bean實例,后面的調用會覆蓋前面的狀態。

      7. websocket作用域(了解)

      websocket作用域:Bean 在每一個新的 WebSocket 會話中都會被創建一次,就像 session 作用域的 Bean 在每一個 HTTP 會話中都會被創建一次一樣。這個Bean在整個WebSocket會話內都是有效的,當WebSocket會話結束后,Bean就會被銷毀。

      @Component
      @Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
      public class WebSocketScopedBean {
       // 在一個WebSocket會話內共享的數據
       private String socketData;
       public void setSocketData(String socketData) {
       this.socketData = socketData;
       }
       public String getSocketData() {
       return this.socketData;
       }
      }

      上述Bean在一個WebSocket會話的生命周期內是一個單例,每個新的WebSocket會話都會創建一個新的Bean實例。

      這個作用域需要Spring Websocket模塊支持,并且應用需要配置為使用websocket。

       

      點擊關注,第一時間了解華為云新鮮技術~

      posted @ 2023-06-15 14:01  華為云開發者聯盟  閱讀(80)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 日本久久一区二区免高清| 国产精品亚洲二区在线播放| 2021亚洲va在线va天堂va国产 | 女人腿张开让男人桶爽| 日本免费一区二区三区日本| 国产精品国产三级国快看| 国产欧美日韩在线在线播放| 4虎四虎永久在线精品免费| 亚洲精品成人久久av| 特级精品毛片免费观看| 国产亚洲色视频在线| 国产成人久久精品二三区| 亚洲日韩VA无码中文字幕| 久久99热只有频精品8| 日韩V欧美V中文在线| 动漫AV纯肉无码AV电影网| 亚洲日韩中文字幕在线播放| 色www视频永久免费| 日本精品一区二区不卡| 国产95在线 | 亚洲| 久久精品蜜芽亚洲国产AV| 日韩一区二区三区水蜜桃| 午夜片神马影院福利| 美女禁区a级全片免费观看| 国产线播放免费人成视频播放| 久久精品国产亚洲AV麻| 色综合天天综合网天天看片| 久久精品av国产一区二区 | 国产熟女精品一区二区三区| 男人的天堂av一二三区| 国产老熟女乱子一区二区| 国产四虎永久免费观看| 老司机午夜精品视频资源| 久久久久成人精品无码中文字幕| 国产亚洲av嫩草久久| 日本伊人色综合网| 人妻少妇精品系列| 亚洲国产在一区二区三区| 熟妇的奶头又大又长奶水视频| 国色天香成人一区二区| 日韩av中文字幕有码|