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

引言

簡易地使用WebSocket時,使用spring-boot-starter-websocket沒什麼問題,雖然路由部分設計得有些缺陷,但不影響正常使用。

但當我使用spring-boot-starter-websocket實現複雜業務的時候,發現這個中間件雖然是spring官方提供的中間件,但是卻像是從來沒有用過spring的人寫出來的一樣。

靈異事件

需求描述

想實現一個外網向企業內局域網轉發數據的雛形,就是下面這張圖:

secret爲內網服務,server爲外網服務,該服務向server註冊,建立WebSocket連接,這樣在外網的server接收到指令就能通過WebSocket通道轉發給內網的secret

image.png

神奇代碼

WebSocket服務端Endpoint,路由映射/websocket/{name}name爲註冊的服務實例的名字。

客戶端連接ws://127.0.0.1:8000/websocket/HEBUT,註冊一個名爲HEBUT的服務實例。

服務端將服務實例名稱到Session的映射存到了一個ConcurrentHashMap裏。

@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裏找相應的Session

@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卻獲取到了一個空的Map

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!”

總結

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

一個團隊寫出來的代碼,就像一個人寫出來的一樣。
——《天津市紅橋區夢雲智軟件開發中心》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章