Skip to content

多例模式

概念

单例模式指的是每次生成的对象都是同一个

多例模式指的是每次新建的对象都不一样

Spring boot默认注入的服务,都是单例模式

单例模式带来的问题是私有的成员变量(private定义的变量)只能通过@Autowired注入

如果一个类里面有@Autowired,那这个类必须放到SpringBoot容器里

对于一个加入到SpringBoot容器里的bean,使用的时候通过@Autowired注入

单例模式

pring boot默认注入的服务,都是单例模式

单例模式带来的问题是私有的成员变量(private定义的变量)只能通过@Autowired注入

如果一个类里面有@Autowired,那这个类必须放到SpringBoot容器里

对于一个加入到SpringBoot容器里的bean,使用的时候通过@Autowired注入

需求和使用

项目:misszb/OrderServiceImpl

封装CouponChecker优惠券校验类,不同用户的不同订单都需要用这个类进行校验,那需要多例模式

如果用@Autowired注入,那就是单例模式,相当于每次修改CouponChecker类里的变量,这样不好

最佳使用方式是:new CouponChecker(),将需要的参数传入到CouponChecker类中并赋值到定义的私有变量上

java
package com.lin.missyou.logic;

import com.lin.missyou.bo.SkuOrderBO;
import com.lin.missyou.core.enumeration.CouponType;
import com.lin.missyou.core.money.IMoneyDiscount;
import com.lin.missyou.exception.http.ForbiddenException;
import com.lin.missyou.exception.http.ParameterException;
import com.lin.missyou.model.Category;
import com.lin.missyou.model.Coupon;
import com.lin.missyou.util.CommonUtil;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

public class CouponChecker {

    private Coupon coupon;

    private IMoneyDiscount iMoneyDiscount;

    public CouponChecker(Coupon coupon,IMoneyDiscount iMoneyDiscount) {
        this.coupon = coupon;
        this.iMoneyDiscount = iMoneyDiscount;
    }

    public void isOk() {
        Date now = new Date();
        Boolean isInTimeline = CommonUtil.isInTimeLine(now, this.coupon.getStartTime(), this.coupon.getEndTime());
        if (!isInTimeline) {
            throw new ForbiddenException(40007);
        }
    }

    //Mark
    public void finalTotalPriceIsOk(List<SkuOrderBO> skuOrderBOList, BigDecimal orderFinalTotalPrice,
                                    BigDecimal serverTotalPrice) {
        BigDecimal serverFinalTotalPrice;

        switch (CouponType.toType(this.coupon.getType())) {
            case FULL_MINUS:
            case NO_THRESHOLD_MINUS:
                serverFinalTotalPrice = serverTotalPrice.subtract(this.coupon.getMinus());
                if (serverFinalTotalPrice.compareTo(new BigDecimal("0")) <= 0) {
                    throw new ForbiddenException(50008);
                }
                break;
            case FULL_OFF:
                BigDecimal orderCategoryPrice = this.canBeUsed(skuOrderBOList, serverTotalPrice);
                BigDecimal discountPrice = this.iMoneyDiscount.discount(orderCategoryPrice,  new BigDecimal(1).subtract(this.coupon.getRate()));
                serverFinalTotalPrice = serverTotalPrice.subtract(discountPrice);
                break;
            default:
                throw new ParameterException(40009);
        }
        int compare = serverFinalTotalPrice.compareTo(orderFinalTotalPrice);
        if (compare != 0) {
            throw new ForbiddenException(50008);
        }
    }

    public BigDecimal canBeUsed(List<SkuOrderBO> skuOrderBOList, BigDecimal serverTotalPrice) {
        // 订单中满足优惠条件的金额
        BigDecimal orderCategoryPrice;

        if(this.coupon.getWholeStore()){
            orderCategoryPrice = serverTotalPrice;
        }
        else{
            List<Long> cidList = this.coupon.getCategoryList()
                    .stream()
                    .map(Category::getId)
                    .collect(Collectors.toList());
            orderCategoryPrice = this.getSumByCategoryList(skuOrderBOList, cidList);
        }
        this.couponCanBeUsed(orderCategoryPrice);
        return orderCategoryPrice;
    }

    private void couponCanBeUsed(BigDecimal orderCategoryPrice) {
        switch (CouponType.toType(this.coupon.getType())){
            case FULL_OFF:
            case FULL_MINUS:
                // 不够满减条件
                int compare = this.coupon.getFullMoney().compareTo(orderCategoryPrice);
                if(compare > 0){
                    throw new ParameterException(40008);
                }
                break;
            case NO_THRESHOLD_MINUS:
                break;
            default:
                throw new ParameterException(40009);
        }
    }

    private BigDecimal getSumByCategoryList(List<SkuOrderBO> skuOrderBOList, List<Long> cidList) {
        BigDecimal sum = cidList.stream()
                .map(cid -> this.getSumByCategory(skuOrderBOList, cid))
                .reduce(BigDecimal::add)
                .orElse(new BigDecimal("0"));
        return sum;
    }

    private BigDecimal getSumByCategory(List<SkuOrderBO> skuOrderBOList, Long cid) {
        BigDecimal sum = skuOrderBOList.stream()
                .filter(sku -> sku.getCategoryId().equals(cid))
                .map(SkuOrderBO::getTotalPrice)
                .reduce(BigDecimal::add)
                .orElse(new BigDecimal("0"));
        return sum;
    }
}

注入多例Bean

项目:misszb/TestController

一般来说,我们使用@Autowired注入进来的Bean都是单例Bean,如果想注入多例Bean,可以使用ObjectFactory和@Scope俩种方式

ObjectFactory

注入的时候用ObjectFactory包裹

Test.java

java
package com.zb.misszb.model;

import lombok.Getter;
import lombok.Setter;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
@Scope(value = "prototype")
public class Test {
    private String name = "zhaobao";
}
java
    @Autowired
    private ObjectFactory<Test> test;

@Scope

Test.java

java
package com.zb.misszb.model;

import lombok.Getter;
import lombok.Setter;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Test {
    private String name = "zhaobao";
}

注入

java
    @Autowired
    private Test test;

备注

通过ObjectFactory注入的多例对象可以正常通过set方法赋值

通过@Scope注入的多例对象通过调试发现是动态代理类, 无法通过set方法赋值

推测下原因:因为开启多例,又开启了CGLIb的代理模式,

那么就会出现这种问题:

由于开启了代理,所以每次访问this.test获取到的都是不同对象

this.test.set是对A实例赋值

而this.test.get 相当于获取了另外一个实例B的属性值

如有转载或 CV 的请标注本站原文地址