【spring】通过抽象类与ApplicationContext编写扩展性强的业务逻辑
通过抽象类与ApplicationContext编写扩展性强的业务逻辑
一、场景分析
我们以支付业务为例,用户每一次支付都会经历永远不变的几个过程,例如:对于库存和金额的前置校验、支付后扣减库存,修改订单状态等等。整个流程变的是什么呢,变的只有支付的方式以及金额和商品订单。
我们知道抽象类中可以包含普通方法和抽象方法,普通方法意味着这一套逻辑是定死的,抽象方法意味着这部分的逻辑是可变的,这是不是与我们前面所说的业务流程相对应上了?接下来我们就通过抽象类来模拟一套支付业务,达到扩展性较强的目的。
二、业务抽象
首先对于支付业务必不可少的肯定就是基本的支付参数
import lombok.Data;
@Data
public class PayParam {
private Integer payMode;
private Long ProductId;
private Long orderId;
}
以及支付方式的枚举
@AllArgsConstructor
public enum PayModeEnum {
WECHAT(10,"微信支付"),
ZHIFUBAO(20,"支付宝");
private Integer code;
private String name;
public static String getNameByCode(Integer code){
if (ObjectUtil.isEmpty(code)){
return "";
}
for (PayModeEnum value : PayModeEnum.values()) {
if (value.code.equals(code)){
return value.name;
}
}
return "";
}
}
有了这两个基本的类之后我们就可以去编写支付业务了,首先是整个支付业务的抽象类。整个支付流程都有什么呢?前置校验,支付,扣减库存…
不变的逻辑就是校验,扣减库存这种,所以他们是普通方法,变的是支付的形式,那么就用抽象方法来表示。这里通过日志的形式来模拟业务逻辑,理解即可。
import com.example.springboot.entity.PayParam;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class AbsPayMode {
public void check(PayParam payParam){
log.info("完成订单{}的前置校验",payParam.getOrderId());
}
public abstract void pay(PayParam payParam);
public void change(PayParam payParam){
log.info("完成对商品{}的扣减库存",payParam.getProductId());
log.info("完成订单{}的订单状态修改",payParam.getOrderId());
}
}
编写具体的支付服务去继承支付业务的抽象类
微信支付:
import com.example.springboot.abs.AbsPayMode;
import com.example.springboot.entity.PayParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
public class WeChatService extends AbsPayMode {
@Override
public void pay(PayParam payParam) {
log.info("调用微信支付");
}
}
支付宝支付:
import com.example.springboot.abs.AbsPayMode;
import com.example.springboot.entity.PayParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
public class ZhiFuBaoService extends AbsPayMode {
@Override
public void pay(PayParam payParam) {
log.info("调用支付宝支付");
}
}
三、具体使用
基本的业务流程我们定义完了,该如何使用呢,我们下面来实操一下。
首先肯定就是支付接口controller
import com.example.springboot.entity.PayParam;
import com.example.springboot.service.IPayService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class PayController {
@Resource
private IPayService payService;
@GetMapping("/pay")
private Boolean pay(PayParam payParam){
return payService.pay(payParam);
}
}
service
public interface IPayService {
Boolean pay(PayParam payParam);
}
impl
到这里就要编写具体的业务流程了,问题也随之出现了,我们该如何使用这个抽象类呢?首先肯定不是直接注入,因为没有具体的支付方式,难道注入具体的支付服务吗?那不是和普通的写法没什么区别吗?
注意:这里我们使用spring提供的ApplicationContext配合@Component来实现
观察@Component注解的源码我们可以发现,其实这个注解是有参数的
他可以传入一个value,那么这个value有什么用呢?
我们知道spring是单例模式,也就是交给spring管理的类只有一个实例化的对象,也就是这些对象有且只有一个。
查看源码,ApplicationContext接口继承了ListableBeanFactory,其中有一个方法
大致描述如下:
返回与给定对象类型(包括子类)匹配的 Bean 实例,从 Bean 定义或 getObjectType 的值(如果是 FactoryBeans)判断。
参数:
type – 要匹配的类或接口,或所有具体 bean 的 null
返回:
一个包含匹配 bean 的Map,Bean 名称作为键,相应的 Bean 实例作为值
那这个方法有什么用呢?不要忘了,我们的微信、支付宝支付的业务是继承于整个支付业务抽象类的!我们可以通过这个方法获取所有的支付服务实例!
回到上面说的@Component注解,这回我们就可以隐约感觉到,这个value是给bean命名的,我们对两个支付服务类做一下修改,让他们的类名与枚举类中的code匹配
@Slf4j
@Component("10")
public class WeChatService extends AbsPayMode {
@Override
public void pay(PayParam payParam) {
log.info("调用微信支付");
}
}
@Slf4j
@Component("20")
public class ZhiFuBaoService extends AbsPayMode {
@Override
public void pay(PayParam payParam) {
log.info("调用支付宝支付");
}
}
知道这个逻辑之后就可以利用这个方法来编写一个工具类来获取
@Component
public class SpringFactoryUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringFactoryUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> Map<String, T> getBeanMap(Class<T> clazz) {
return getApplicationContext().getBeansOfType(clazz);
}
}
终于,到这里我们就完成了所有准备工作,可以编写具体业务了
import com.example.springboot.abs.AbsPayMode;
import com.example.springboot.entity.PayParam;
import com.example.springboot.service.IPayService;
import com.example.springboot.utils.SpringFactoryUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import java.util.Map;
@Slf4j
@Service
@DependsOn("springFactoryUtils")
public class PayServiceImpl implements IPayService {
Map<String, AbsPayMode> beanMap = SpringFactoryUtils.getBeanMap(AbsPayMode.class);
@Override
public Boolean pay(PayParam payParam) {
// 按支付类型获取支付业务
AbsPayMode absPayMode = beanMap.get(String.valueOf(payParam.getPayMode()));
try{
// 前置校验
absPayMode.check(payParam);
// 支付
absPayMode.pay(payParam);
// 扣减库存,修改订单状态
absPayMode.change(payParam);
return Boolean.TRUE;
}catch (Exception e){
log.error("出现异常",e);
return Boolean.FALSE;
}
}
}
我们惊讶的发现,就这!??
没错,就这些,只要我们统一了每种支付方式的code,那么这套支付逻辑就可以通用。
需要注意的是:@DependsOn(“springFactoryUtils”),我们需要添加这个注解来保证这个类的实例化在springFactoryUtils之后,否则会出现空指针异常
四、如何扩展
还记得我们的真正目的吗,没错,我们想要整套支付逻辑的扩展性强一些,那么该如何扩展这套业务呢?比如我想加入一个新的支付模式,我想多一步后置校验?其实很简单,我们只需要修改枚举类或者抽象类就行了。
添加新的支付模式:
比如说我想加入京东支付
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum PayModeEnum {
WECHAT(10,"微信支付"),
ZHIFUBAO(20,"支付宝"),
JINGDONG(30,"京东支付");
private Integer code;
private String name;
public static String getNameByCode(Integer code){
if (ObjectUtil.isEmpty(code)){
return "";
}
for (PayModeEnum value : PayModeEnum.values()) {
if (value.code.equals(code)){
return value.name;
}
}
return "";
}
}
@Slf4j
@Component("30")
public class JingDongService extends AbsPayMode {
@Override
public void pay(PayParam payParam) {
log.info("调用京东支付接口");
}
}
这样就实现了,前提是统一规定好code,约定大于配置
添加新的校验:
@Slf4j
public abstract class AbsPayMode {
public void check(PayParam payParam){
log.info("完成订单{}的前置校验",payParam.getOrderId());
}
public abstract void pay(PayParam payParam);
public void change(PayParam payParam){
log.info("完成对商品{}的扣减库存",payParam.getProductId());
log.info("完成订单{}的订单状态修改",payParam.getOrderId());
}
public void checkAfter(PayParam payParam){
log.info("完成订单{}的后置校验",payParam.getOrderId());
}
}
@Override
public Boolean pay(PayParam payParam) {
// 按支付类型获取支付业务
AbsPayMode absPayMode = beanMap.get(String.valueOf(payParam.getPayMode()));
try{
// 前置校验
absPayMode.check(payParam);
// 支付
absPayMode.pay(payParam);
// 扣减库存,修改订单状态
absPayMode.change(payParam);
// 后置校验
absPayMode.checkAfter(payParam);
return Boolean.TRUE;
}catch (Exception e){
log.error("出现异常",e);
return Boolean.FALSE;
}
}
五、启动测试