2022-02-07 Springmvc+Websocket 发送JSON消息实例

WebSocket 是HTML5一种新的协议,实现了浏览器与服务器全双工通信。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,服务端与客户端通过此TCP连接进行实时通信。

1. 开发环境

    系统:macOS High Sierra 10.13.6

    开发工具:IntelliJ IDEA 2020.1.4 (Community Edition)

    Java, Maven 和 IDEA 的安装配置过程,见 IDEA创建Maven Quickstart项目


2. 在 IDEA上创建项目

    Name: SpringmvcWebsocket

    GroupId: com.example

    ArtifactId: SpringmvcWebsocket

    项目目录结构,参考 IDEA创建Maven Webapp项目


3. 使用 tomcat7-maven-plugin, 将 tomcat 内嵌运行

    1) 修改 pom.xml:

        <project ... >

            <build>

                <plugins>

                    <plugin>

                        <groupId>org.apache.tomcat.maven</groupId>

                        <artifactId>tomcat7-maven-plugin</artifactId>

                        <version>2.2</version>

                        <configuration>               

                            <path>/</path>

                            <port>9090</port>

                            <uriEncoding>UTF-8</uriEncoding>

                        </configuration>

                    </plugin>

                </plugins>

            </build>

        </project>

        *注: path 项目访问路径, 本例:localhost:9090, 如果配置的aa,则访问路径为localhost:9090/aa;uriEncoding 非必选项。

        2) 运行

            Run -> Edit configurations -> Click "+" -> Select "Maven"

                Command line: clean tomcat7:run

                Name: SpringmvcWebsocket [clean,tomcat7:run]

                Before Launch:

                    Click "+", select "Launch Web Browser"

                    Browser: default

                    Url: http://localhost:9090

            -> OK

            Run -> Run "SpringmvcWebsocket [clean,tomcat7:run]"

            * tomcat7 除了支持 run, 还可以支持如下 Goal: 

                        help, deploy, deploy-only, redeploy, redeploy-only, undeploy,

                        exec-war, exec-war-only, run-war, run-war-only,

                        standalone-war, standalone-war-only,  shutdown

        3) 打包 War

            Run -> Edit configurations -> Click "+" -> Select "Maven"

                Command line: clean tomcat7:run-war

                Name: SpringmvcWebsocket [clean,tomcat7:run-war]

                Before Launch:

                    Click "+", select "Launch Web Browser"

                    Browser: default

                    Url: http://localhost:9090

            -> OK

            Run -> Run "SpringmvcWebsocket [clean,tomcat7:run-war]"

            可见到 target/SpringmvcWebsocket.war


4. 导入 spring-webmvc, Servlet & JSTL, websocket, fastjson

    访问 http://www.mvnrepository.com/,查询 ...

    修改 pom.xml

        <project ... >
            ...

            <dependencies>

                ...

                <!-- Servlet & JSTL -->
                <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
                <dependency>
                  <groupId>javax.servlet</groupId>
                  <artifactId>javax.servlet-api</artifactId>
                  <version>4.0.1</version>
                  <scope>provided</scope>
                </dependency>
                <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
                <dependency>
                  <groupId>javax.servlet</groupId>
                  <artifactId>jstl</artifactId>
                  <version>1.2</version>
                </dependency>
                <!-- https://mvnrepository.com/artifact/taglibs/standard -->
                <dependency>
                  <groupId>taglibs</groupId>
                  <artifactId>standard</artifactId>
                  <version>1.1.2</version>
                </dependency>

                <!-- Spring web/mvc -->
                <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
                <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-web</artifactId>
                  <version>4.3.9.RELEASE</version>
                </dependency>
                <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
                <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-webmvc</artifactId>
                  <version>4.3.9.RELEASE</version>
                </dependency>

                <!-- Spring Websocket -->
                <!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket -->
                <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-websocket</artifactId>
                  <version>4.3.9.RELEASE</version>
                </dependency>
                <!-- https://mvnrepository.com/artifact/org.springframework/spring-messaging -->
                <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-messaging</artifactId>
                  <version>4.3.9.RELEASE</version>
                </dependency>

                <!-- JSON -->
                <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
                <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
                  <version>1.2.79</version>
                </dependency>

                ...

            </dependencies>
       
            ...   

        </project>

        在IDE中项目列表 -> 点击鼠标右键 -> Maven -> Reload Project


5. 支持 SpringMVC

1) 修改 src/main/webapp/WEB-INF/web.xml

<!DOCTYPE web-app PUBLIC 

    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 

    "http://java.sun.com/dtd/web-app_2_3.dtd" > 

<web-app> 

  ...

  <!-- Spring mvc 适配器 --> 

    <servlet> 

        <servlet-name>springMVC</servlet-name> 

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 

        <init-param> 

            <param-name>contextConfigLocation</param-name> 

            <param-value>classpath:springmvc-beans.xml</param-value> 

        </init-param> 

        <load-on-startup>1</load-on-startup> 

    </servlet> 

    <servlet-mapping> 

        <servlet-name>springMVC</servlet-name> 

        <url-pattern>/</url-pattern> 

    </servlet-mapping>

    ...

</web-app>

2) 添加 src/main/resources/springmvc-beans.xml  (中间目录如果不存在,新建目录,下同)

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

<beans xmlns="http://www.springframework.org/schema/beans"

      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xmlns:mvc="http://www.springframework.org/schema/mvc"

      xmlns:context="http://www.springframework.org/schema/context"

      xsi:schemaLocation="

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context.xsd

        http://www.springframework.org/schema/mvc

        http://www.springframework.org/schema/mvc/spring-mvc.xsd"

      default-init-method="init"

      default-destroy-method="destroy">

    <!-- Scan package -->

    <context:component-scan base-package="com.example"/>

    <mvc:annotation-driven />

    <!-- MVC viewResolver -->

    <bean id="viewResolver"         class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <property name="prefix" value="/WEB-INF/jsp/"/>

        <property name="suffix" value=".jsp"/>

    </bean>

</beans>


6. 支持静态资源 (html/js/css/images)

    1) 修改 src/main/resources/springmvc-beans.xml

        <beans ...>

            ...

            <!-- html,css,js,images -->

            <mvc:resources location="/static/" mapping="/static/**" />

            ...

        </beans>

        新建目录  src/main/webapp/static

    2) 添加 jQuery

        从  https://jquery.com/ 下载 JQuery 包,添加到:

            src/main/webapp/static/js/jquery-1.12.2.min.js

        * jQuery版本根据项目需要,这里用1.12.2, CSS和图片也是放到static目录下

    3) 添加 src/main/webapp/static/test.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>Static HTML</title>

    <script language="javascript" src="js/jquery-1.12.2.min.js"></script>

</head>

<body>

    <h3>Static HTML Page</h3>

    <p>&nbsp;</p>

    <p id="message"></p>

    <script type="text/javascript">

        $(document).ready(function() {

            console.log("Static HTML Page");

            $("#message").html("JQuery is ready");

        });

    </script>

</body>

</html>

    http://localhost:9090/static/test.html


7. 支持 spring-websocket

    1) 添加 src/main/java/com/example/ws/WSInterceptor.java

        package com.example.ws;

        import org.springframework.http.server.ServerHttpRequest;
        import org.springframework.http.server.ServerHttpResponse;
        import org.springframework.web.socket.WebSocketHandler;
        import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

        import java.util.Map;

        public class WSInterceptor extends HttpSessionHandshakeInterceptor {
            @Override
            public boolean beforeHandshake(ServerHttpRequest request,
                                           ServerHttpResponse response,
                                           WebSocketHandler wsHandler,
                                           Map<String, Object> attributes) throws Exception {
                return super.beforeHandshake(request, response, wsHandler, attributes);
            }

            @Override
            public void afterHandshake(ServerHttpRequest request,
                                       ServerHttpResponse response,
                                       WebSocketHandler wsHandler,
                                       Exception ex) {
                super.afterHandshake(request, response, wsHandler, ex);
            }

        }

    2) 添加 src/main/java/com/example/ws/WSTextHandler.java

        package com.example.ws;

        import java.util.List;
        import java.util.Map;
        import java.util.HashMap;
        import java.util.concurrent.CopyOnWriteArrayList;

        import org.springframework.web.socket.CloseStatus;
        import org.springframework.web.socket.TextMessage;
        import org.springframework.web.socket.WebSocketMessage;
        import org.springframework.web.socket.WebSocketSession;
        import org.springframework.web.socket.handler.TextWebSocketHandler;

        import com.alibaba.fastjson.JSON;
        import com.alibaba.fastjson.JSONObject;

        public class WSTextHandler extends TextWebSocketHandler{

            private List<WebSocketSession> clientSessions = new CopyOnWriteArrayList<>();

            @Override
            public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
                clientSessions.remove(session);
                System.out.println("Client (" + session.getId() + ") closed, status: " + status);
            }

            @Override
            public void afterConnectionEstablished(WebSocketSession session) throws Exception {
                clientSessions.add(session);
                System.out.println("Client (" + session.getId() + ") connected ... ");
            }

            @Override
            public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {

                Map<String, Object> retMap = new HashMap<>();

                JSONObject recvJson = (JSONObject)JSON.parse(message.getPayload().toString());
                String opt = recvJson.getString("operation");

                if ("command".equals(opt)) {
                    retMap.put("ret", "data");
                    retMap.put("message", "Get command '" + recvJson.getString("param") + "' from client");
                    session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));
                } else if ("close".equals(opt)) {
                    retMap.put("ret", "finish");
                    retMap.put("description", "Finish directly");
                    session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));
                } else {
                    retMap.put("ret", "error");
                    retMap.put("description", "Invalid data format");
                    session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));
                }

            }

        }


    3) 添加 src/main/java/com/example/ws/WSConfig.java

        package com.example.ws;

        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.socket.config.annotation.EnableWebSocket;
        import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
        import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
        import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

        @Configuration
        @EnableWebSocket
        public class WSConfig implements WebSocketConfigurer{

            @Override
            public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                registry.addHandler(new WSTextHandler(), "/websocket")
                        .addInterceptors(new HttpSessionHandshakeInterceptor())
                        .setAllowedOrigins("*"); // Allow cross site
            }
        }


8. 视图和控制器

    1) 添加 src/main/webapp/WEB-INF/jsp/home.jsp

        <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  isELIgnored="false" %>
        <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>Home Page</title>
            <script language="javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.12.2.min.js"></script>
        </head>
        <body>
            <c:if test="${not empty message}">
                <p style="color: blue;">${message}</p>
            </c:if>
            <p>&nbsp;</p>

            <form action="" method="post">
                <p>
                    <label>Websocket:</label><br/>
                    <input type="text" name="ws_url" id="ws_url" value="ws://${pageContext.request.getServerName()}:${pageContext.request.getServerPort()}${pageContext.request.contextPath}/websocket" style="height: 32px; width:50%" />
                </p>
                <p>
                    <button type="button" id="btn_connect" class="btn btn-default btn-sm" onClick="javascript: connectWebsocket();">
                        Connect
                    </button>
                    <button type="button" id="btn_close" class="btn btn-default btn-sm" onClick="javascript: closeWebsocket();" style="display: none;">
                        Close
                    </button>
                </p>
            </form>

            <p>&nbsp;</p>
            <div id="cmd_result" style="padding: 15px; background-color: #e2e2e2; width: 50%;  font-size: 12px; min-height: 120px;">
            </div>
            <p>&nbsp;</p>

            <script type="text/javascript">
                var globalSocket = null;

                $(document).ready(function() {
                    console.log("Home Page");
                });

               function connectWebsocket() {
                    var wsUrl = $("#ws_url").val();
                    if (wsUrl == '') {
                        alert("Please enter url");
                        $("#ws_url").focus();
                        return;
                    }

                    if (globalSocket == null) {
                        $("#cmd_result").html('');
                        $("#btn_connect").attr("disabled", "disabled");

                        createWebsocket(wsUrl);
                    }
                }

                function createWebsocket(url) {
                    if (globalSocket != null || url == '')
                        return;

                    console.log("createWebsocket(): url = ", url);
                    globalSocket = new WebSocket(url);
                    globalSocket.onopen = funcWSOpen;
                    globalSocket.onclose = funcWSClose;
                    globalSocket.onerror = funcWSError;
                    globalSocket.onmessage = funcWSMessage;
                }

                function closeWebsocket() {
                    if (globalSocket != null) {
                        console.log("closeWebsocket(): send close");
                        globalSocket.send(JSON.stringify({ "operation": "close"}));
                        $("#btn_close").attr("disabled", "disabled");
                    }
                }

                function funcWSOpen(e) {
                    console.log("funcWSOpen(): ", e);
                    $("#cmd_result").html("Executing ... <br><br>");
                    $("#btn_close").removeAttr("disabled");
                    $("#btn_close").css("display", "");

                    var data = {
                        "operation": "command",
                        "param": "test",
                    }
                    globalSocket.send(JSON.stringify(data));
                }

                function funcWSClose(e) {
                    console.log("funcWSClose(): ", e);
                    $("#cmd_result").append("<br>Websocket: close<br>");
                    $("#btn_connect").removeAttr("disabled");
                    $("#btn_close").css("display", "none");
                    globalSocket = null;
                }

                function funcWSError(e) {
                    console.error("funcWSError(): ", e);
                    $("#cmd_result").append("<br>Websocket: error<br>");
                    $("#btn_connect").removeAttr("disabled");
                    $("#btn_close").css("display", "none");
                    globalSocket = null;
                }

                function funcWSMessage(e) {
                    console.log("funcWSMessage(): e.data = ", e.data);
                    var dataObj = JSON.parse(e.data);
                    if (dataObj['ret'] == "data") {
                        $("#cmd_result").append(dataObj['message'] + "<br>");
                    } else if (dataObj['ret'] == "finish") {
                        console.log("funcWSMessage(): ", dataObj['description'])
                        $("#cmd_result").append("<br>Websocket: " + dataObj['description'] + "<br>");
                        globalSocket.close(3009)
                    } else if (dataObj['ret'] == "error") {
                        console.log("funcWSMessage(): ", dataObj['description']);
                        $("#cmd_result").append("<br>Websocket: " + dataObj['description'] + "<br>");
                    } else {
                        $("#cmd_result").append("<br>Websocket: invalid data format<br>");
                    }
                }
            </script>
        </body>
        </html>


    2) 添加 src/main/java/com/example/controller/IndexController.java

        package com.example.controller;

        import java.io.IOException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;

        import org.springframework.stereotype.Controller;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RequestMethod;
        import org.springframework.ui.ModelMap;

        @Controller
        @RequestMapping("/")
        public class IndexController {
            @RequestMapping(method = RequestMethod.GET)
            public String home(ModelMap modelMap) {
                modelMap.addAttribute("message", "Springmvc Websocket");
                return "home";
            }

        }

    3) 删除 src/main/webapp/index.jsp


9. 运行

    参考第 3 步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容