本文主要是介绍Spring Boot Messaging Chapter 7 Web Messaging,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
摘要:本章介绍了带有Spring Boot的WebSockets,并描述了该技术如何帮助您跨应用实现消息传递,甚至跨应用程序的多个实例。在讨论web应用程序时,我们可以说REST是另一种进行消息传递的方式。在这一章中,我们将重点讨论一种有状态的通信方式,这就是WebSockets带来的内容。
一:WebSockets
WebSockets是一种允许双向通信的协议,通常在web浏览器中使用。这个协议首先使用一个握手(通常是一个HTTP请求),然后通过TCP发送一个基本的消息帧(一个协议开关)。WebSockets的想法是避免多个HTTP连接,如AJAX(XMLHttpRequest)或iframe和长轮询。见图7 - 1。
二:Using WebSockets with Spring
在我们讨论如何使用Spring引导的WebSockets之前,重要的是要知道并不是所有的浏览器都支持这种技术。访问http://caniuse.com/websockets,了解哪些浏览器是支持WebSockets。
其中绿色代表支持WebSockets技术
Spring框架版本4包含一个支持WebSockets的新Spring websocket模块;它还兼容Java规范JSR-356。该模块还具有在必要时模拟WebSockets API的后备选项(请记住,并不是所有的浏览器都支持WebSockets)。它使用SockJS协议来完成这项任务。您可以在https://github.com/sockjs/sockjs-protocol中获得关于它的更多信息。
同样值得一提的是,在最初的握手(HTTP,我们使用SockJS)之后,通信切换到TCP连接(这意味着您只发送了一个字节流,要么是文本,要么是二进制)。因此,您可以使用任何类型的消息传递架构,比如异步或事件驱动的消息传递。在这个级别上,您可以使用诸如STOMP(简单/流文本定向消息协议)这样的子协议,这允许您拥有更好的消息传递格式,客户端和服务器可以理解。见图7 - 2。
图7-2展示了如何使用Spring和客户端所需的组件来实现WebSockets。
2.1.Low-Level WebSockets
在我们进入回退选项(SockJS)和子协议(STOMP)之前,让我们看看如何使用Spring Boot实现一个低级别的WebSockets应用。
在本书的源代码中我们将使用websocket-demo项目。让我们从分析配置开始。为了使用低级的WebSockets通信,我们需要实现org.springframework.web.socket.config.annotation.WebSocketConfigurer接口,打开com.micai.spring.messaging.config.LlWebSocketConfig类,如清单7-1所示。
Listing 7-1. com.micai.spring.messaging.config.LlWebSocketConfig.java
@Configuration
@EnableWebSocket
public class LlWebSocketConfig implements WebSocketConfigurer {@Autowiredprivate LlWebSocketHandler llWebSocketHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(llWebSocketHandler, "/llws");}
}
清单7-1展示了在Spring中启用WebSockets所需的配置。请记住,我们配置这个类的方式是使用一个低级的WebSockets通信。让我们回顾一下使用的组件:
• @EnableWebSocket: 这对于启用和配置WebSockets请求是必要的。
• WebSocketConfigurer: 这是一个定义回调方法来配置WebSockets请求处理的接口。通常,您需要实现registerWebSocketHandlers方法。
• registerWebSocketHandlers: 这个方法需要通过添加将用于WebSockets处理请求的处理程序来实现。在这个方法中,我们注册一个处理程序(LlWebSocketHandler)实例,并传递用于握手和通信的终端地址。
• WebSocketHandlerRegistry: 这是一个用于注册WebSocketHandler实现的接口。我们将使用TextWebSocketHandler实现,并在下一节中查看它的代码。
正如您所看到的,使用Spring配置一个低级WebSockets是非常简单的。接下来,让我们打开com.micai.spring.messag.web.socket.LlWebSocketHandler类。参见清单7 - 2。
Listing 7-2. com.micai.spring.messaging.web.socket.LlWebSocketHandler.java
@Component
public class LlWebSocketHandler extends TextWebSocketHandler {private static final Logger LOGGER = LoggerFactory.getLogger(LlWebSocketHandler.class);@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {super.afterConnectionEstablished(session);}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {LOGGER.info(">>>> ", message);}
}
清单7-2展示了我们将要用来接收来自客户端的消息的WebSockets handler。让我们来检查这个类:
• TextWebSocketHandler: 这是一个具体的类,通过AbstractWebSocketHandler类实现WebSocketHandler接口。此实现仅用于处理文本消息。我们将重写两种方法:afterConnectionEstablished和handleTextMessage。
• afterConnectionEstablished: 当客户端成功地使用WebSockets协议连接时,就调用此方法。在这个方法中,您可以使用WebSocketSession实例来发送或接收消息。现在,我们将使用它的默认行为,但是我们将在日志中看到更多(通过AOP 的WebSocketsAudit类)。
• handledTextMessage: 这个方法接收一个WebSocketSession(稍后我们将使用它)和一个TextMessage实例。TextMessage实例管理字节流并将它们转换成字符串。
到目前为止,我们已经实现了服务器端,但是客户端呢?通常,您将有一个web页面来完成客户端的工作。打开 src/main/resources/static/llws.html文件,如清单7-3所示。
Listing 7-3. Snippet of llws.html
<script>$(function(){var connection = new WebSocket('ws://localhost:8080/llws');connection.onopen = function () {console.log('Connected...');};connection.onmessage = function(event){console.log('>>>>> ' + event.data);var json = JSON.parse(event.data);$("#output").append("<span><strong>" + json.user + "</strong>: <em>" + json.message +"</em></span><br/>");};connection.onclose = function(event){$("#output").append("<p class=\"text-uppercase\"><strong>CONNECTION: CLOSED</strong></p>");};$("#send").click(function(){var message = {};message["user"] = $("#user").val();message["message"] = $("#message").val();connection.send(JSON.stringify(message));});});
</script>
清单7-3只显示了重要代码的JavaScript片段,我将在下面解释:
• WebSocket: 这是JavaScript引擎的一部分,它将连接到指定的URI。请注意,模式是ws,我们正在使用/llws地址,我们在configuration类中指定了(请参见清单7-1)。
• onopen: 这是一个回调函数,它是在与服务器建立连接时执行的。请注意,我们只是将字符串记录到控制台。
• onmessage: 这是一个回调函数,当从服务器收到一条消息时执行。在这种情况下,我们正在解析一个事件。将数据转换为JSON对象。
• onclose: 这是一个回调函数,当连接被关闭或与服务器丢失时执行。
• $.click/send: 这是一个附在Send按钮上的回调函数,当按钮被单击时,它会被调用。这里我们使用send方法,它将发送消息对象的JSON字符串。
接下来,让我们运行websocket-demo项目;启动之后,打开浏览器并转到http://localhost:8080/llws.html。您应该会看到类似于图7-3的内容。
Figure 7-3. http://localhost:8080/llws.html
在你通过浏览器打开llws.html页面之后,查看一下应用程序日志。您应该会看到类似于图7-4的内容。
Figure 7-4. The websocket-demo project logs
图7-4显示了日志,由此建立的后续连接是从LlWebSocketHandler类调用的。这意味着客户端成功地连接到服务器。您还可以查看一下浏览器的开发者控制台,以查看显示连接的字符串的日志。见图7 - 5。
Figure 7-5. Browser’s console
接下来,您可以通过修改来自llws.html的用户和消息输入来发送一条消息。并单击Send按钮。单击Send按钮之后,您应该会看到类似于图7-6的内容。
Figure 7-6. websocket-demo project logs after clicking Send
图7-6显示了发送消息后的日志以及从handleTextMessage方法打印出来的日志。
这个例子向您展示了如何从客户端发送一条消息到服务器。现在,让我们从服务器上做一个响应,一个来自服务端的回复。您需要在handleTextMessage中添加一些东西来处理响应。
修改handleTextMessage以如下所列:
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println(">>>> " + message);session.sendMessage(message);
}
正如您所看到的,我们使用session实例来调用sendMessage,这意味着我们将使用连通客户端的相同session来应答。
重新启动websocket-demo项目并刷新llws.html页面。然后通过填写用户和消息输入来发送一条消息。您应该在Server面板的回应消息中看到响应,如图7-7所示。注意,如果您正在使用STS,您只需要等待项目重新启动;这是有可能的,这要归功于spring-boot-devtools插件。
Figure 7-7. Echo server response
如果您停止了应用程序,您将看到来自服务器面板的回应消息中的图7-8。
Figure 7-8. Closed connection
正如您所看到的,创建低级WebSockets应用程序非常简单。如果您需要从Spring应用程序发送消息,会发生什么情况?换句话说,你的应用需要成为客户端。您可以向应用程序添加以下代码(参见清单7-4)。
Listing 7-4. WebSockets Client—Snippet of the WebSocketDemoApplication.java
StandardWebSocketClient client = new StandardWebSocketClient();
ListenableFuture<WebSocketSession> future =client.doHandshake(handler,new WebSocketHttpHeaders(),new URI("ws://localhost:8080/llws"));
WebSocketSession session = future.get();
WebSocketMessage<String> message =new TextMessage("Hello there...");
session.sendMessage(message);
清单7-4显示了如果您想要创建WebSockets客户端(而不是HTML web页面),您需要添加哪些内容。在这里,我们使用StandardWebSocketClient(一个低级WebSockets协议),并手动执行握手。它传递给处理程序一些头文件和你将要连接的URI。(注意,您可以使用以前的处理程序或创建自己的处理程序,然后您可以实现afterConnectionEstablished检查您是否成功连接。)然后你会得到一个WebSocketSession实例,并可以发送消息。您可以看到,使用带有Spring类的WebSockets客户端是一个简单的实现。接下来,让我们开始使用SockJS和STOMP子协议的备用选项。
2.2.Using SockJS and STOMP
为什么我们需要使用SockJS和STOMP?请记住,并非所有浏览器都支持WebSockets,通常客户端和服务器必须就如何处理消息达成一致。
当然,这不是正确的消息传递方式,因为我们想要有一个解耦场景,客户端没有绑定到服务器。
SockJS帮助模拟WebSockets并执行最初的握手。然后,通过使用STOMP,我们可以用一种可互操作的有线格式来回复,这样我们就可以使用支持该协议的多个代理。
2.2.1.Chat Room Application(聊天室程序)
我们将继续使用websocket-demo项目,但是我们将使用不同的文件和类。这个例子创建了一个聊天室,这是WebSockets技术的一个非常常见的用法。
首先,让我们看看如何配置这个项目将使用SockJS和STOMP。打开com.micai.spring.messaging.config.WebSocketConfig类。参见清单7 - 5。
Listing 7-5. com.micai.spring.messaging.config.WebSocketConfig.java
@Configuration
@EnableWebSocketMessageBroker
@EnableConfigurationProperties(SimpleWebSocketsProperties.class)
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {@Autowiredprivate SimpleWebSocketsProperties simpleWebSocketsProperties;@Overridepublic void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {stompEndpointRegistry.addEndpoint(simpleWebSocketsProperties.getEndpoint()).withSockJS();}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {registry.enableSimpleBroker(simpleWebSocketsProperties.getTopic());registry.setApplicationDestinationPrefixes(simpleWebSocketsProperties.getAppDestinationPrefix());}
}
清单7-5显示了使用SocksJS和STOMP使用WebSockets所需的配置。让我们回顾一下它:
• @EnableWebSocketMessageBroker: 这个注解需要使用更高级别的消息传递子协议(SockJS/STOMP)来支持基于WebSockets的代理后端消息传递。
• AbstractWebSocketMessageBrokerConfigurer: 这个类实现了WebSocketMessageBrokerConfigurer来配置消息处理,使用简单的消息传递协议,比如WebSockets客户端的STOMP。
• registerStompEndpoints: 这种方法被调用来注册STOMP端点,将每个端点映射到一个特定的URL并配置SockJS后备选项。
• StompEndpointRegistry: 这是一个在WebSockets端点上注册STOMP的契约。它提供了一个流畅的API来构建注册表。
• withSockJS: 这个方法将使握手所需的SockJS后备选项成为可能。
• configureMessageBroker:这个方法被调用来配置message broker options。在这种情况下,我们使用MessageBrokerRegistry。
• MessageBrokerRegistry: 这个实例将帮助配置所有代理选项。我们使用enableSimpleBroker,它接受一个或多个前缀来过滤针对代理的目的地。这将与带有@SendTo注释的带注释的方法一起使用。我们还使setApplicationDestinationPrefixes前缀来配置一个或多个前缀,以过滤目标瞄准应用程序注释的方法。换句话说,它将寻找用@MessageMapping映射注释的方法。
您可以看到,我们需要添加一个端点、一个代理和前缀路径,以使用SockJS和STOMP来配置WebSockets。看一看图7-9。
图7-9向您展示了客户端和服务器之间通信的一般情况。接下来,打开com.micai.spring.messaging.controller.SimpleController类。参见清单7-6。
Listing 7-6. com.micai.spring.messaging.controller.SimpleController.java
@Controller
public class SimpleController {@MessageMapping("${micai.ws.mapping}")@SendTo("/topic/chat-room")public ChatMessage chatRoom(ChatMessage message) {return message;}
}
清单7-6通过使用新的注解向您展示了接收者和发送者。请记住,项目现在将运行一个聊天室,因此不同的客户端可以连接和接收来自其他用户的消息:
• @MessageMapping: 这个注释与应用程序前缀有关,这意味着客户端需要向前缀+映射发送一条消息。在我们的例子中,他是/my-app/chat-room。这个注释由@Controller类的方法支持,并且这个值可以被当作ant-style、散列分隔和路径模式来对待。有了这个注释,您就有了可以用作方法参数的其他注释,包括@Payload, @Header, @Headers, @DestinationVariable, and java.security.Principal.
• @SendTo: 你已经知道这个注解;它和我们在JMS和RabbitMQ章节中使用的是相同的。在这种情况下,这个注解用于向任何其他目的地发送一条消息。
尽管我们没有使用@SubscribeMapping,但是您可以在@Controller注释类中使用它,这是您想要用来处理传入消息的方法。当您需要获得@SendTo的响应副本时,这通常是有用的。
另一个我们没有使用的实用程序类是SimpleMessagingTemplate。它可以用来发送消息。例如:
@Controller
public class AnotherController {@Autowiredprivate SimpMessagingTemplate template;@RequestMapping(path="/rate/new", method = RequestMethod.POST)public void newRates(Rate rate) {this.template.convertAndSend("/topic/new-rate", rate);}
}
正如您所看到的,使用WebSockets技术实现subscriber/publisher模型非常简单。AnotherController通过发送一条消息(ChatMessage)来充当客户端。
接下来,让我们看看另一个客户端。打开src/main/resources/static/sockjs-stomp.html页面。参见清单7-7。
Listing 7-7. Snippet of sockjs-stomp.html
<script>$(function(){var socket = new SockJS('http://localhost:8080/stomp-endpoint');var stompClient = Stomp.over(socket);stompClient.connect({}, function (frame) {console.log('Connected: ' + frame);stompClient.subscribe('/topic/chat-room', function (data) {console.log('>>>>> ' + data);var json = JSON.parse(data.body);$("#output").append("<span><strong>" + json.user + "</strong>: <em>" + json.message +"</em></span><br/>");});});$("#send").click(function(){var chatMessage = {}chatMessage["user"] = $("#user").val();chatMessage["message"] = $("#message").val();stompClient.send("/my-app/chat-room",{},JSON.stringify(chatMessage));});});
</script>
清单7-7显示了JavaScript客户端。让年代分析它:
• SockJS: 这是一个模拟WebSockets协议的JavaScript库。这个对象连接到服务器端指定的/stomp端点。有关此库的更多信息,请访问http://sockjs.org/。
• STOMP: 这是一个使用WebSockets协议的JavaScript库,通常需要SockJS对象。要了解更多信息,请访问http://jmesnil.net/stomp-websocket/doc/。
• connect: 这是一个回调,当连接与服务器建立时将被调用。
• subscribe: 这是一个回调,当在订阅的目的地有一条消息时,它将被调用。
• send: 该方法将向所提供的目的地发送一条消息。
这是在JavaScript客户端中使用SockJS和STOMP的非常简单的方法。现在您已经准备好运行websockets-demo项目了。
注意:在本节中运行websocket-demo项目之前,请确保LlWebSocketConfig类是禁用的(通过注释掉@Configuration和@Enablewebsocket注释)。
运行这个项目并打开一个浏览器。去http://localhost:8080/sockjs-stomp.html页面。您将看到类似于前面的示例。如果您感兴趣的话,您可以到浏览器的开发者控制台查看控制台日志。见图7 - 10。
Figure 7-10. Browser’s console log
图7-10显示了连接日志。现在打开第二个浏览器窗口并访问相同的URL。这个想法是为了模拟两个客户的通信。接下来,发送一些消息并查看结果。参见图7-11和7-12。
Figure 7-11. Two browser clients
Figure 7-12. Application logs
图7-11显示了两个客户端使用SockJS和STOMP通过WebSockets发送消息。
图7-12显示了SimpleController日志和ChatMessage正在由聊天室方法处理(这是因为@MessageMapping和@SendTo注解)。
这就是你如何用Spring Boot和spring-websocket模块轻松创建聊天室的方式。
问:如何使用Spring创建SockJS客户端?假设您需要使用STOMP将消息发送到远程WebSockets连接。看一下WebSocketsDemoApplication类中的代码,它被注释掉了。您将看到如何使用SockJSClient和WebSocketsStompClient类。
三:Using RabbitMQ as a STOMP Broker Relay
您是否想知道如果您的应用程序需要更多的支持,更可伸缩,会发生什么?一种解决方案是添加几个Spring Boot应用程序,这些应用程序启用了WebSockets代理,然后在它们前面添加一个负载均衡器。这样就能获得高可用性,因为您需要为应用程序添加逻辑和行为,这样,如果其中一个应用挂掉了,其他的应用还能继续对客户端作出响应。
好消息是spring-websocket模块有一种方法可以使用外部消息中间件:RabbitMQ。RabbitMQ将STOMP协议作为一个插件。它还包括真正的高可用性和建立集群的简单方法。
按照以下步骤将RabbitMQ作为STOMP中继添加到您的应用程序中:
1. 确保启用RabbitMQ STOMP插件:
$ rabbitmq-plugins enable rabbitmq_stomp
$ rabbitmq-plugins enable rabbitmq_web_stomp
2. 将以下依赖项添加到pom.xml文件.
<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId>
</dependency>
<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-net</artifactId>
</dependency>
<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.8.Final</version>
</dependency>
3. 配置类似于以下代码的WebSocketsConfig类:
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {config.setApplicationDestinationPrefixes(props.getAppDestinationPrefix());config.enableStompBrokerRelay("/topic", "/queue").setRelayPort(61613);
}
与前一个版本不同的是,现在configureMessageBroker方法中,您正在配置enableStompBrokerRelay(使用/topic和/queue),并使用setRelayPort方法添加STOMP端口(值61613,RabbitMQ的STOMP端口)。
这样就。您现在可以使用RabbitMQ作为消息代理。在运行项目之前,请确保RabbitMQ服务已经启动并运行。然后您可以运行这个项目并使用相同的sockjs-stomp.html页面。这里的重要部分是密切关注RabbitMQ控制台,以查看连接和队列。
四.Currency Project
看一下rest-api-websockets项目。您将会找到RateWebSocketsConfig类,它与另一个项目非常相似。其思想是,货币项目有一个简单的WebSockets代理,它将通过WebSockets协议接受任何客户端连接。
每当有一个新的利率发布时,它都会向客户端发送一条消息到/rat/new端点。看一看这个:
• RateWebSocketsConfig:该类具有WebSockets消息传递所需的配置。
• CurrencyController: 添加新利率方法中的该类有以下语句
webSocket.convertAndSend("/rate/new", currencyExchange);
• src/main/resources/public/index-ws.html: 这个网页有定义连接到服务器的客户端的代码。看一看;它非常简单。
要测试它,只需在命令行中添加一个简单的post
$ curl -i -X POST -H "Content-Type: application/json" -H "Accept: application/json"
-d '{"base":"USD","date":"2017-02-15","rates":[{"code":"EUR","rate":0.82857,
"date":"2017-02-15"},{"code":"JPY","rate":105.17,"date":"2017-02-15"},
{"code":"MXN","rate":22.232,"date":"2017-02-15"},
{"code":"GBP","rate":0.75705,"date":"2017-02-16"}]}' localhost:8080/currency/new
您还可以使用任何其他REST客户端,比如POSTMAN:https://www.getpostman.com/。在发布新利率之后,您将看到面板中的利率,如图7-13所示。
Figure 7-13. Currency exchange through WebSockets
五:总结
本章讨论了使用Spring websocket模块和Spring Boot的WebSockets消息。
您了解了WebSockets是如何使用HTTP握手的,然后切换到TCP连接。您看到了如何创建低级别服务器/客户端的示例。
您看到了如何使用SockJS和STOMP来促进异步或事件驱动消息传递的通信。您还学习了如何配置RabbitMQ,并将其用作STOMP转发消息。您在货币交换项目中看到了一些代码,它为连接到该服务的任何客户端发送新利率。
下一章将向您展示如何使用Spring Integration module将您的代码与多种技术集成在一起。
六:源代码
https://gitee.com/micai/micai-spring-message/tree/master/websocket-demo
https://gitee.com/micai/micai-spring-message/tree/master/rest-api-websockets
这篇关于Spring Boot Messaging Chapter 7 Web Messaging的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!