多例模式
概念
单例模式指的是每次生成的对象都是同一个
多例模式指的是每次新建的对象都不一样
Spring boot默认注入的服务,都是单例模式
单例模式带来的问题是私有的成员变量(private定义的变量)只能通过@Autowired注入
如果一个类里面有@Autowired,那这个类必须放到SpringBoot容器里
对于一个加入到SpringBoot容器里的bean,使用的时候通过@Autowired注入
单例模式
pring boot默认注入的服务,都是单例模式
单例模式带来的问题是私有的成员变量(private定义的变量)只能通过@Autowired注入
如果一个类里面有@Autowired,那这个类必须放到SpringBoot容器里
对于一个加入到SpringBoot容器里的bean,使用的时候通过@Autowired注入
需求和使用
封装CouponChecker优惠券校验类,不同用户的不同订单都需要用这个类进行校验,那需要多例模式
如果用@Autowired注入,那就是单例模式,相当于每次修改CouponChecker类里的变量,这样不好
最佳使用方式是:new CouponChecker(),将需要的参数传入到CouponChecker类中并赋值到定义的私有变量上
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
一般来说,我们使用@Autowired注入进来的Bean都是单例Bean,如果想注入多例Bean,可以使用ObjectFactory和@Scope俩种方式
ObjectFactory
注入的时候用ObjectFactory包裹
Test.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";
}
@Autowired
private ObjectFactory<Test> test;
@Scope
Test.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";
}
注入
@Autowired
private Test test;
备注
通过ObjectFactory注入的多例对象可以正常通过set方法赋值
通过@Scope注入的多例对象通过调试发现是动态代理类, 无法通过set方法赋值
推测下原因:因为开启多例,又开启了CGLIb的代理模式,
那么就会出现这种问题:
由于开启了代理,所以每次访问this.test获取到的都是不同对象
this.test.set是对A实例赋值
而this.test.get 相当于获取了另外一个实例B的属性值