神坑中間件: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!」

總結

我只是一個默默無聞的小程序員,和老師、同窗們創業學習。雖然我進不去華爲騰訊,雖然我沒寫過開源項目;可是我知道,寫代碼作開發,要遵照規範。

一個團隊寫出來的代碼,就像一我的寫出來的同樣。 ——《天津市紅橋區夢雲智軟件開發中心》
相關文章
相關標籤/搜索