毫无疑问的,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后端到全栈》视频课程