首页 > 技术文章 > 全栈之路-杂篇-探究SpringBoot多例模式注入的方案

ssh-html 2020-11-29 21:21 原文

  毫无疑问的,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

 

推荐阅读