簡易地使用WebSocket
時,使用spring-boot-starter-websocket
沒什麼問題,雖然路由部分設計得有些缺陷,但不影響正常使用。java
但當我使用spring-boot-starter-websocket
實現複雜業務的時候,發現這個中間件雖然是spring
官方提供的中間件,可是卻像是歷來沒有用過spring
的人寫出來的同樣。程序員
想實現一個外網向企業內局域網轉發數據的雛形,就是下面這張圖:web
secret
爲內網服務,server
爲外網服務,該服務向server
註冊,創建WebSocket
鏈接,這樣在外網的server
接收到指令就能經過WebSocket
通道轉發給內網的secret
。spring
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
裏找相應的Session
。app
@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
方法,將映射put
到sessionMap
中,中斷可看到sessionMap
中已有當前實例名HEBUT
到Session
的映射。框架
但是在執行控制器的方法時,getSessionMap
卻獲取到了一個空的Map
。socket
我當時就很蒙圈呀~,明明put
進去了,怎麼再get
就沒了呢?怎麼也想不明白呀?
掉坑的緣由是:由於這個是spring
官方提供的starter
,我默認認爲它是使用了spring ioc
的。
震驚。這個ServerEndpoint
的對象實例居然不是從上下文裏拿的!!!
WebSocket
創建鏈接時ServerEndpoint
對象的地址編號是5653
。
從spring
上下文裏autowire
進來的ServerEndpoint
對象的地址編號是6719
。
這個Bean
是singleton
的。由此推測,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
!」
我只是一個默默無聞的小程序員,和老師、同窗們創業學習。雖然我進不去華爲騰訊,雖然我沒寫過開源項目;可是我知道,寫代碼作開發,要遵照規範。
一個團隊寫出來的代碼,就像一我的寫出來的同樣。 ——《天津市紅橋區夢雲智軟件開發中心》