【Web应用架构】使用WebSocket构建交互式web应用程序

简介: 【Web应用架构】使用WebSocket构建交互式web应用程序

本指南引导您完成创建“Hello, world”应用程序的过程,该应用程序在浏览器和服务器之间来回发送消息。WebSocket是TCP之上的一个轻薄的层。这使得它适合使用“子协议”来嵌入消息。在本指南中,我们使用STOMP消息传递和Spring创建交互式web应用程序。

 

你将建立什么

您将构建一个接收带有用户名称的消息的服务器。作为响应,服务器将把问候语推送到客户机订阅的队列中。

 

你需要的东西

  • 大约15分钟
  • 最喜欢的文本编辑器或IDE
  • JDK 1.8或更高版本
  • Gradle 4+或Maven 3.2+
  • 你也可以直接导入代码到你的IDE:
  • Spring Tool Suite(STS)
  • IntelliJ IDEA

 

如何完成本指南

与大多数Spring入门指南一样,您可以从头开始并完成每个步骤,或者可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。

 

要从头开始,先从 Starting with Spring Initializr开始。

 

要跳过基本步骤,请做以下步骤:

 

下载并解压缩本指南的源存储库,或者使用Git克隆它:

 

  • 向前跳转Create a Resource Representation Class.
  • 完成后,可以对照gs-messaging-stomp-websocket/complete中的代码检查结果。

 

从Spring Initializr开始

对于所有Spring应用程序,都应该从Spring Initializr开始。Initializr提供了一种快速获取应用程序所需的所有依赖项的方法,并为您进行了大量设置。这个例子只需要Websocket依赖。下图显示了为这个示例项目设置的Initializr:

前面的图像显示了选择Maven作为构建工具的Initializr。你也可以使用Gradle。它还显示了com的值。示例和消息传递-stomp-websocket分别作为组和工件。您将在本示例的其余部分使用这些值。

下面的清单显示了当你选择Maven时创建的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.2.2.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId>

<artifactId>messaging-stomp-websocket</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>messaging-stomp-websocket</name>

<description>Demo project for Spring Boot</description>


<properties>

<java.version>1.8</java.version>

</properties>


<dependencies>

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>


<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-test</artifactId>

  <scope>test</scope>

  <exclusions>

  <exclusion>

    <groupId>org.junit.vintage</groupId>

    <artifactId>junit-vintage-engine</artifactId>

  </exclusion>

  </exclusions>

</dependency>

</dependencies>


<build>

<plugins>

  <plugin>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-maven-plugin</artifactId>

  </plugin>

</plugins>

</build>


</project>

 

下面的清单显示了构建过程。gradle文件是创建时,你选择gradle:

plugins {

id 'org.springframework.boot' version '2.2.2.RELEASE'

id 'io.spring.dependency-management' version '1.0.8.RELEASE'

id 'java'

}


group = 'com.example'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'


repositories {

mavenCentral()

}


dependencies {

implementation 'org.springframework.boot:spring-boot-starter-websocket'

testImplementation('org.springframework.boot:spring-boot-starter-test') {

exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'

}

}


test {

useJUnitPlatform()

}

 

添加依赖关系

在这种情况下,Spring Initializr并没有提供您需要的一切。对于Maven,您需要添加以下依赖项:

<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.1-1</version>
</dependency>

下面的清单显示了完成的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.2.2.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId>

<artifactId>messaging-stomp-websocket</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>messaging-stomp-websocket</name>

<description>Demo project for Spring Boot</description>


<properties>

<java.version>1.8</java.version>

</properties>


<dependencies>

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>


<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-test</artifactId>

  <scope>test</scope>

  <exclusions>

  <exclusion>

    <groupId>org.junit.vintage</groupId>

    <artifactId>junit-vintage-engine</artifactId>

  </exclusion>

  </exclusions>

</dependency>

</dependencies>


<build>

<plugins>

  <plugin>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-maven-plugin</artifactId>

  </plugin>

</plugins>

</build>


</project>

如果你使用Gradle,你需要添加以下依赖:

implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
implementation 'org.webjars:bootstrap:3.3.7'
implementation 'org.webjars:jquery:3.1.1-1'

下面的清单显示了完成的构建。gradle文件:

plugins {

id 'org.springframework.boot' version '2.2.2.RELEASE'

id 'io.spring.dependency-management' version '1.0.8.RELEASE'

id 'java'

}


group = 'com.example'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'


repositories {

mavenCentral()

}


dependencies {

implementation 'org.springframework.boot:spring-boot-starter-websocket'

testImplementation('org.springframework.boot:spring-boot-starter-test') {

exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'

}

}


test {

useJUnitPlatform()

}

 

创建资源表示类

现在已经设置了项目和构建系统,可以创建STOMP消息服务了。

 

通过考虑服务交互来开始流程。

 

服务将接受主体为JSON对象的STOMP消息中包含名称的消息。如果名称是Fred,则消息可能类似于以下内容:

 

{

   "name": "Fred"

}

要对带有名称的消息进行建模,您可以创建一个普通的旧Java对象,该对象带有name属性和相应的getName()方法,如下所示(来自src/main/ Java /com/example/messagingstompwebsocket/HelloMessage.java):

 

package com.example.messagingstompwebsocket;
public class HelloMessage {
  private String name;
  public HelloMessage() {
  }
  public HelloMessage(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

在接收到消息并提取名称后,服务将通过创建一个问候语并将该问候语发布到客户机订阅的单独队列中来处理它。问候语也是一个JSON对象,如下所示:

 

{
    "content": "Hello, Fred!"
}

要对问候表示建模,添加另一个具有内容属性和相应getContent()方法的普通Java对象,如下所示(来自src/main/ Java /com/example/messagingstompwebsocket/Greeting.java):

 

package com.example.messagingstompwebsocket;
public class Greeting {
  private String content;
  public Greeting() {
  }
  public Greeting(String content) {
    this.content = content;
  }
  public String getContent() {
    return content;
  }
}

Spring将使用Jackson JSON库自动将Greeting类型的实例编组为JSON。

 

接下来,您将创建一个控制器来接收hello消息并发送问候消息。

 

创建一个消息处理控制器

在Spring处理STOMP消息传递的方法中,可以将STOMP消息路由到@Controller类。例如,GreetingController(来自src/main/java/com/example/messagingstompwebsocket/GreetingController.java)被映射为处理/hello目的地的消息,如下所示:

 

package com.example.messagingstompwebsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller
public class GreetingController {
  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
  }
}

这个控制器是简洁和简单的,但是还有很多工作要做。我们一步一步地把它分解。

 

@MessageMapping注释确保,如果消息被发送到/hello目的地,则调用greeting()方法。

 

消息的有效负载被绑定到HelloMessage对象,该对象被传递到greeting()。

 

在内部,该方法的实现通过导致线程休眠一秒来模拟处理延迟。这是为了演示,在客户机发送消息后,服务器可以按照需要花费多长时间来异步处理消息。客户机可以继续执行它需要执行的任何工作,而无需等待响应。

 

延迟一秒之后,greeting()方法创建一个greeting对象并返回该对象。返回值广播给/topic/greetings的所有订阅者,如@SendTo注释中指定的那样。注意,来自输入消息的名称是经过清理的,因为在本例中,它将回显并在客户端浏览器DOM中重新呈现。

为STOMP消息传递配置Spring

现在已经创建了服务的基本组件,您可以配置Spring来启用WebSocket和STOMP消息传递。

 

创建一个名为WebSocketConfig的Java类,它类似于下面的清单(来自src/main/ Java /com/example/messagingstompwebsocket/WebSocketConfig.java):

 

package com.example.messagingstompwebsocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
  }
  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/gs-guide-websocket").withSockJS();
  }
}

WebSocketConfig带有@Configuration注释,表示它是一个Spring配置类。它还使用@EnableWebSocketMessageBroker进行了注释。顾名思义,@EnableWebSocketMessageBroker支持由消息代理支持的WebSocket消息处理。

 

configureMessageBroker()方法实现了WebSocketMessageBrokerConfigurer中的默认方法来配置消息代理。它首先调用enableSimpleBroker(),使一个简单的基于内存的消息代理能够将问候消息带回以/topic为前缀的目的地上的客户机。它还为绑定带有@MessageMapping注释的方法的消息指定/app前缀。这个前缀将用于定义所有的消息映射。例如,/app/hello是GreetingController.greeting()方法映射要处理的端点。

 

registerStompEndpoints()方法注册/gs-guide-websocket端点,启用SockJS回退选项,以便在WebSocket不可用的情况下使用替代传输。SockJS客户机将尝试连接到/gs-guide-websocket,并使用最好的传输(websocket、xhr-streaming、xhr-polling,等等)。

创建浏览器客户端

有了服务器端部分,您就可以将注意力转向将消息发送到服务器端和从服务器端接收消息的JavaScript客户机了。

 

创建一个类似如下清单的index.html文件(来自src/main/resources/static/index.html):

 

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/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>

这个HTML文件导入了SockJS和STOMP javascript库,这些库将用于通过STOMP over websocket与我们的服务器通信。我们还导入app.js,它包含客户端应用程序的逻辑。下面的清单(来自src/main/resources/static/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('/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).content);
        });
    });
}
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(); });
});

这个JavaScript文件需要理解的主要部分是connect()和sendName()函数。

 

connect()函数使用SockJS和stomp.js打开到/gs-guide-websocket的连接,这是我们的SockJS服务器等待连接的地方。成功连接后,客户端订阅/topic/greetings目的地,服务器将在其中发布问候消息。当目的地接收到问候语时,它将向DOM追加一个段落元素以显示问候语消息。

 

sendName()函数检索用户输入的名称,并使用STOMP客户机将其发送到/app/hello目的地(在那里GreetingController.greeting()将接收它)。

使应用程序可执行

Spring Boot为您创建一个应用程序类。在这种情况下,它不需要进一步修改。您可以使用它来运行此应用程序。下面的清单(来自src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java)显示了应用程序类:

 

package com.example.messagingstompwebsocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MessagingStompWebsocketApplication {
  public static void main(String[] args) {
    SpringApplication.run(MessagingStompWebsocketApplication.class, args);
  }
}

@SpringBootApplication是一个方便的注释,添加了以下所有内容:

 

  • @Configuration:标记类作为应用程序上下文bean定义的源。

  • @EnableAutoConfiguration:告诉Spring Boot根据类路径设置、其他bean和各种属性设置开始添加bean。例如,如果spring-webmvc在类路径中,这个注释将应用程序标记为web应用程序并激活关键行为,比如设置一个DispatcherServlet。

  • 告诉Spring在com - example包中寻找其他组件、配置和服务,让它找到控制器。

 

main()方法使用Spring引导的Spring application. run()方法来启动应用程序。您注意到没有一行XML吗?也没有web.xml文件。这个web应用程序是100%纯Java的,您不必配置任何管道或基础设施。

 

构建一个可执行JAR

您可以使用Gradle或Maven从命令行运行该应用程序。您还可以构建一个包含所有必要的依赖项、类和资源的可执行JAR文件并运行它。构建可执行jar使得在整个开发生命周期中,跨不同环境,等等,将服务作为应用程序进行发布、版本和部署变得更加容易。

 

如果你使用Gradle,你可以使用./gradlew bootRun来运行这个应用程序。或者,您可以使用./gradlew build构建JAR文件,然后运行JAR文件,如下所示:

 

java -jar build/libs/gs-messaging-stomp-websocket-0.1.0.jar

如果使用Maven,可以使用./mvnw spring-boot:run来运行应用程序。或者,您可以使用./mvnw clean包构建JAR文件,然后运行JAR文件,如下所示:

java -jar target/gs-messaging-stomp-websocket-0.1.0.jar

这里描述的步骤创建了一个可运行的JAR。您还可以构建一个经典的WAR文件。

将显示日志输出。服务应该在几秒钟内启动并运行。

 

测试服务

现在服务已经运行,将浏览器指向http://localhost:8080并单击Connect按钮。

 

在打开连接时,会询问您的姓名。输入您的姓名并单击Send。您的名字将通过STOMP以JSON消息的形式发送到服务器。经过一秒钟的模拟延迟后,服务器返回一条消息,其中包含在页面上显示的“Hello”问候语。此时,您可以发送另一个名称,或者单击Disconnect按钮关闭连接。

 

总结

恭喜你!您刚刚用Spring开发了一个基于stomp的消息传递服务。

相关文章
|
3天前
|
缓存 Kubernetes 容灾
如何基于服务网格构建高可用架构
分享如何利用服务网格构建更强更全面的高可用架构
|
12天前
|
弹性计算 持续交付 API
构建高效后端服务:微服务架构的深度解析与实践
在当今快速发展的软件行业中,构建高效、可扩展且易于维护的后端服务是每个技术团队的追求。本文将深入探讨微服务架构的核心概念、设计原则及其在实际项目中的应用,通过具体案例分析,展示如何利用微服务架构解决传统单体应用面临的挑战,提升系统的灵活性和响应速度。我们将从微服务的拆分策略、通信机制、服务发现、配置管理、以及持续集成/持续部署(CI/CD)等方面进行全面剖析,旨在为读者提供一套实用的微服务实施指南。
|
11天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
44 5
|
8天前
|
监控 安全 持续交付
构建高效微服务架构:策略与实践####
在数字化转型的浪潮中,微服务架构凭借其高度解耦、灵活扩展和易于维护的特点,成为现代企业应用开发的首选。本文深入探讨了构建高效微服务架构的关键策略与实战经验,从服务拆分的艺术到通信机制的选择,再到容器化部署与持续集成/持续部署(CI/CD)的实践,旨在为开发者提供一套全面的微服务设计与实现指南。通过具体案例分析,揭示如何避免常见陷阱,优化系统性能,确保系统的高可用性与可扩展性,助力企业在复杂多变的市场环境中保持竞争力。 ####
26 2
|
9天前
|
弹性计算 Kubernetes API
构建高效后端服务:微服务架构的深度剖析与实践####
本文深入探讨了微服务架构的核心理念、设计原则及实现策略,旨在为开发者提供一套系统化的方法论,助力其构建灵活、可扩展且易于维护的后端服务体系。通过案例分析与实战经验分享,揭示了微服务在提升开发效率、优化资源利用及增强系统稳定性方面的关键作用。文章首先概述了微服务架构的基本概念,随后详细阐述了其在后端开发中的应用优势与面临的挑战,最后结合具体实例,展示了如何从零开始规划并实施一个基于微服务的后端项目。 ####
|
13天前
|
消息中间件 监控 安全
构建高效微服务架构:最佳实践与挑战
在现代软件开发中,微服务架构因其高度的可扩展性、灵活性和敏捷性而受到青睐。本文深入探讨了构建高效微服务架构的关键策略,包括服务的划分、通信机制、数据管理、部署与监控等方面的最佳实践。同时,文章也分析了在实施过程中可能遇到的挑战,如服务间的依赖管理、数据一致性问题、安全考量及性能优化等,并提出了相应的解决方案。通过实际案例分析,本文旨在为开发者提供一套实用的指南,帮助他们在构建微服务系统时能够有效规避风险,提升系统的健壮性和用户体验。
|
13天前
|
监控 持续交付 API
深入理解微服务架构:构建高效、可扩展的系统
深入理解微服务架构:构建高效、可扩展的系统
29 0
|
7天前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
16天前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
30 3
|
24天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####