Spring Boot教程32——WebSocket

WebSocket为浏览器和服务端提供了双工异步通信功能,即浏览器可以向服务端发送消息,服务端也可以向浏览器发送消息。WebSocket需要IE10+、Chrome13+、Firefox6+。
WebSocket是通过一个socket来实现双工异步通信能力的。但直接使用WebSocket协议开发程序比较繁琐,我们会使用它的子协议STOMP,它是一个更高级别的协议,使用一个基于帧(frame)的格式来定义消息,与Http的request和response类似(具有类似于@RequestMapping的@MessageMapping)。

Spring Boot对内嵌的Tomcat(7或者8)、Jetty9和Undertow使用WebSocket提供了支持。配置源码存于org.springframework.boot.autoconfigure.websocket下。
?

实战

1.新建Spring Boot项目

选择Thymeleaf和Websocket依赖
?

2.广播式

广播式即服务端有消息时,会将消息发送给所有连接了当前endpoint的浏览器。

1>.配置WebSocket

需要在配置类上使用@EnableWebSocketMessageBroker开启WebSocket支持,并通过继承AbsractWebSocketMessageBrokerConfigurer类,重写其方法来配置WebSocket。

package net.quickcodes.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker //1.通过@EnableWebSocketMessageBroker注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样。
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {//2.注册STOMP协议的节点(endpoint),并映射为指定的URL
        registry.addEndpoint("/endpointQuickcodes").withSockJS();//3.注册一个STOMP的endpoint,并指定使用SockJS协议。
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {//4.配置消息代理(Message Broker)
        registry.enableSimpleBroker("/topic"); //5.广播式应配置一个/topic消息代理。
    }

}

2>.浏览器向服务端发送的消息用此类接受

package net.quickcodes.websocket.domain;

public class QuickCodesMessage {
    private String name;

    public String getName(){
        return name;
    }
}

3>.服务端向浏览器发送的消息用此类接受

package net.quickcodes.websocket.domain;

public class QuickCodesResponse {
    private String responseMessage;
    public QuickCodesResponse(String responseMessage){
        this.responseMessage = responseMessage;
    }
    public String getResponseMessage(){
        return responseMessage;
    }
}

4>.演示控制器

package net.quickcodes.websocket.web;

import java.security.Principal;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import net.quickcodes.websocket.domain.QuickCodesMessage;
import net.quickcodes.websocket.domain.QuickCodesResponse;

@Controller
public class QcController {
    @MessageMapping("/welcome")//1.当浏览器向服务端发送请求时,通过@MessageMapping映射/welcome这个地址,类似于@RequestMapping
    @SendTo("/topic/getResponse")//2.当服务端有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
    public QuickCodesResponse say(QuickCodesMessage message) throws Exception{
        Thread.sleep(3000);
        return new QuickCodesResponse("Welcome, "+message.getName() + "!");
    }
}

5>.添加脚本

将stomp.min.js(STOMP协议的客户端脚本)、sockjs.min.js(SockJS的客户端脚本)以及jQuery放置在src/main/resources/static下。

6>.演示页面

在src/main/resources/templates下新建qc.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Spring Boot+WebSocket+广播式</title>

</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2></noscript>
<div>
    <div>
        <button id="connect" onclick="connect();">连接</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
    </div>
    <div id="conversationDiv">
        <label>输入你的名字</label><input type="text" id="name" />
        <button id="sendName" onclick="sendName();">发送</button>
        <p id="response"></p>
    </div>
</div>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>
<script type="text/javascript">
    var stompClient = null;

    function setConnected(connected) {
        document.getElementById(‘connect‘).disabled = connected;
        document.getElementById(‘disconnect‘).disabled = !connected;
        document.getElementById(‘conversationDiv‘).style.visibility = connected ? ‘visible‘ : ‘hidden‘;
        $(‘#response‘).html();
    }

    function connect() {
        var socket = new SockJS(‘/endpointQuickcodes‘); //1.连接SockJS的endpoint名称为/endpointQuickcodes
        stompClient = Stomp.over(socket);//2.使用WebSocket子协议的STOMP客户端
        stompClient.connect({}, function(frame) {//3.连接WebSocket服务端
            setConnected(true);
            console.log(‘Connected: ‘ + frame);
            stompClient.subscribe(‘/topic/getResponse‘, function(respnose){ //4.通过stompClient.subscribe订阅/topic/getResponse目标(destination)发送的消息,这个是在控制器的@SendTo中定义的。
                showResponse(JSON.parse(respnose.body).responseMessage);
            });
        });
    }

    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }

    function sendName() {
        var name = $(‘#name‘).val();
        //5.通过stompClient.send向/welcome目标发送消息,这个是在控制器的@MessageMapping中定义的。
        stompClient.send("/welcome", {}, JSON.stringify({ ‘name‘: name }));
    }

    function showResponse(message) {
        var response = $("#response");
        response.html(message);
    }
</script>
</body>
</html>

7>.配置viewController

为qc.html提供便捷的路径映射

package net.quickcodes.websocket;

import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;

@Configuration
public class WebMvcConfig extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry){
        registry.addViewController("/qc").setViewName("/qc");
    }
}

8>.运行

开启三个浏览器,并都访问http://localhost:8080/qc,分别连接服务器。然后在一个浏览器中发送一条消息,其他浏览器接收消息。

3.点对点式

广播式有自己的应用场景,但不能解决我们的一个常见场景,即消息由谁发送,就由谁接收的场景。
本例演示一个简单聊天室程序。例子中只有两个用户,互相发消息给彼此,因需要用户相关的内容,所以在这里引入最简单的Spring Security相关内容。

1>.在pom.xml添加Spring Security的starter pom:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2>.Spring Security的简单配置

package net.quickcodes.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/","/login").permitAll()//1根路径和/login路径不拦截
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login") //2登陆页面
                .defaultSuccessUrl("/chat") //3登陆成功转向该页面
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }

    //4
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("manon").password("manon").roles("USER")
                .and()
                .withUser("qc").password("qc").roles("USER");
    }
    //5忽略静态资源的拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/static/**");
    }
}

3>.配置WebSocket

package net.quickcodes.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpointQuickcodes").withSockJS();
        registry.addEndpoint("/endpointChat");//注册一个名为/endpointChat的endpoint
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue","/topic"); //增加一个/queue消息代理
    }

}

4>.控制器

package net.quickcodes.websocket.web;

import java.security.Principal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;

import net.quickcodes.websocket.domain.QuickCodesMessage;
import net.quickcodes.websocket.domain.QuickCodesResponse;

@Controller
public class QcController {
    @MessageMapping("/welcome")
    @SendTo("/topic/getResponse")
    public QuickCodesResponse say(QuickCodesMessage message) throws Exception{
        Thread.sleep(3000);
        return new QuickCodesResponse("Welcome, "+message.getName() + "!");
    }

    @Autowired
    private SimpMessagingTemplate messagingTemplate;//1.通过SimpMessagingTemplate向浏览器发送消息

    @MessageMapping("/chat")
    public void handleChat(Principal principal, String msg) { //2.在Spring MVC中,可以直接在参数中获得principal,principal中包含当前用户的信息
        if (principal.getName().equals("qc")) {//3.这是一段硬编码,如果发送人是qc,则发送给manon;如果发送人是manon,则发送给qc,可根据项目实际需要改写此处代码
            messagingTemplate.convertAndSendToUser("manon",
                    "/queue/notifications", principal.getName() + "-send:"
                            + msg);//4.通过messagingTemplate.convertAndSendToUser向用户发送消息,第一个参数是接收消息的用户,第二个是浏览器订阅的地址见,第三个是消息本身。
        } else {
            messagingTemplate.convertAndSendToUser("qc",
                    "/queue/notifications", principal.getName() + "-send:"
                            + msg);
        }
    }
}

5>.登陆页面

src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<meta charset="UTF-8" />
<head>
    <title>登陆页面</title>
</head>
<body>
<div th:if="${param.error}">
    无效的账号和密码
</div>
<div th:if="${param.logout}">
    你已注销
</div>
<form th:action="@{/login}" method="post">
    <div><label> 账号 : <input type="text" name="username"/> </label></div>
    <div><label> 密码: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="登陆"/></div>
</form>
</body>
</html>

6>.聊天页面

src/main/resources/templates/chat.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8" />
<head>
    <title>Home</title>
    <script th:src="@{sockjs.min.js}"></script>
    <script th:src="@{stomp.min.js}"></script>
    <script th:src="@{jquery.js}"></script>
</head>
<body>
<p>
    聊天室
</p>

<form id="wiselyForm">
    <textarea rows="4" cols="60" name="text"></textarea>
    <input type="submit"/>
</form>

<script th:inline="javascript">
    $(‘#wiselyForm‘).submit(function(e){
        e.preventDefault();
        var text = $(‘#wiselyForm‘).find(‘textarea[name="text"]‘).val();
        sendSpittle(text);
    });

    var sock = new SockJS("/endpointChat"); //1.连接名称为/endpointChat的endpoint
    var stomp = Stomp.over(sock);
    stomp.connect(‘guest‘, ‘guest‘, function(frame) {
        stomp.subscribe("/user/queue/notifications", handleNotification);//2.订阅/user/queue/notifications发送的消息,这里与控制器的messagingTemplate.convertAndSendToUser中定义的订阅地址保持一致。这里多了一个/user,并且这个/user是必须的,使用了/user才会发送消息到指定的用户
    });

    function handleNotification(message) {
        $(‘#output‘).append("<b>Received: " + message.body + "</b><br/>")
    }

    function sendSpittle(text) {
        stomp.send("/chat", {}, text);
    }
    $(‘#stop‘).click(function() {sock.close()});
</script>

<div id="output"></div>
</body>
</html>

7>.增加页面的viewController

package net.quickcodes.websocket;

import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
    @Override
    public void addViewControllers(ViewControllerRegistry registry){
        registry.addViewController("/qc").setViewName("/qc");
        registry.addViewController("/login").setViewName("/login");
        registry.addViewController("/chat").setViewName("/chat");
    }
}

8>.运行

分别在两个用户的浏览器下访问http://localhost:8080/login并分别用不同用户名登陆,然后互发消息

时间: 2024-10-21 20:34:53

Spring Boot教程32——WebSocket的相关文章

【视频分享】Spring Boot 教程全集

# [视频分享]Spring Boot 教程全集 ## 获取方式 **方式一:****链接:**[百度网盘](https://pan.baidu.com/s/137KFcoCE-i75vA8FE_OYFQ)==关注公众号极客萧(xiaoyxyj),并且回复关键字:springboot 即可获取下载链接和提取码(注意大小写别错)====如果链接失效,请及时联系我== 原文地址:https://www.cnblogs.com/icefirebug/p/11784818.html

spring boot教程 网盘下载

教程下载地址:https://u18103887.ctfile.com/fs/18103887-309551343 I. Spring Boot文档1. 关于本文档2. 获取帮助3. 第一步4. 使用Spring Boot5. 了解Spring Boot特性6. 迁移到生产环境7. 高级主题II. 开始8. Spring Boot介绍9. 系统要求9.1. Servlet容器10. Spring Boot安装10.1. 为Java开发者准备的安装指南10.1.1. Maven安装10.1.2.

Spring Boot教程35——Spring Data JPA

Hibernate是数据访问解决技术的绝对霸主.JPA是由Hibernate主导的一个基于O/R映射的标准规范.O/R映射即将领域模型类和数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无须关注数据库相关的技术. Spring Data JPA介绍 1.定义数据访问层 使用Spring Data JPA建立数据访问层十分简单,只需定义一个继承JpaRepository的接口即可: public interface PersonRepository extends Jpa

Spring Boot 教程系列学习

Spring Boot基础教程1-Spring Tool Suite工具的安装 Spring Boot基础教程2-RESTfull API简单项目的快速搭建 Spring Boot基础教程3-配置文件详解:Properties和YAML Spring Boot基础教程4-配置文件-多环境配置 Spring Boot基础教程5-日志配置-logback和log4j2 源码地址:https://github.com/roncoo/spring-boot-demo 1.工具下载地址: Eclipse:

Spring Boot教程1——Spring概述

1.Spring发展的过程 1>.第一阶段:Xml配置(需要将xml配置文件分放到不同的配置文件里): 2>.第二阶段:注解配置(提供了声明Bean的注解,如@Component.@Service,此阶段基本配置如数据库配置用xml,业务配置用注解): 3>.第三阶段:Java配置(Spring4.x和Spring Boot都推荐使用Java配置). 2.Spring框架是一个轻量级的企业级开发的一站式解决方案. 所谓解决方案就是可以基于Spring解决Java EE开发的所有问题(Io

Spring Boot教程30——Tomcat配置

本节的配置方法对Tomcat.Jetty和Undertow等内嵌servlet容器都是通用的. 1.Properties配置Tomcat 关于Tomcat的所有属性都在org.springframework.boot.autoconfigure.web.ServerProperties配置类中做了定义,我们只需在application.properties配置属性做配置即可.通用的Servlet容器配置都以“server”作为前缀,而Tomcat特有配置都以“server.tomcat”作为前缀

Spring Boot教程31——Favicon配置

1.默认的Favicon Spring Boot提供了一个默认的favicon,每次访问应用的时候都能看到. ? 2.关闭Favicon 可在application.properties中设置关闭Favicon spring.mvc.favicon.enabled=false ? 3.设置自己的Favicon 只需将自己的favicon.ico文件放置在类路径根目录.类路径META-INF/resources/下.类路径resources/下.类路径static/下或类路径public/下即可.

Spring Boot教程 - 1. 简介

一.导览 本文主要介绍以下几部分: 1. 什么是Spring Boot? 2. 为什么使用Spring Boot? 3. Spring Boot提供哪些功能? 4. 如何使用Spring Boot? 5. Spring Boot有哪些不足? 二.什么是Spring Boot? Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程. 该框架使用了特定的方式(继承starter,约定优先于配置)来进行配置,从而使开发人员不再需要定义

Spring Boot 教程

目录 1.什么是Spring boot starter template 2.Spring boot 自动配置 3.嵌入式Web Server 4.启动应用程序 Spring boot是一个Spring框架模块,它为Spring框架提供RAD(快速应用程序开发)功能.它高度依赖于启动器模板功能,该功能非常强大且完美无缺. 1.什么是Spring boot starter template Spring Boot Starter包含启动特定功能所需的所有相关依赖关系的集合的模板.比如,我们自己创建