神坑中间件:spring-boot-starter-websocket

引言

简易地使用WebSocket时,使用spring-boot-starter-websocket没什么问题,虽然路由部分设计得有些缺陷,但不影响正常使用。java

但当我使用spring-boot-starter-websocket实现复杂业务的时候,发现这个中间件虽然是spring官方提供的中间件,可是却像是历来没有用过spring的人写出来的同样。程序员

灵异事件

需求描述

想实现一个外网向企业内局域网转发数据的雏形,就是下面这张图:web

secret为内网服务,server为外网服务,该服务向server注册,创建WebSocket链接,这样在外网的server接收到指令就能经过WebSocket通道转发给内网的secretspring

image.png

神奇代码

WebSocket服务端Endpoint,路由映射/websocket/{name}name为注册的服务实例的名字。小程序

客户端链接ws://127.0.0.1:8000/websocket/HEBUT,注册一个名为HEBUT的服务实例。websocket

服务端将服务实例名称到Session的映射存到了一个ConcurrentHashMap里。session

@Component
@ServerEndpoint("/websocket/{name}")
public class YunzhiWebSocket {

    private static final Logger logger = LoggerFactory.getLogger(YunzhiWebSocket.class);

    private Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(@PathParam(value = "name") String name, Session session) throws IOException {
        if (name != null && !name.equals("")) {
            logger.debug("名称合法,添加到Map中");
            sessionMap.put(name, session);
        } else {
            logger.debug("关闭链接");
            session.close();
        }
    }

    @OnMessage
    public void onMessage(String message) {
        logger.error("接收到消息 {}", message);
    }

    @OnError
    public void onError(@PathParam(value = "name") String name, Throwable throwable) {
        logger.error("链接发生错误 {}", throwable.getMessage());
        sessionMap.remove(name);
    }

    @OnClose
    public void onClose(@PathParam(value = "name") String name) {
        logger.debug("关闭链接");
        sessionMap.remove(name);
    }

    public Map<String, Session> getSessionMap() {
        return sessionMap;
    }
}

一个映射**的方法,将全部其余的请求都交给当前action处理,根据要访问的实例名去SessionMap里找相应的Sessionapp

@RequestMapping("{name}/**")
public void dispatcher(@PathVariable String name, HttpServletRequest request) {
    logger.debug("根据服务名查询Session");
    Session session = yunzhiWebSocket.getSessionMap().get(name);

    logger.debug("未找到服务,抛出异常");
    if (session == null) {
        throw new ServiceNotFoundException("找不到该服务实例");
    }
}

诡异

WebSocket链接以后,执行onOpen方法,将映射putsessionMap中,中断可看到sessionMap中已有当前实例名HEBUTSession的映射。框架

image.png

但是在执行控制器的方法时,getSessionMap却获取到了一个空的Mapsocket

image.png

我当时就很蒙圈呀~,明明put进去了,怎么再get就没了呢?怎么也想不明白呀?

image.png

缘由

掉坑的缘由是:由于这个是spring官方提供的starter,我默认认为它是使用了spring ioc的。

震惊。这个ServerEndpoint的对象实例居然不是从上下文里拿的!!!

image.png

WebSocket创建链接时ServerEndpoint对象的地址编号是5653

spring上下文里autowire进来的ServerEndpoint对象的地址编号是6719

这个Beansingleton的。由此推测,spring-boot-starter-websocket使用的对象没有从上下文里拿,就是本身造的。

回忆

我记得上次我遇到这个问题是在编写hibernate拦截器的时候,autowire的时候一直注不进来。

由于hibernate拦截器组件并不是spring官方编写,因此很天然就想到多是hibernate没有遵循spring ioc的规范,没有获取上下文的对象,很快便解决了。

问题是时间已通过了一年半,技术提高巨大,但是我再次碰到相似问题的时候,竟然花了两个小时解决!!!

最后反思就是中间件spring-boot-starter-websocket背锅,hibernate拦截器很差使,我第一个想到的就是上下文对象的获取问题,由于hibernate是第三方orm框架。

一直没有往这方面想,在个人印象里,spring-boot十分优秀,整合的每个starter都是spring这样式的。

但是谁想到官方提供的starter给我整了这么一出,“没想到吧,别看我是spring开头的,其实我没用ioc!”

总结

我只是一个默默无闻的小程序员,和老师、同窗们创业学习。虽然我进不去华为腾讯,虽然我没写过开源项目;可是我知道,写代码作开发,要遵照规范。

一个团队写出来的代码,就像一我的写出来的同样。 ——《天津市红桥区梦云智软件开发中心》
相关文章
相关标签/搜索