概念
模板方法模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。
它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
工厂方法模式
定义一个场景对象的接口,让其子类决定实例化哪一个工厂类。
单例模式
单例模式,保证一个类仅有一个实例,并提供访问它的全局访问点。 I/O与数据库的连接,一般就用单例模式实现的。
业务场景一
在我们的系统中有这样的业务场景比如
- 待结算消息推送
- 支付成功推送
- 异步查询订单状态推送
- 处理XXX中台回调
这些业务场景我们都会放入到比如RocketMQ或Redis的延迟队列中进行消费,而消费的逻辑以及步骤是一致的。
模板模式
具体步骤如下:
- 获取消息
- 根据不同的业务场景处理消息进行返回处理完的状态
- 返回的结果是重试,则重新投递任务,如果重试失败则进行失败落盘
- 返回的结果是成功则进行成功的具体业务处理
- 返回的结果是失败则进行失败的具体业务处理
- 如果出现异常进行异常的具体业务处理
这些主流程都是固定的,具体业务处理的方法得要根据不同业务场景去具体实现。所以我们可以用抽象类来规范化这个具体的流程。每个业务场景都新建一个类来继承这个抽象类,需要根据具体业务处理的再去子类实现。这个就是模板方法。
public void doBiz(MqMsg msg) {
String msgStr = JSONObject.toJSONString(msg);
log.info("开始处理调度任务,TOPIC={},MSG={}", msg.getTopic(), msgStr);
MqMsg<T> ampMqMsg = getMsg(msgStr);
try {
MqBizStatusEnums handleResult = handle(ampMqMsg);
if (MqBizStatusEnums.RETRY.equals(handleResult)) {
// 重新投递消息
boolean retry = retry(ampMqMsg);
// 重试失败则落盘进行具体后续处理
if (!retry) {
onRetryMaxError(ampMqMsg);
}
} else if (MqBizStatusEnums.SUCCESS.equals(handleResult)) {
log.info("处理业务成功!msgStr={}", msgStr);
onSuccess(ampMqMsg);
} else if (MqBizStatusEnums.FAIL.equals(handleResult)) {
log.info("处理业务失败!msgStr={}", msgStr);
onFail(ampMqMsg);
} else if (MqBizStatusEnums.EXP.equals(handleResult)) {
log.info("处理业务异常!msgStr={}", msgStr);
onExp(ampMqMsg, new BusinessException(MessageCode.UNKNOWN));
} else {
log.info("处理业务异常!参数未定义,msgStr={}", msgStr);
onExp(ampMqMsg, new BusinessException(MessageCode.ERROR_REQUEST_PARAM, "参数未定义!"));
} } catch (Exception e) {
log.warn("mqMsg={},队列消费出现异常!", msgStr, e);
onExp(ampMqMsg, e);
} catch (Throwable e) {
log.warn("mqMsg={},队列消费出现致命性异常!", msgStr, e);
onExp(ampMqMsg, e);
} finally {
log.info("结束处理调度任务,TOPIC={},MSG={}", msg.getTopic(), msgStr);
}}
public abstract void onSuccess(MqMsg<T> msg);
public abstract void onExp(MqMsg<T> msg, Throwable throwable);
public abstract void onFail(MqMsg<T> msg);
public abstract MqBizStatusEnums handle(MqMsg<T> msg);
工厂模式+单例模式
上面我们讲了不同的业务场景继承抽象的业务队列来实现具体的消费逻辑,那么想想看,这些继承的消费队列Handler我们要怎么去管理呢?
我们需要在程序启动的时候,将这些继承抽象类的Handler消费者注入到Spring的ioc容器中。
// 实现ApplicationRunner
public class XXRegister implements ApplicationRunner
Map<String, AbstractXXBizHandler> beansOfType = applicationContext.getBeansOfType(AbstractXXBizHandler.class);
beansOfType.forEach((k, v) -> {
MsgListener queueListener = v.getClass().getAnnotation(MsgListener.class);
String topic = spElUtil.resolveValue(queueListener.topic());
//
XXHandlerFactory handlerFactory = XXHandlerFactory.getInstance();
log.info("注册队列监听TOPIC:[{}],监听处理类:[{}]", topic, v.getClass().getSimpleName());
handlerFactory.register(topic, v);
});
这时候我们通过单例模式+工厂模式的来将消费者Hanler注入。
单例模式的工厂类提供ConcurretHashMap其中key为topic,value为抽象类Handler。
key我们是从继承抽象类Hanler的子类上面自定义注解得来的。
那么工厂模式体现在哪里呢?
前面我们概念提到过 定义一个场景对象的接口,让其子类决定实例化哪一个工厂类。
当我们把这些消费者Handler注册后,我们就开始启动消费了。
那么消费启动后的逻辑会出现工厂模式的身影
XXHandlerFactory handlerFactory = XXHandlerFactory.getInstance();
AbstractXXBizHandler handlerTask = handlerFactory.getBizHandlerByTopic(topic);
handlerTask.doBiz(XXX);
这段代码就是工厂模式的具体应用,我们以Redis实现延迟队列为例,我们获取keys,之后根据keys得出topic,再通过工厂找到对应的子类,开始消费。
总结
在我们系统中用到的消息队列的注册、使用用到了模板模式、单例模式、工厂模式。