WebSocket 两个实现方式 (Spring Boot)

开始

众所周知,在Spring Boot下,实现WebSocket共有两种方式。

第一种是使用由 Jakarta EE 规范提供的 API,也就是 jakarta.websocket 包下的接口。第二种是使用 Spring 提供的支持,也就是 spring-websocket 模块。

1.使用 Jakarta EE 规范提供的 API
jakarta.ee/learn/docs/…
2.使用 spring 提供的支持,也就是 spring-websocket 模块
docs.spring.io/spring-fram…

两种方式,喜欢那种就用那种吧,Jakarta 功能更直接些,Spring更好的屏蔽了服务器差异,封装了更多功能,可以直接配置服务器参数,且更支持Spring生态

无论那种方式,我们都需要添加依赖

整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记的【点击此处即可】即可免费获取

xml

复制代码

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

jakarta-websocket

配置 ServerEndpointExporter

docs.spring.io/spring-boot…
ServerEndpointExporter 会尝试去扫描并注册所有标有 @ServerEndpoint 注解的类。这是 Spring Boot 中使用 Jakarta EE WebSocket 规范的一种方式。

 

java

复制代码

import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig implements WebSocketConfigurer { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }

上例中显示的 Bean 会向底层 WebSocket 容器注册任何带有 @ServerEndpoint 注释的 Bean。当部署到独立的 servlet 容器时,这一角色由 servlet 容器初始化器执行,而不由ServerEndpointExporter

书写 ServerEndpoint

 

java

复制代码

@ServerEndpoint("/websocket") @Component // 交由Spring管理,便于自动注册 public class MyWebSocket implements ApplicationContextAware { @OnOpen public void onOpen(Session session) throws IOException { session.getBasicRemote().sendText(hashCode() + ""); } @OnMessage public void onMessage(String message, Session session) throws IOException { sendMessageToAll(message, rid, session); } @OnError public void onError(Session session, Throwable throwable) { System.out.println("Error"); throwable.printStackTrace(); } @OnClose public void onClose(Session session, CloseReason reason) throws IOException { session.close(); System.out.println("disconnected"); } private void sendMessageToAll(String message, Session session) throws IOException { for (Session sess : session.getOpenSessions()) { if (sess.isOpen() && !sess.equals(session)) { sess.getBasicRemote().sendText(message); } } } // 无参构造器 public MyWebSocket() { System.out.println("MyWebSocket NoConstructor"); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("MyWebSocket setApplicationContext"); } @PostConstruct public void init() { System.out.println("MyWebSocket init-method"); } }

image.png

Encoder Decoder

Encoder Decoder可以在接收msg前与发送msg后对msg进行一些编码解码操作。详见
jakarta.ee/learn/docs/…

路径参数

详见
jakarta.ee/learn/docs/…

握手阶段拦截

jakarta.ee/learn/docs/…

注意

多次实例化

As opposed to servlets, WebSocket endpoints are instantiated multiple times. The container creates an instance of an endpoint per connection to its deployment URI. Each instance is associated with one and only one connection. This facilitates keeping user state for each connection and makes development easier, because there is only one thread executing the code of an endpoint instance at any given time.

验证

我们通过在每次连接后返回对应实例的hashcode来判断是否是同一实例

image.png

image.png

 由此可知,在这种方法下,我们每一次的连接都会创建一个新的webSocket endpoints

注入Bean

The bean shown in the preceding example registers any @ServerEndpoint annotated beans with the underlying WebSocket container. When deployed to a standalone servlet container, this role is performed by a servlet container initializer, and the ServerEndpointExporter bean is not required.

原因:运行时的 WebSocket 连接对象,也就是端点实例,是由服务器创建,而不是 Spring,所以不能使用自动装配

解决

❗️ 要求ServerEndpoint由Spring管理 :heavy_exclamation_mark:

 

java

复制代码

@ServerEndpoint(value = "/channel/echo") @Component // 由 spring 扫描管理 public class EchoChannel implements ApplicationContextAware { // 实现 ApplicationContextAware 接口, Spring 会在运行时注入 ApplicationContext // 全局静态变量,保存 ApplicationContext private static ApplicationContext applicationContext; // 声明需要的 Bean private UserService userService; // 保存 Spring 注入的 ApplicationContext 到静态变量 @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { EchoChannel.applicationContext = applicationContext; } @OnOpen public void onOpen(Session session, EndpointConfig endpointConfig){ // 保存 session 到对象 this.session = session; // 连接创建的时候,从 ApplicationContext 获取到 Bean 进行初始化 this.userService = EchoChannel.applicationContext.getBean(UserService.class); // 在业务中使用 this.userService.foo(); } // .... }

流程如下
Spring启动--Spring注册Bean--Spring将ServerEndpoint移交web容器
我们可以在Spring移交前,在注册Bean时,对Bean的静态变量进行操作,这样就可以把一下参数带入容器
ApplicationContextAware是什么?,参考Bean的生命周期

spring-websocket

Spring Framework 提供了 WebSocket API,您可以用它来编写处理 WebSocket 消息的客户端和服务器端应用程序。

配置 handler

你可以通过去实现WebSocketHandler, 或扩展 TextWebSocketHandler 或 BinaryWebSocketHandler

 

java

复制代码

public class MyWebSocketHandler implements WebSocketHandler { @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) { // ... } }

 

java

复制代码

import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.TextMessage; public class MyHandler extends TextWebSocketHandler { @Override public void handleTextMessage(WebSocketSession session, TextMessage message) { // ... } }

如下例所示,有专门的 WebSocket Java 配置将 WebSocket 处理程序映射到特定 URL

 

java

复制代码

import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket // 必须 public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/myHandler"); } @Bean public WebSocketHandler myHandler() { return new MyHandler(); } }

至此,一个简单的websocket编写完毕

握手阶段拦截器

定制初始 HTTP WebSocket 握手请求的最简单方法是通过 HandshakeInterceptor,它为握手的 "前" 和 "后" 暴露了方法。你可以使用这样的拦截器来处理握手。

 

java

复制代码

@Component public class MyHandshakeInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { System.out.println("beforeHandshake"); return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { System.out.println("afterHandshake"); } }

 

java

复制代码

@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myWebSocketHandler(), "/ws") .addInterceptors(new MyHandshakeInterceptor()) .setAllowedOrigins("*"); }

异常处理器

被修饰的Handler将实现全局异常处理

 

typescript

复制代码

@Bean public ExceptionWebSocketHandlerDecorator myWebSocketHandler() { return new ExceptionWebSocketHandlerDecorator(new MyWebSocketHandler()); }

配置服务器

docs.spring.io/spring-fram…

Encoder Decoder

没找到。。自己使用AOP增强吧

路径参数

不支持

注意

单次实例化

每次访问同一个路径使用的都是同一个handler对象

image.png

Spring 提供了一个 WebSocketHandlerDecorator 基类,您可以用它来为 WebSocketHandler 装饰附加行为。在使用 WebSocket Java 配置或 XML 命名空间时,默认情况下会提供并添加日志和异常处理实现。ExceptionWebSocketHandlerDecorator 会捕获任何 WebSocketHandler 方法产生的所有未捕获异常,并以 1011 状态关闭 WebSocket 会话,该状态表示服务器出错。

注入Bean,同上面的解决方案

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的 Spring Boot 集成 WebSocket 的 Demo,可以实现简单的对话功能: 1. 首先,在 pom.xml 中引入 Spring BootWebSocket 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建一个 WebSocket 配置类,用于配置 WebSocket 相关的内容: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHandler(), "/chat"); } } ``` 3. 创建一个 WebSocketHandler 类,用于处理 WebSocket 的连接和消息: ```java public class WebSocketHandler extends TextWebSocketHandler { private final List<WebSocketSession> sessions = new ArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { sessions.add(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { for (WebSocketSession s : sessions) { s.sendMessage(message); } } } ``` 4. 在 Controller 中添加一个映射,用于返回聊天页面: ```java @Controller public class ChatController { @GetMapping("/") public String index() { return "chat"; } } ``` 5. 创建一个 HTML 页面,用于展示聊天界面和发送消息: ```html <!DOCTYPE html> <html> <head> <title>Chat</title> </head> <body> <div id="messages"></div> <form id="message-form"> <input type="text" id="message-input"> <button type="submit">Send</button> </form> <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> var stompClient = Stomp.over(new SockJS("/chat")); stompClient.connect({}, function() { stompClient.subscribe("/chat", function(message) { $("#messages").append("<p>" + message.body + "</p>"); }); }); $("#message-form").submit(function(event) { event.preventDefault(); var message = $("#message-input").val(); stompClient.send("/chat", {}, message); $("#message-input").val(""); }); </script> </body> </html> ``` 这个 Demo 中,前端使用了 SockJS 和 STOMP.js 这两个库来实现 WebSocket 的连接和消息发送。后端使用了 Spring BootWebSocket 功能来处理 WebSocket 的连接和消息。前端和后端通过 /chat 这个 URL 进行连接。 这个 Demo 可以在本地启动后,通过访问 http://localhost:8080/ 来进入聊天界面。多个浏览器窗口之间可以进行简单的对话。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值