SpringBoot学习笔记(11):使用WebSocket构建交互式Web应用程序
快速开始
本指南将引导您完成创建“hello world”应用程序的过程,该应用程序在浏览器和服务器之间来回发送消息。 WebSocket是一个非常薄,轻量级的TCP层。它使得非常适合使用“子协议”来嵌入消息。在本指南中,我们将深入研究并使用Spring的STOMP消息来创建交互式Web应用程序。
我们将建立一个服务用来接收一个携有用户名称的消息,然后作为回应,推送一个问候语到客户订阅的队列中。
本节内容的思维导图大致如下:
STOMP消息
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。
STOMP是一个非常简单和容易实现的协议,其设计灵感源自于HTTP的简单性。尽管STOMP协议在服务器端的实现可能有一定的难度,但客户端的实现却很容易。例如,可以使用Telnet登录到任何的STOMP代理,并与STOMP代理进行交互。
STOMP协议与2012年10月22日发布了最新的STOMP 1.2规范。
要查看STOMP 1.2规范,见: https://stomp.github.io/stomp-specification-1.2.html
基于Maven构建应用
首先,设置一个基本的构建脚本。在使用Spring构建应用程序时,您可以使用任何您喜欢的构建系统,但此处包含了使用Maven所需的代码。
如果您不熟悉Maven,请参阅使用Maven构建Java项目。
<!--添加WebSocket依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.1.0</version>
</dependency>
创建一个资源表示类
现在您已经设置了项目和构建系统,您可以创建STOMP消息服务。通过考虑服务交互来开始这个过程。
该服务将接受一个包含在STOMP消息中name的消息,该消息的主体是JSON对象。如果给出的名称是“Fred”,那么消息可能如下所示:
{
"name": "Fred"
}
要对带有name的消息建模,可以使用name属性和相应的getName()方法创建一个普通的Java对象:
public class HelloMessage {
private String name;
public HelloMessage(String name){
=name;
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
}
在接收到消息并提取名称后,服务将通过创建问候语并在客户端订阅的单独队列上发布该问候语来处理它。问候语也将是一个JSON对象,可能看起来像这样:
public class GreetingMessage {
private String context;
public GreetingMessage(String context) {
this.context = context;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
}
Spring将使用Jackson JSON库自动封送以上类型为JSON的实例。接下来,您将创建一个控制器来接收问候消息并发送问候消息。
创建一个消息处理控制器
import com.mrsaber.filebar.domain.GreetingMessage;
import com.mrsaber.filebar.domain.HelloMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class GreetController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public GreetingMessage greeting(HelloMessage message) throws Exception
{
Thread.sleep(1000);
return new GreetingMessage("Hello,"+message.getName());
}
}
这个控制器看起来是简洁又简单的,但是已经做了大量的工作。让我们来分解一下这个操作。
- @MessageMapping注解确保如果将消息发送到目标“/ hello”,则调用greeting()方法。
- 消息的原数据绑定到HelloMessage对象,该对象将传递给greeting()。
- 在内部,该方法的实现通过使线程休眠1秒来模拟处理延迟。这是为了演示客户端发送消息后,只要需要异步处理消息,服务器就可以使用。客户可以继续进行它需要做的任何工作而无需等待响应。
- 在1秒延迟之后,greeting()方法创建一个Greeting对象并返回它。返回值将广播给@SendTo注解中指定的“/ topic / greetings”的所有订阅者。
配置Spring以进行STOMP消息传递
现在已经创建了服务的基本组件,您可以配置Spring以启用WebSocket和STOMP消息传递。
创建一个名为WebSocketConfig的Java类,如下所示:
创建浏览器客户端
HTML部分
<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<link href="webs/bootstrap.min.css" rel="stylesheet">
<script src="webs/jquery.min.js"></script>
<script src="webs/sockjs.min.js"></script>
<script src="webs/stomp.min.js"></script>
<script src="webs/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">WebSocket connection:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">What is your name?</label>
<input type="text" id="name" class="form-control" placeholder="Your name here...">
</div>
<button id="send" class="btn btn-default" type="submit">Send</button>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>Greetings</th>
</tr>
</thead>
<tbody id="greetings">
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
JavaScript部分
我们编写了一个app.js文件用来存放我们的业务逻辑。
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}
function connect() {
var socket = new SockJS('localhost:8080/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).context);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
$("#greetings").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});
测试程序