Spring+策略模式+反射+泛型=无策略模式?

为什么要用策略模式?何时用策略模式?

策略模式根据教科书式中的介绍为定义一系列算法,把它们一个个封装起来,并且使它们之间可互相替换,从而让算法可以独立于使用它的用户而变化。
在实际业务开发中,算法一般表现为一系列特定的业务操作,根据特定的业务策略执行相应的“算法”。一般在以下场景可以考虑使用策略模式:

  • 选择语句(如if/else、switch)的多,选择执行块中操作各不相同且之后可能会进行业务扩展
  • 避免选择语句导致的难以维护
  • 想提高代码逼格

自定义式的策略模式演变

策略模式1.0 - 教材式的使用过程

  • 将具体的算法封装到特定的策略接口具体实现类方法中
  • 设定一个Context类封装策略操作接口,使用策略时传具体的执行策略实现类实例到Context中

例:

strategy1.0

new Context(new ConcreteAStrategy()).handle(obj);

策略模式2.0 - 业务开发的使用过程(结合Spring)

  • 将传统作为选择语句的条件设为算法切换的策略,通过特定常量或枚举包装
  • 将选择执行块中的业务操作设为算法,从执行块中抽离封装到特定的策略操作类方法中
  • 设定一个Context类,封装所有策略常量与对应处理类的映射,需要处理时传特定策略与处理对象(如VO)

例:
strategy2.0

context.handle(strategy,vo);

策略模式-Wilson.0 - 无策略模式(点题)

  • 将选择执行块中的业务操作设为算法,从执行块中抽离封装到特定的策略操作类方法中
  • 设定一个Context类,封装所需映射,处理时只需传处理对象???策略常量不定义了???
    在这里插入图片描述
context.handle(msg);

自定义无策略模式例子

诞生场景

众所周知,WebSocket是一种在单个TCP连接上进行全双工通信的协议,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket比较常见的应用场景有服务器推送、聊天室等,而个人在最近遇到了不同消息需要特定的处理需求(也可作为WebSocket消息路由分发的一种解决方案),于是就想到了策略模式,结合了Spring与目前个人的见解搭建了以下策略模式结构:
msg-strategy

看到这里相信大家都觉得我不讲码德了,明明上面的msgType就是策略,为什么说无策略呢?且听我下文辩解。

编码设计

设计实现思路

设计思路:

  1. 个人认为,其实很多策略常量的设定都会与一些类的定义有所重叠的,且这些策略都是为了让程序运行时能识别执行哪些行为罢了,所以何不把这块谁当交给程序去做?
  2. 如果交给程序策略去做,那么肯定是需要约定的,之后的开发最好也遵循这些约定,COC(Convention over configuration)嘛,说起来也高逼格点
  3. 虽然程序定义好了策略常量了,但是程序得会找到这些常量的处理策略

实现思路:

  1. 为了方便消息的处理与转换,先为所有消息类定义一个基类WebSocketMessage,将msgType设到基类中
  2. 既然由程序去定义策略常量(msgType),那么这些常量最好是基于现有需要处理的消息去演化(如不同消息的命名)
  3. 既然要由现在处理类去演化,那么最好这些类命名都遵循一个约定,既然都继承WebSocketMessage,那就父类名作为后缀
    在本例中我把消息类的前缀作为了策略常量,即msgTypeMap中的key, value则是实际的消息Class,基类下的子类不可能出现同名情况(有的话就是开发问题了),所以也不用考虑策略命名冲突问题
  4. 虽然程序定义好了策略常量(msgType)了,但策略处理类(MessageHandler)不做点特殊处理依旧无法与这些策略常量匹配上,所以就想到了用泛型作为策略处理方法的参数
  5. 仅仅通过泛型在编码时是无法获取参数的类的,但Java的泛型是伪泛型,会在编译时进行类型擦除,擦除完我就可以拿到这个策略处理参数的实际Class了,没错,正是反射

于是就有了以下映射:

/**
 * Map{消息类名前缀:消息类Class}
 */
private Map<String, Class<? extends WebSocketMessage>> msgTypeMap;
/**
 * Map{消息类名前缀:消息类对应的handler}
 */
private Map<String, MessageHandler> messageHandlerMap;

程序的实现流程:
初始化:MessageContext获取所有的MessageHandler类,遍历时通过反射获取参数设置msgTypeMapmessageHandlerMap
消息处理:

  1. 调用MessageContext.convertJsonToMessage(msgJson)将WebSocket传参的msgType根据messageHandlerMap转换msgJson成相应消息对象
  2. 调用MessageContext.handlerMsg(websocketMessage),messageHandlerMap根据实际的消息Class得到策略名获取对应的MessageHandler进行消息处理

编码

WebSocketMessage
@Data
@Accessors(chain = true)
public abstract class WebSocketMessage implements Serializable {
    public static final String MSG_TYPE = "msgType";
    public static final String MSG_TYPE_SEPARATOR = "WebSocket";

    private String msgType;
    protected String content;
}
MessageContext
@Component
@Slf4j
@SuppressWarnings({"rawtypes", "unchecked"})
public class MessageContext implements ApplicationContextAware {
    /**
     * Map{消息类名前缀:消息类Class}
     */
    private Map<String, Class<? extends WebSocketMessage>> msgTypeMap;
    /**
     * Map{消息类名前缀:消息类对应的handler}
     */
    private Map<String, MessageHandler> messageHandlerMap;
    private Map<String, ChannelService> channelServiceMap;

    /**
     * 初始化:注入灵魂,映射初始化
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.channelServiceMap = applicationContext.getBeansOfType(ChannelService.class);
        // 获取容器中的所有MessageHandler
        Map<String, MessageHandler> handlerBeanMap = applicationContext.getBeansOfType(MessageHandler.class);
        this.messageHandlerMap = new HashMap<>(handlerBeanMap.size());
        this.msgTypeMap = new HashMap<>(handlerBeanMap.size());
        handlerBeanMap.values()
                .forEach(messageHandler -> {
                    Class msgClass = Arrays.stream(
                            Arrays.stream(messageHandler.getClass().getMethods())
                                    .filter(method -> Objects.equals(method.getName(), "handleMsg") && method.getParameterCount() == 1)
                                    .findFirst()
                                    .orElseThrow(() -> new IllegalArgumentException(messageHandler.getClass().getName() + " didn't implement handleMsg()"))
                                    .getParameterTypes())
                            .filter(WebSocketMessage.class::isAssignableFrom)
                            .findFirst()
                            .orElseThrow(() -> new IllegalArgumentException(messageHandler.getClass().getName() + " strategy init failed"));
                    String msgClassName = msgClass.getSimpleName();
                    this.messageHandlerMap.put(msgClassName, messageHandler);
                    String msgType = getMsgType(msgClassName);
                    this.msgTypeMap.put(msgType, msgClass);
                });
        log.info("websocket message context init completed, msg type: {}, handler map: {}", msgTypeMap, messageHandlerMap);
    }


    /**
     * 获取所有的消息类型
     *
     * @return
     */
    public Set<String> msgTypes() {
        return msgTypeMap.keySet();
    }

    /**
     * 检测字符串是否为有效的json字符串并包含消息类型msgType
     *
     * @param msgJson
     * @return
     */
    public boolean isValidMessage(String msgJson) {
        ......
    }

    /**
     * 根据消息类Class.simpleName获取实际策略名,如BroadcastWebSocketMessage策略名为Broadcast
     * @param msgClassName
     * @return
     */
    private String getMsgType(String msgClassName) {
        return msgClassName.contains(WebSocketMessage.MSG_TYPE_SEPARATOR) ?
                StringUtils.substringBefore(msgClassName, WebSocketMessage.MSG_TYPE_SEPARATOR) : msgClassName;
    }

    public MessageHandler getMessageHandler(String msgType) {
        if (msgTypeMap.containsKey(msgType)) {
            return messageHandlerMap.get(msgTypeMap.get(msgType).getSimpleName());
        }
        throw new IllegalArgumentException("msg type[" + msgType + "] handler doesn't exist");
    }

    public Object handleMessage(WebSocketMessage message) {
        return getMessageHandler(message.getMsgType()).handleMsg(message);
    }

    public Object handleMessage(String msgJson) {
        if (JSONValidator.from(msgJson).validate()) {
            JSONObject jsonObject = JSON.parseObject(msgJson);
            String msgType = jsonObject.getString(WebSocketMessage.MSG_TYPE);
            MessageHandler messageHandler = getMessageHandler(msgType);
            WebSocketMessage message = JSONObject.parseObject(msgJson, msgTypeMap.get(msgType));
            return messageHandler.handleMsg(message);
        } else {
            throw new IllegalArgumentException("invalid msg json");
        }
    }

    public Object handleMessage(JSONObject jsonObject) {
        String msgType = jsonObject.getString(WebSocketMessage.MSG_TYPE);
        Assert.isTrue(msgTypeMap.containsKey(msgType), "msgType [" + msgType + "] doesn't exist");
        WebSocketMessage msgRequest = jsonObject.toJavaObject(msgTypeMap.get(msgType));
        MessageHandler messageHandler = messageHandlerMap.get(msgRequest.getClass().getSimpleName());
        return messageHandler.handleMsg(msgRequest);
    }

    public <T extends WebSocketMessage> WebSocketMessage convertJsonToMessage(JSONObject msgJson) {
        String msgType = msgJson.getString(WebSocketMessage.MSG_TYPE);
        return msgJson.toJavaObject(msgTypeMap.get(msgType));
    }


    /**
     * remove channel from the all channel services
     *
     * @param channel
     */
    public void removeChannel(Channel channel) {
        channelServiceMap.values()
                .forEach(channelService -> channelService.removeChannel(channel));
    }

}
MessageHandler
/**
 * @author Wilson
 */
public interface MessageHandler<T extends WebSocketMessage> {
    Object handleMsg(T msg);
}

@Service
public class DirectionMessageHandler implements MessageHandler<DirectionWebSocketMessage> {
    @Override
    public Object handleMsg(DirectionWebSocketMessage msg) {
        return "DirectionWebSocketMessage handles msg";
    }
}

@Service
public class BroadcastMessageHandler implements MessageHandler<BroadcastWebSocketMessage> {
    @Override
    public Object handleMsg(BroadcastWebSocketMessage msg) {
        return "BroadcastMessageHandler handles msg";
    }
}
补个Controller测下MessageContext
@RestController
@RequestMapping("/message-context")
public class MessageContextController {
    @Resource
    private MessageContext messageContext;

    @GetMapping("/msg-types")
    @ApiOperation("所有的消息类型")
    public ServerResponse<?> msgTypes() {
        return ServerResponse.success(messageContext.msgTypes());
    }

    @PostMapping("/direction")
    public ServerResponse<?> handleDirectionMsg(@RequestBody DirectionWebSocketMessage message) {
        return ServerResponse.success(messageContext.handleMessage(message));
    }

    @PostMapping("/broadcast")
    public ServerResponse<?> handleBroadcastMsg(@RequestBody BroadcastWebSocketMessage message) {
        return ServerResponse.success(messageContext.handleMessage(message));
    }
}

测试JSON:

/direction:
{
  "msgType": "Direction"
}

/broadcast:
{
  "msgType": "Broadcast"
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTFFpIYl-1606468028536)(:storage\e2604992-7141-4720-ac99-7321aafab80e\8ff4141d.png)]

与2.0的区别

  • 初始化时比较麻烦,去掉了策略常量定义一步
  • 扩展时只需定义新的消息类与新的处理类即可,如GroupWebSocketMessage与GroupMessageHandler

引出天坑

当MessageHandler的子类扩展了其他的方法(如实现其它接口)时反射获取到的消息参数Class可能会是WebSocketMessage而非实际的WebSocketMessage子类,如BroadcastMessageHandler实现了ChannelService后会出现以下初始化问题:

@Service
public class BroadcastMessageHandler implements MessageHandler<BroadcastWebSocketMessage>, ChannelService {
    @Override
    public Object handleMsg(BroadcastWebSocketMessage msg) {
        System.out.println("BroadcastMessageHandler handles msg");
        return "BroadcastMessageHandler handles msg";
    }

    @Override
    public void addChannel(Channel channel) {
    }

    @Override
    public void removeChannel(Channel channel) {
    }
}

反射初始化错误日志(msgType丢失了Broadcast,多了个空字符串""):

websocket message context init completed, msg type: {=class per.wilson.chat.websocket.msg.request.WebSocketMessage, Direction=class per.wilson.chat.websocket.msg.request.DirectionWebSocketMessage}, handler map: {WebSocketMessage=per.wilson.chat.websocket.strategy.BroadcastMessageHandler@1d54b3, DirectionWebSocketMessage=per.wilson.chat.websocket.strategy.DirectionMessageHandler@1f40ca0}

该问题的原因目前只通过原生环境下反射初始化排除了框架问题,且非必现,有时会正常初始化:

websocket message context init completed, msg type: {Broadcast=class per.wilson.chat.websocket.msg.request.BroadcastWebSocketMessage, Direction=class per.wilson.chat.websocket.msg.request.DirectionWebSocketMessage}, handler map: {BroadcastWebSocketMessage=per.wilson.chat.websocket.strategy.BroadcastMessageHandler@21f43f, DirectionWebSocketMessage=per.wilson.chat.websocket.strategy.DirectionMessageHandler@10a133d}

当只实现MessageHandler一个接口方法时,则必然不会出现以上问题,目前猜测是反射执行与类加载的时序问题,欢迎填坑。

养老程序员要保证月更真难,文中的websocket留下个月吧
转载可以,copy必究

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值