简易地使用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
!”
我只是一个默默无闻的小程序员,和老师、同窗们创业学习。虽然我进不去华为腾讯,虽然我没写过开源项目;可是我知道,写代码作开发,要遵照规范。
一个团队写出来的代码,就像一我的写出来的同样。 ——《天津市红桥区梦云智软件开发中心》