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、总结
模板方式很好的满足了开闭原则,将不可变的逻辑私有化,将拓展的逻辑开放给子类实现。