- 作者简介
- 内容提要
- 关于本书
- 路线图
- 代码规范与下载
- 作者在线
- 封面插图简介
- 前言
- 译者序
- 致谢
- 第1部分 Spring 的核心
- 第1章 Spring 之旅
- 第2章 装配 Bean
- 第3章 高级装配
- 第4章 面向切面的 Spring
- 第2部分 Web 中的 Spring
- 第5章 构建 Spring Web 应用程序
- 第6章 渲染 Web 视图
- 第7章 Spring MVC 的高级技术
- 第8章 使用 Spring Web Flow
- 第9章 保护 Web 应用
- 第3部分 后端中的 Spring
- 第10章 通过 Spring 和 JDBC 征服数据库
- 第11章 使用对象-关系映射持久化数据
- 第12章 使用 NoSQL 数据库
- 第13章 缓存数据
- 第14章 保护方法应用
- 第4部分 Spring 集成
- 第15章 使用远程服务
- 第16章 使用 Spring MVC 创建 REST API
- 第17章 Spring消息
- 第18章 使用 WebSocket 和 STOMP 实现消息功能
- 第19章 使用 Spring 发送 Email
- 第20章 使用 JMX 管理 Spring Bean
- 第21章 借助 Spring Boot 简化 Spring 开发
18.1 使用 Spring 的低层级 WebSocket API
按照其最简单的形式,WebSocket只是两个应用之间通信的通道。位于WebSocket一端的应用发送消息,另外一端处理消息。因为它是全双工的,所以每一端都可以发送和处理消息。如图18.1所示。
图18.1 WebSocket是两个应用之间全双工的通信通道
WebSocket通信可以应用于任何类型的应用中,但是WebSocket最常见的应用场景是实现服务器和基于浏览器的应用之间的通信。浏览器中的JavaScript客户端开启一个到服务器的连接,服务器通过这个连接发送更新给浏览器。相比历史上轮询服务端以查找更新的方案,这种技术更加高效和自然。
为了阐述Spring低层级的WebSocket API,让我们编写一个简单的WebSocket样例,基于JavaScript的客户端与服务器玩一个无休止的“Marco Polo”游戏。服务器端的应用会处理文本消息(“Marco!”),然后在相同的连接上往回发送文本消息(“Polo!”)。为了在Spring使用较低层级的API来处理消息,我们必须编写一个实现WebSocketHandler的类:
可以看到,WebSocketHandler需要我们实现五个方法。相比直接实现WebSocketHandler,更为简单的方法是扩展AbstractWebSocketHandler,这是WebSocketHandler的一个抽象实现。如下的程序清单展现了MarcoHandler,它是AbstractWebSocketHandler的一个子类,会在服务器端处理消息。
程序清单18.1 MarcoHandler处理通过WebSocket传送的文本消息
尽管AbstractWebSocketHandler是一个抽象类,但是它并不要求我们必须重载任何特定的方法。相反,它让我们来决定该重载哪一个方法。除了重载WebSocketHandler中所定义的五个方法以外,我们还可以重载AbstractWebSocketHandler中所定义的三个方法:
handleBinaryMessage()
handlePongMessage()
handleTextMessage()
这三个方法只是handleMessage()方法的具体化,每个方法对应于某一种特定类型的消息。
因为MarcoHandler将会处理文本类型的“Marco!”消息,因此我们应该重载handleTextMessage()方法。当有文本消息抵达的时候,日志会记录消息内容,在两秒钟的模拟延迟之后,在同一个连接上返回另外一条文本消息。
MarcoHandler所没有重载的方法都由AbstractWebSocketHandler以空操作的方式(no-op)进行了实现。这意味着MarcoHandler也能处理二进制和pong消息,只是对这些消息不进行任何操作而已。
另外一种方案,我们可以扩展TextWebSocketHandler,不再扩展Abstract-WebSocketHandler:
TextWebSocketHandler是AbstractWebSocketHandler的子类,它会拒绝处理二进制消息。它重载了handleBinaryMessage()方法,如果收到二进制消息的时候,将会关闭WebSocket连接。与之类似,BinaryWebSocketHandler也是AbstractWeb-SocketHandler的子类,它重载了handleTextMessage()方法,如果接收到文本消息的话,将会关闭连接。
尽管你会关心如何处理文本消息或二进制消息,或者二者兼而有之,但是你可能还会对建立和关闭连接感兴趣。在本例中,我们可以重载afterConnectionEstablished()和afterConnectionClosed():
我们通过afterConnectionEstablished()和afterConnectionClosed()方法记录了连接信息。当新连接建立的时候,会调用afterConnectionEstablished()方法,类似地,当连接关闭时,会调用afterConnectionClosed()方法。在本例中,连接事件仅仅记录了日志,但是如果我们想在连接的生命周期上建立或销毁资源时,这些方法会很有用。
注意,这些方法都是以“after”开头。这意味着,这些事件只能在事件发生后才产生响应,因此并不能改变结果。
现在,已经有了消息处理器类,我们必须要对其进行配置,这样Spring才能将消息转发给它。在Spring的Java配置中,这需要在一个配置类上使用@EnableWebSocket,并实现WebSocketConfigurer接口,如下面的程序清单所示。
程序清单18.2 在Java配置中,启用WebSocket并映射消息处理器
registerWebSocketHandlers()方法是注册消息处理器的关键。通过重载该方法,我们得到了一个WebSocketHandlerRegistry对象,通过该对象可以调用addHandler()来注册信息处理器。在本例中,我们注册了MarcoHandler(以bean的方式进行声明)并将其与“/marco”路径相关联。
另外,如果你更喜欢使用XML来配置Spring的话,那么可以使用websocket命名空间:
程序清单18.3 借助websocket命名空间以XML的方式配置WebSocket
不管使用Java还是使用XML,这就是所需的配置。
现在,我们可以把注意力转向客户端,它会发送“Marco!”文本消息到服务器,并监听来自服务器的文本消息。如下程序清单所展示的JavaScript代码开启了一个原始的WebSocket并使用它来发送消息给服务器。
程序清单18.4 连接到“marco” WebSocket的JavaScript客户端
在程序清单18.4的代码中,所做的第一件事情就是创建WebSocket实例。对于支持WebSocket的浏览器来说,这个类型是原生的。通过创建WebSocket实例,实际上打开了到给定URL的WebSocket。在本例中,URL使用了“ws://”前缀,表明这是一个基本的WebSocket连接。如果是安全WebSocket的话,协议的前缀将会是“wss://”。
WebSocket创建完毕之后,接下来的代码建立了WebSocket的事件处理功能。注意,WebSocket的onopen、onmessage和onclose事件对应于MarcoHandler的after-ConnectionEstablished()、handleTextMessage()和afterConnectionClosed()方法。在onopen事件中,设置了一个函数,它会调用sayMarco()方法,在该WebSocket上发送“Marco!”消息。通过发送“Marco!”,这个无休止的Marco Polo游戏就开始了,因为服务器端的MarcoHandler作为响应会将“Polo!”发送回来,当客户端收到来自服务器的消息后,onmessage事件会发送另外一个“Marco!”给服务器。
这个过程会一直持续下去,直到连接关闭。在程序清单18.4中所没有展示的是如果调用sock.close()的话,将会结束这个疯狂的游戏。在服务端也可以关闭连接,或者浏览器转向其他的页面,都会关闭连接。如果发生以上任意的场景,只要连接关闭,都会触发onclose事件。在这里,出现这种情况将会在控制台日志上记录一条信息。
到此为止,我们已经编写完使用Spring低层级WebSocket API的所有代码,包括接收和发送消息的处理器类,以及在浏览器端完成相同功能的JavaScript客户端。如果我们构建这些代码并将其部署到Servlet容器中,那它有可能能够正常运行。
从我选择“可能”这个词,你是不是能够感觉到这里有一点悲观的情绪?这是因为我不能保证它可以正常运行。实际上,它很有可能运行不起来。即便把所有的事情都做对了,诡异的事情依然会困扰我们。
让我们看一下都有什么事情会阻止WebSocket正常运行,并采取一些措施提高成功的几率。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论