从Spring中扒设计模式之模板方法

0、前言

最近在学习Spring相关知识,突然就想着扒一扒在Spring中用到的设计模式,记录一下,与君共勉。

1、在Spring源码中的实现

说到模板方法,在spring中最经典的实现应该是**refresh()**方法,定义了spring容器初始化的整个流程,内部调用的15个方法均可重写。

a、模板定义

spring容器启动的refresh()方法

org.springframework.context.support.AbstractApplicationContext#refresh

在这里插入图片描述

refresh()方法中结构化的调用了spring容器启动的核心方法。AbstractApplicationContext本身是抽象类,不提供实例,只定义基础逻辑流程。

b、模板实现

子类 AnnotationConfigApplicationContext

我们现在所熟知的AnnotationConfigApplicationContext也是继承至AbstractApplicationContext

org.springframework.context.annotation.AnnotationConfigApplicationContext

在这里插入图片描述

在这里插入图片描述
通过继承GenericApplicationContext来继承AbstractApplicationContext的方法,实现javaConfig配置类扫描逻辑

2、照猫画虎

这里用公众号发送模板消息的功能进行示例,公众号发送模板消息,基础逻辑都一样,仅仅是调用不同的模板和配置不同的参数

a、定义

先定义一个接口 WxTemplateMsgService

/**
 * 微信模板消息发送方法
 * @author YearOfTheRain
 * @create 2024-06-17  14:36
 */
public interface WxTemplateMsgService {

    /**
     * 购买成功通知
     */
    boolean buySuccess(BuySuccessTmp buySuccessTmp);

    /**
     * 加入拼团成功通知
     */
    boolean joinGroup(BeginGroupTmp beginGroupTmp);
}

定义消息模板

@Data
@EqualsAndHashCode(callSuper = false)
public class TmpBase implements Serializable {
    private static final long serialVersionUID = 1L;
    //userId,openid 填写其中一个
    /**
     * 接收者userId
     */
    private String userId;
    /**
     * 用户点击消息跳转链接
     */
    private String url;
    /**
     * 接收者openid
     */
    private String openid;

    public String getUrl() {
        if (ObjectUtil.isNull(url)) {
            return "";
        }
        return url;
    }
}

/**
 * 购买成功消息推送模板
 *
 * 模板示例
 * 订单支付成功通知
 *
 * 订单号  G202205230000010
 * 商品名称 矿泉水
 * 支付方式 微信支付
 * 支付金额 200.00
 * 下单时间 2022年11月11日 12:12
 *
 * 点击查看订单
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class BuySuccessTmp extends TmpBase implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 购买时间
     */
    private Date buyTime;
    /**
     * 商品名称
     */
    private String goodsName;
    /**
     * 支付金额
     */
    private BigDecimal paymentAmount;
    /**
     * 付款方式(零售币/积分/微信支付。。)
     */
    private String paymentMethod;

    /** 订单号 */
    private String orderNo;
}

@Data
@EqualsAndHashCode(callSuper = false)
public class BeginGroupTmp extends TmpBase implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 商品名称
     */
    private String goodsName;
    /**
     * 商品价格
     */
    private BigDecimal goodsPrice;
    /**
     * 成团人数
     */
    private int groupAmount;
    /**
     * 组团时间限制 以小时为单位
     */
    private int groupTime;
}

实现消息发送方法:

/**
 * 实现方法
 * @author YearOfTheRain
 * @create 2024-06-17  14:38
 */
@Service("new1WxTemplateMsgServiceImpl")
@Slf4j
public class WxTemplateMsgServiceImpl implements WxTemplateMsgService {

    @Override
    public boolean buySuccess(BuySuccessTmp tmp) {
        AbstractTemplateMessageHandler<BuySuccessTmp> messageHandler = new AbstractTemplateMessageHandler<BuySuccessTmp>() {
            @Override
            WxMpTemplateMessage buildTemplate(BuySuccessTmp temp) {
                WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                        // 接收者openid
                        .toUser(temp.getOpenid())
                        // 模板id  模板标题:支付成功通知
                        .templateId("buySuccess")
                        // 模板跳转链接
                        .url(tmp.getUrl())
                        .build();
                // 添加模板数据
                templateMessage.addData(new WxMpTemplateData("first", "您已成功下单", "#888888"))
                        .addData(new WxMpTemplateData("time4", tmp.getGoodsName(), "#AAAAAA"))
                        .addData(new WxMpTemplateData("amount5", tmp.getPaymentAmount().setScale(2, RoundingMode.DOWN).toPlainString() + "零售币", "#AAAAAA"))
                        .addData(new WxMpTemplateData("thing3", tmp.getPaymentMethod() + "人", "#AAAAAA"))
                        .addData(new WxMpTemplateData("character_string2", tmp.getBuyTime() + "小时", "#AAAAAA"))
                        .addData(new WxMpTemplateData("remark", "详情请点击这里,祝生活愉快", "#0000FF"));
                return templateMessage;
            }
        };
        return messageHandler.send(tmp);
    }

    @Override
    public boolean joinGroup(BeginGroupTmp tmp) {
        AbstractTemplateMessageHandler<BeginGroupTmp> messageHandler = new AbstractTemplateMessageHandler<BeginGroupTmp>() {
            @Override
            WxMpTemplateMessage buildTemplate(BeginGroupTmp temp) {
                WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                        // 接收者openid
                        .toUser(temp.getOpenid())
                        // 模板id  模板标题:开团成功通知
                        .templateId("begingroupSuccess")
                        // 模板跳转链接
                        .url(tmp.getUrl())
                        .build();
                // 添加模板数据
                templateMessage.addData(new WxMpTemplateData("first", "您已成功加入一个拼团", "#888888"))
                        .addData(new WxMpTemplateData("keyword1", tmp.getGoodsName(), "#AAAAAA"))
                        .addData(new WxMpTemplateData("keyword2", tmp.getGoodsPrice().setScale(2, RoundingMode.DOWN).toPlainString() + "零售币", "#AAAAAA"))
                        .addData(new WxMpTemplateData("keyword3", tmp.getGroupAmount() + "人", "#AAAAAA"))
                        .addData(new WxMpTemplateData("keyword4", tmp.getGroupTime() + "小时", "#AAAAAA"))
                        .addData(new WxMpTemplateData("remark", "详情请点击这里,祝生活愉快", "#0000FF"));
                return templateMessage;
            }
        };
        return messageHandler.send(tmp);
    }


}

注意看,下面是重点,定义模板

/**
 * 使用模板方法重构,定义执行逻辑
 * @param <T>
 */
@Slf4j
abstract class AbstractTemplateMessageHandler<T extends TmpBase> {
    @Autowired
    private WxMpService wxmpservice;

    /**
     * 模板消息发送
     * @param temp
     * @return
     */
    public boolean send(T temp) {
        if (ifOpen()){
            return false;
        }
        String openid = this.getOpenId(temp);
        if (StrUtil.isBlank(openid)) {
            return false;
        }
        // 设置最终发送消息的openID
        temp.setOpenid(openid);
        log.info("最终发送消息的openID为[{}]", openid);
        WxMpTemplateMessage templateMessage = buildTemplate(temp);

        return judge(templateMessage);
    }

    //发送模板消息并返回是否成功
    private boolean judge(WxMpTemplateMessage templateMessage) {
        log.info("执行发送模板消息方法");
        String msgId = null;
        try {
            // 发送模板消息
            msgId = wxmpservice.getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error("发送公众号模板消息出现异常,消息发送参数为[{}]", templateMessage, e);
            // 记录异常信息 43004 是用户未关注、未关注消息不推送,只打印日志
            if (e.getError().getErrorCode() != 43004) {
                ErrorInfoUtil.sendErrorMessageTo(e, "公众号消息");
            }
        }
        return msgId != null;
    }

    //判断动态参数的微信模板消息开关是否开启
    private boolean ifOpen() {
        // 这里可从数据库读取消息开关配置
        return false;
    }

    /**
     * 组装待发送的模板消息
     * @param temp
     * @return
     */
    abstract WxMpTemplateMessage buildTemplate(T temp);

    /**
     * 获取 openId
     * @param tmpBase openId
     * @return
     */
    private String getOpenId(TmpBase tmpBase) {
        if (StrUtil.isNotBlank(tmpBase.getOpenid())) {
            return tmpBase.getOpenid();
        }
        // 根据用户ID查询openID
        // 获取openId
        // 检查openId
        return "openId";
    }
}

通过一个抽象类,将核心逻辑封装在 send() 方法中,对外提供public方法访问

b、测试

直接调用不同的方法,推送不同的模板消息

/**
 * @author YearOfTheRain
 * @create 2024-06-17  15:05
 */
@SpringBootTest
public class WxTemplateMsgServiceTest {

    @Autowired
    private WxTemplateMsgService new1WxTemplateMsgServiceImpl;

    @Test
    void testBuySuccess() {
        BuySuccessTmp buySuccessTmp = new BuySuccessTmp();
        buySuccessTmp.setBuyTime(new Date());
        buySuccessTmp.setGoodsName("111");
        buySuccessTmp.setPaymentAmount(new BigDecimal("0.99"));
        buySuccessTmp.setPaymentMethod("222");
        buySuccessTmp.setOrderNo("111");
        buySuccessTmp.setUserId("111");
        buySuccessTmp.setUrl("111");
        buySuccessTmp.setOpenid("1111");

        new1WxTemplateMsgServiceImpl.buySuccess(buySuccessTmp);
    }
    @Test
    void testJoinGroup() {
        BeginGroupTmp beginGroupTmp = new BeginGroupTmp();

        beginGroupTmp.setGoodsPrice(new BigDecimal("30"));
        beginGroupTmp.setGroupTime(20);
        beginGroupTmp.setGoodsName("111");
        beginGroupTmp.setGroupAmount(30);
        beginGroupTmp.setUserId("111");
        beginGroupTmp.setUrl("111");
        beginGroupTmp.setOpenid("1111");

        new1WxTemplateMsgServiceImpl.joinGroup(beginGroupTmp);
    }
}

3、变种使用

模板方法本身没有太大的可变性,所以我这里给大家留下一点悬念。
我将会用模板方法实战重构上生产的微信公众号模板消息发送方法,先留个钩子,明天呈现给大家。

重构实战之用模板方法改造微信公众号模板消息发送

4、总结

模板方式很好的满足了开闭原则,将不可变的逻辑私有化,将拓展的逻辑开放给子类实现。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值