毫無疑問的,springboot托管的實體類默認是以單例模式的形式進行實例化的,但是在某些場景下,我們需要的不是單例模式的實體類,這個時候我們該如何去實現springboot托管的實體類進行多例模式的創建呢?
一、單例模式存在的問題
1、業務場景介紹
就是我們為什么會有改變springboot默認單例模式的這種創建bean的形式?這個業務場景是這樣的,我們在做優惠券的校驗的時候,我們自己封裝了一個優惠券的校驗類,這樣的話,我們就可以很方便的進行統一的優惠券的校驗了,在做優惠券的校驗的時候,我們會有一個打折的情況,這里會出現一個四舍五入的問題,就會導致數據並不是原來的數據,會有一個偏差,我們到底是采用什么模式進行小數的取舍,這里是值得探討的,但是我們這里的處理方式是將折扣價的計算封裝成了一個接口,這樣我們寫了幾個實現類,有四舍五入的方式,有直接舍去的方式,有銀行家算法的方式,我們在使用的時候直接使用實現類來計算就好,在代碼中我們是這樣實現的:
接口類:
1 // 接口類 2 public interface IMoneyDiscount { 3 BigDecimal discount(BigDecimal original, BigDecimal discount); 4 }
第一個實現類(四舍五入):
1 public class HalfUpRound implements IMoneyDiscount{ 2 3 @Override 4 public BigDecimal discount(BigDecimal original, BigDecimal discount) { 5 BigDecimal actualMoney = original.multiply(discount); 6 BigDecimal finalMoney = actualMoney.setScale(2, RoundingMode.HALF_UP); 7 return finalMoney; 8 } 9 }
第二個實現類(向上取整):
1 public class UpRound implements IMoneyDiscount{ 2 @Override 3 public BigDecimal discount(BigDecimal original, BigDecimal discount) { 4 BigDecimal actualMoney = original.multiply(discount); 5 BigDecimal finalMoney = actualMoney.setScale(2, RoundingMode.UP); 6 return finalMoney; 7 } 8 }
第三個實現類(銀行家算法):
1 public class HalfEvenRound implements IMoneyDiscount{ 2 @Override 3 public BigDecimal discount(BigDecimal original, BigDecimal discount) { 4 BigDecimal actualMoney = original.multiply(discount); 5 BigDecimal finalMoney = actualMoney.setScale(2, RoundingMode.HALF_EVEN); 6 return finalMoney; 7 } 8 }
我們在做訂單校驗的時候,需要對優惠券進行校驗,在做優惠券校驗的時候,我們構建了一個CouponChecker的優惠券校驗類,在這個類中我們會做訂單金額的校驗,當然可以直接使用new對象的方法,來使用折扣計算的方法,但是我們這里使用的是注入的方式來優化這個,但是這樣就存在問題了,我們需要的是每一個訂單都是有每一個校驗的,這時候單例模式就存在問題了,因為我們是需要Coupon類和UserCoupon類同時作為參數傳入進行CouponChecker類中的,也就是我們通過傳統的new對象的方式進行校驗,但是如果是單例模式的話,這些是沒有辦法使用的。
2、單例模式下的實體類不能有自己私有的成員變量的,即使有的話,那么這些成員變量也必須是單例模式的,所以我們必須解決這個單例模式的問題,來符合我們業務的需要
二、解決方案
1、我們把CouponChecker作為一個不在springboot容器中托管的類,只是作為一個普通類來操作,這樣我們在service層,也就是OrderService類中將IMoneyDiscount的實現類作為參數傳入到CouponChecker中,這樣可以解決那個單例模式存在的問題,代碼如下:
(1)OrderService類,來處理order訂單相關的邏輯類:
1 @Service 2 public class OrderService { 3 // 省略部分代碼...... 4 5 @Autowired 6 private IMoneyDiscount iMoneyDiscount; 7 8 /** 9 * 訂單校驗的主方法 10 * 11 * @param uid 用戶id 12 * @param orderDTO 訂單相關數據 13 */ 14 public void isOk(Long uid, OrderDTO orderDTO) { 15 // 省略部分代碼...... 16 // 數據的校驗 17 CouponChecker couponChecker = new CouponChecker(coupon, userCoupon, this.iMoneyDiscount); 18 19 }
(2)CouponChecker類,來進行優惠券的校驗
1 // @Service 不需要在這里讓springboot容器托管 2 public class CouponChecker { 3 4 // @Autowired 5 private IMoneyDiscount iMoneyDiscount; 6 7 private Coupon coupon; 8 private UserCoupon userCoupon; 9 // 這里在構造方法中進行傳入進來 10 public CouponChecker(Coupon coupon, UserCoupon userCoupon, IMoneyDiscount iMoneyDiscount) { 11 this.coupon = coupon; 12 this.userCoupon = userCoupon; 13 this.iMoneyDiscount = iMoneyDiscount; 14 } 15 // 省略部分校驗方法邏輯代碼...... 16 }
這種方法是可以解決我們上面提到的那個單例模式存在的問題的,但是這種方法可能不是很完美,我們還有更好的解決方法
2、使用@Scope注解
這里具體的應用方案好像在這里並不是很實用,具體使用方法我想了一下,不知道怎么使用!!!講一下@Scope注解是怎么使用的吧
(1)實體類代碼
1 @Getter 2 @Setter 3 @Component 4 @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) 5 public class Test { 6 private String name; 7 }
(2)controller層代碼
1 @RestController 2 @RequestMapping(value = "/test") 3 public class TestController { 4 5 @Autowired 6 private Test test; 7 8 @GetMapping(value = "") 9 public void getDetail() { 10 System.out.println(this.test); 11 } 12 }
注意:這里只是@Scope注解的一個使用,具體怎么在那個業務場景下使用,我這里還暫時沒有想到很好的辦法!!!
內容出處:七月老師《從Java后端到全棧》視頻課程
七月老師課程鏈接:https://class.imooc.com/sale/javafullstack