目录
一、JSR 356方式:简单示例
1、引入依赖
2、注册端点扫描器
3、编写通过注解处理生命周期和消息
4、细节解读
5、总结
二、聊天室案例
方案流程
1、引入依赖
2、注册端点扫描器
3、编写一个配置类,读取httpsession
4、编写通过注解处理生命周期和消息,并且读取到配置类
5、读取配置文件
6、OnOpen方法,获取httpsession
7、OnMessage方法
8、OnClose方法
大家好,我是jstart千语。在上一篇已经讲了Spring原生底层API的实现方式,这一篇我们讲解JSR 356 标准的实现方式。
Spring原生底层API的使用方式:【webSocket协议】进阶实战案例(Spring 原生低层 API)-CSDN博客
一、JSR 356方式:简单示例
实现流程:
- 引入依赖
- 注册端点扫描器
- 编写通过注解处理生命周期和消息
1、引入依赖
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、注册端点扫描器
@Configuration
public class Jsr356Config {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); // 扫描 @ServerEndpoint 注解类
}
}
3、编写通过注解处理生命周期和消息
@Component //便于后续注入spring容器的bean
@ServerEndpoint("/jsr-ws") // 连接路径
public class Jsr356Endpoint {
//注意要使用静态变量
private static Map<Session, String> sessions = new ConcurrentHashMap<>();
private static UserService userService; // 静态变量解决依赖注入问题
// 通过 setter 注入 Spring Bean
@Autowired
public void setUserService(UserService userService) {
Jsr356Endpoint.userService = userService;
}
@OnOpen
public void onOpen(Session session) {
sessions.put(session, "User-" + session.getId());
}
@OnMessage
public void onMessage(Session session, String message) {
userService.logMessage(message); // 使用注入的 Bean
sessions.forEach((s, username) -> {
s.getAsyncRemote().sendText("收到消息: " + message);
});
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
}
4、细节解读
- @ServerEndpoint 注解的类由 WebSocket 容器(如 Tomcat、Jetty)直接实例化,而不是通过 Spring 容器创建。因此,Spring 的依赖注入(如 @Autowired)在普通成员变量上无法生效。
- 由于每个 WebSocket 连接都会创建一个新的 @ServerEndpoint 实例,若想在这些实例之间共享同一个 Spring Bean(如 UserService),必须通过 静态变量 持有该 Bean 的引用。
实现机制:
1、静态 set 方法:
Spring 在启动时会扫描所有被 @Component 注解的类,并处理其中的 @Autowired 注解。通过在静态 set 方法上添加 @Autowired,Spring 会调用该方法将 Bean 注入到静态变量中。
2、注入时机:
Spring 在初始化 Jsr356Endpoint 类时,发现该静态 set 方法,并将 UserService Bean 赋值给静态变量。这样所有 @ServerEndpoint 实例都能共享同一个 Bean。
5、总结
- 静态 set 方法 + @Autowired 是为了绕过 @ServerEndpoint 类无法由 Spring 直接管理的问题,强制注入 Spring Bean。
- 本质是一种变通方案,并非 Spring 的标准用法,需谨慎处理线程安全和初始化顺序问题。
- 优先使用 STOMP,减少对底层 WebSocket API 的直接操作。
二、聊天室案例
方案流程
- 引入依赖
- 注册端点扫描器
- 编写一个配置类,读取httpsession
- 编写通过注解处理生命周期和消息,并且读取到配置类
- 新用户连接到来,在open方法广播消息给聊天室其他用户
- 用户给另一个用户发消息时,走OnMessage方法,向指定用户发送消息
- 用户断开连接,广播消息,通知该用户下线
1、引入依赖
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、注册端点扫描器
3、编写一个配置类,读取httpsession
4、编写通过注解处理生命周期和消息,并且读取到配置类
这里就会在websocket通信前完成,可以从request中获取session,并且在重写方法的形参中的sec参数间接传入session,这样在后续的OnOpen、OnMessage、OnClose方法中使用到httpsession啦。
5、读取配置文件
直接在消息处理类的上面@ServerEndpoint注解里面添加一个属性,并指定要读取的配置文件即可
6、OnOpen方法,获取httpsession
可以在连接建立时,也就是OnOpen方法中,就将httpsession获取出来,然后赋值给一个类的成员变量,这样接下来的其他方法也可以共同读取了,不用反复获取httpsession。
(1)首先我们明确httpsession里面存的是什么
(2)从OnOpen的形参中获取配置文件中已经放入websocket的session的值:
(3)OnOpen完整方法(广播消息)