当前位置: 首页 > article >正文

【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;
        }
    }

五、启动测试

在这里插入图片描述
在这里插入图片描述


http://www.kler.cn/a/9833.html

相关文章:

  • git命令及原理
  • day60 图论章节刷题Part10(Floyd 算法、A * 算法)
  • 16S,18S引物覆盖度测试:SILVA和PR2
  • 【ARM】MDK-烧录配置文件无权限访问
  • JS之正则表达式
  • 【初阶数据结构与算法】线性表之链表的分类以及双链表的定义与实现
  • 使用国密SSL证书,实现SSL/TLS传输层国密改造
  • 【你听说了吗】GPT-5据说已经学完了世界上现存所有的视频
  • 电脑自动录屏软件哪个好用 电脑自动录屏怎么设置
  • 剑指 Offer 49. 丑数
  • 【计算机组成原理 - 第二章】系统总线(完结)
  • css 导航栏效果
  • ICPC SWERC 2020 K - Unique Activities(SAM记录子串第一次结束位置 or SAM + hash)
  • ​分析新特征背后的内在逻辑,才能把握未来一段时期的科技发展新方向
  • Python 进阶指南(编程轻松进阶):十二、使用 Git 组织您的代码项目
  • springboot项目配置文件不允许出现明文密码的解决方法(jasypt使用方法)
  • ChatGPT 指令大全
  • 力扣119杨辉三角 II:代码实现 + 方法总结(数学规律法 记忆法/备忘录)
  • 低静态电流-汽车电池反向保护系统的方法
  • 第八章 Vite4+Vue3+Vtkjs 完整demo演示
  • 刘二大人《Pytorch深度学习实践》第十一讲卷积神经网络(高级篇)
  • 工厂模式白话 - 3种都有哦
  • C语言——变参函数
  • 为什么Java8不使用CMS作为默认垃圾收集器
  • 死锁的检测和案例
  • Qt使用std::thread更新QPlainTextEdit内容