TECH NOTES / Java 与 Spring

Spring 实现工厂模式示例

原笔记 2022-03-28 · 整理于 2026-10-05

用 Spring 管理多个消息处理实现,通过工厂按类型找到对应处理器。

示例说明:代码展示工厂与多个处理实现之间的组织方式;业务服务、实体、异常及枚举依赖原项目,不能作为单文件直接编译。项目包名已替换为 com.example。

微信订阅消息服务工厂

java
package com.example.service.submsg;

import com.example.remote.enums.SubMsgBusinessTypeEnum;
import com.example.remote.util.ValidUtils;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * 微信订阅消息服务工厂
 *
 * @author leiguoqing
 * @date 2022 -2-18 14:00
 */
@Slf4j
@Service
public class WxMaSubMsgServiceFactory implements ApplicationListener<ContextRefreshedEvent> {

    /**
     * 策略实现类保存到 Map 中 Key 的前缀
     */
    private static final String KEY_PREFIX = "wxMaSubMsgServiceTemplate";

    /**
     * 用于保存所有策略实现类
     */
    private final Map<String, WxMaSubMsgServiceTemplate> wxMaSubMsgServiceTemplateMap = Maps.newConcurrentMap();

    /**
     * 获取微信订阅消息服务
     *
     * @param busType the bus type
     * @return the wx ma sub msg service
     */
    public WxMaSubMsgServiceTemplate getService(SubMsgBusinessTypeEnum busType) {
        final WxMaSubMsgServiceTemplate wxMaSubMsgServiceTemplate = wxMaSubMsgServiceTemplateMap.get(KEY_PREFIX + busType.getCode());
        ValidUtils.isNull(wxMaSubMsgServiceTemplate, "获取微信订阅消息服务策略异常,请检查订阅消息业务类型");
        return wxMaSubMsgServiceTemplate;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        final ApplicationContext applicationContext = event.getApplicationContext();

        // key 默认是以实现类的名称,首字母小写名称
        final Map<String, WxMaSubMsgServiceTemplate> templateMap = applicationContext.getBeansOfType(WxMaSubMsgServiceTemplate.class);
        if (MapUtils.isEmpty(templateMap)) {
            return;
        }

        // 转化为我们自定义的名称
        templateMap.values().forEach(templateObj -> {
            final Integer code = templateObj.getBusinessType().getCode();
            wxMaSubMsgServiceTemplateMap.put(KEY_PREFIX + code, templateObj);
        });

        wxMaSubMsgServiceTemplateMap.forEach((k, v) -> log.info("init WxMaSubMsgService bean success, name = {},bean = {}", k, v.toString()));
    }
}

微信订阅消息服务模板

java
package com.example.service.submsg;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import com.example.properties.WxMaSubMsgProperties;
import com.example.remote.enums.SubMsgBusinessTypeEnum;
import com.example.service.IMaAccountService;
import com.example.service.IMaSubMsgSendRecordService;
import com.example.service.submsg.data.SubMsgData;
import com.example.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxMaErrorMsgEnum;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Objects;

/**
 * 微信订阅消息服务模板
 *
 * @author leiguoqing
 * @date 2022 -2-18 9:29
 */
@Slf4j
public abstract class WxMaSubMsgServiceTemplate implements SubMsgCallBack {

    /**
     * 发送订阅消息
     *
     * @param accountId   微信小程序账户id
     * @param businessIds 业务ids
     * @param subMsgData  微信订阅消息参数
     */
    public final void sendMsg(Long accountId, List<Long> businessIds, SubMsgData subMsgData) {
        Assert.notNull(accountId, "accountId must not null");
        Assert.notEmpty(businessIds, "businessIds must not null");
        Assert.notNull(subMsgData, "subMsgData must not null");

        final Boolean enable = getSpringBean(WxMaSubMsgProperties.class).getEnable();
        if (!enable) {
            return;
        }

        // 构建 MsgData
        final List<WxMaSubscribeMessage.MsgData> msgDatas = this.buildMsgData(subMsgData.getMsgDataValues());
        if (CollectionUtils.isEmpty(msgDatas)) {
            return;
        }

        // 构建 WxMaSubscribeMessage
        final WxMaSubscribeMessage wxMaSubscribeMessage = this.buildWxMaSubscribeMessage(getSpringBean(IMaAccountService.class).getOpenIdById(accountId), msgDatas);
        if (Objects.isNull(wxMaSubscribeMessage)) {
            return;
        }

        // 默认发送成功
        WxError wxError = WxError.builder().errorCode(0).errorMsg("成功").build();

        // 发送订阅消息
        try {
            getSpringBean(WxMaService.class).getSubscribeService().sendSubscribeMsg(wxMaSubscribeMessage);
        } catch (WxErrorException e) {
            wxError = e.getError();
            if (wxError.getErrorCode() != WxMaErrorMsgEnum.CODE_43101.getCode()) {
                log.error("推送核销订阅消息异常:", e);
            }
        }

        // 保存订阅消息发送记录
        getSpringBean(IMaSubMsgSendRecordService.class).saveRecord(accountId, businessIds, wxError, this.getBusinessType());

        // 回调
        this.callBack(accountId, businessIds);
    }


    /**
     * 获取业务类型
     *
     * @return the business type
     */
    protected abstract SubMsgBusinessTypeEnum getBusinessType();


    /**
     * 构建 MsgData 参数
     * <br/>
     * 可以使用 {@link WxMaSubMsgServiceTemplate#newMsgData(String, String)} 方法构建
     *
     * @param msgDataValues the msg data values
     * @return the list
     */
    protected abstract List<WxMaSubscribeMessage.MsgData> buildMsgData(List<String> msgDataValues);

    /**
     * 构建 WxMaSubscribeMessage 参数
     *
     * @param toUserOpenId the to user open id
     * @param msgDatas     the msg datas
     * @return the wx ma subscribe message
     */
    protected abstract WxMaSubscribeMessage buildWxMaSubscribeMessage(String toUserOpenId, List<WxMaSubscribeMessage.MsgData> msgDatas);


    /**
     * 创建一个 MsgData
     *
     * @param name  the name
     * @param value the value
     * @return the wx ma subscribe message . msg data
     */
    protected final WxMaSubscribeMessage.MsgData newMsgData(String name, String value) {
        WxMaSubscribeMessage.MsgData msgData = new WxMaSubscribeMessage.MsgData();
        msgData.setName(name);
        msgData.setValue(value);
        return msgData;
    }


    private <T> T getSpringBean(Class<T> cls) {
        return SpringContextUtils.getBean(cls);
    }

}

微信小程序优惠券过期订阅消息服务

java
package com.example.service.submsg;

import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import com.example.constant.SystemConstants;
import com.example.properties.WxMaSubMsgProperties;
import com.example.remote.enums.SubMsgBusinessTypeEnum;
import com.example.service.IMaAccountCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 微信小程序优惠券过期订阅消息服务
 *
 * @author leiguoqing
 * @date 2022 -1-20 11:39
 */
@RequiredArgsConstructor
@Service
public class ExpiredWxMaSubMsgService extends WxMaSubMsgServiceTemplate {

    /**
     * 微信小程序订阅消息参数配置
     */
    private final WxMaSubMsgProperties wxMaSubMsgProperties;

    /**
     * 小程序账户-门店优惠券关联表
     */
    private final IMaAccountCouponService maAccountCouponService;


    /**
     * 获取业务类型
     *
     * @return the business type
     */
    @Override
    protected SubMsgBusinessTypeEnum getBusinessType() {
        return SubMsgBusinessTypeEnum.COUPON_EXPIRED;
    }


    /**
     * 构建 MsgData 参数
     *
     * @param msgDataValues the msg data values
     * @return the list
     */
    @Override
    protected List<WxMaSubscribeMessage.MsgData> buildMsgData(List<String> msgDataValues) {
        return IntStream.range(0, msgDataValues.size())
                .mapToObj(idx -> super.newMsgData(wxMaSubMsgProperties.getExpiredTempMsgDataNames()[idx], msgDataValues.get(idx)))
                .collect(Collectors.toList());
    }

    /**
     * 构建 WxMaSubscribeMessage 参数
     *
     * @param toUserOpenId the to user open id
     * @param msgDatas     the msg datas
     * @return the wx ma subscribe message
     */
    @Override
    protected WxMaSubscribeMessage buildWxMaSubscribeMessage(String toUserOpenId, List<WxMaSubscribeMessage.MsgData> msgDatas) {
        return WxMaSubscribeMessage.builder()
                .templateId(wxMaSubMsgProperties.getExpiredTempId())
                .toUser(toUserOpenId)
                .page(SystemConstants.WX_MA_SUB_MSG_JUMP_PAGE)
                // 消息参数
                .data(msgDatas)
                .miniprogramState(wxMaSubMsgProperties.getState())
                .build();
    }

    /**
     * 发送消息后回调
     *
     * @param accountId   微信小程序账户id
     * @param businessIds 业务ids
     */
    @Override
    public void callBack(Long accountId, List<Long> businessIds) {
        // 修改该优惠券 “是否发送过期订阅通知” 标志
        maAccountCouponService.updateBatchSendExpiredSubMsgToTrue(businessIds);
    }
}

微信小程序到账订阅消息服务

java
package com.example.service.submsg;

import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import com.example.constant.SystemConstants;
import com.example.properties.WxMaSubMsgProperties;
import com.example.remote.enums.SubMsgBusinessTypeEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 微信小程序到账订阅消息服务
 *
 * @author leiguoqing
 * @date 2022 -1-20 11:39
 */
@RequiredArgsConstructor
@Service
public class ReceiveWxMaSubMsgService extends WxMaSubMsgServiceTemplate {

    /**
     * 微信小程序订阅消息参数配置
     */
    private final WxMaSubMsgProperties wxMaSubMsgProperties;


    /**
     * 获取业务类型
     *
     * @return the business type
     */
    @Override
    protected SubMsgBusinessTypeEnum getBusinessType() {
        return SubMsgBusinessTypeEnum.COUPON_RECEIVE;
    }


    /**
     * 构建 MsgData 参数
     *
     * @param msgDataValues the msg data values
     * @return the list
     */
    @Override
    protected List<WxMaSubscribeMessage.MsgData> buildMsgData(List<String> msgDataValues) {
        return IntStream.range(0, msgDataValues.size())
                .mapToObj(idx -> super.newMsgData(wxMaSubMsgProperties.getReceiveTempMsgDataNames()[idx], msgDataValues.get(idx)))
                .collect(Collectors.toList());
    }

    /**
     * 构建 WxMaSubscribeMessage 参数
     *
     * @param toUserOpenId the to user open id
     * @param msgDatas     the msg datas
     * @return the wx ma subscribe message
     */
    @Override
    protected WxMaSubscribeMessage buildWxMaSubscribeMessage(String toUserOpenId, List<WxMaSubscribeMessage.MsgData> msgDatas) {
        return WxMaSubscribeMessage.builder()
                .templateId(wxMaSubMsgProperties.getReceiveTempId())
                .toUser(toUserOpenId)
                .page(SystemConstants.WX_MA_SUB_MSG_JUMP_PAGE)
                // 消息参数
                .data(msgDatas)
                .miniprogramState(wxMaSubMsgProperties.getState())
                .build();
    }
}

微信小程序核销订阅消息服务

java
package com.example.service.submsg;

import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import com.example.constant.SystemConstants;
import com.example.properties.WxMaSubMsgProperties;
import com.example.remote.enums.SubMsgBusinessTypeEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 微信小程序核销订阅消息服务
 *
 * @author leiguoqing
 * @date 2022 -1-20 11:39
 */
@RequiredArgsConstructor
@Service
public class WriteOffWxMaSubMsgService extends WxMaSubMsgServiceTemplate {

    /**
     * 微信小程序订阅消息参数配置
     */
    private final WxMaSubMsgProperties wxMaSubMsgProperties;


    /**
     * 获取业务类型
     *
     * @return the business type
     */
    @Override
    protected SubMsgBusinessTypeEnum getBusinessType() {
        return SubMsgBusinessTypeEnum.COUPON_WRITE_OFF;
    }


    /**
     * 构建 MsgData 参数
     *
     * @param msgDataValues the msg data values
     * @return the list
     */
    @Override
    protected List<WxMaSubscribeMessage.MsgData> buildMsgData(List<String> msgDataValues) {
        return IntStream.range(0, msgDataValues.size())
                .mapToObj(idx -> super.newMsgData(wxMaSubMsgProperties.getWriteOffTempMsgDataNames()[idx], msgDataValues.get(idx)))
                .collect(Collectors.toList());
    }

    /**
     * 构建 WxMaSubscribeMessage 参数
     *
     * @param toUserOpenId the to user open id
     * @param msgDatas     the msg datas
     * @return the wx ma subscribe message
     */
    @Override
    protected WxMaSubscribeMessage buildWxMaSubscribeMessage(String toUserOpenId, List<WxMaSubscribeMessage.MsgData> msgDatas) {
        return WxMaSubscribeMessage.builder()
                .templateId(wxMaSubMsgProperties.getWriteOffTempId())
                .toUser(toUserOpenId)
                .page(SystemConstants.WX_MA_SUB_MSG_JUMP_PAGE)
                // 消息参数
                .data(msgDatas)
                .miniprogramState(wxMaSubMsgProperties.getState())
                .build();
    }
}

微信订阅回调接口

java
package com.example.service.submsg;

import java.util.List;

/**
 * 微信订阅回调接口
 *
 * @author leiguoqing
 * @date 2022 -2-18 14:49
 */
public interface SubMsgCallBack {

    /**
     * 发送消息后回调
     *
     * @param accountId   微信小程序账户id
     * @param businessIds 业务ids
     */
    default void callBack(Long accountId, List<Long> businessIds) {

    }

}

文章配图