免费招聘网站有哪些平台微信开店
目录
使用Spring 原生低层 API
1、引入依赖
2、按需编写webSocket的拦截器
3、编写消息处理器类
4、注册 WebSocket 处理器
使用低层 API的总结
大家好,我是jstart千语。今天给大家带来webSocket在使用过程的实战案例,让大家明白webSocket在项目中是如何使用的,其实webSocket的实现方式有好几种,这里给大家介绍三种使用方式。分别是Spring 原生低层 API、 使用 @EnableWebSocketMessageBroker + STOMP 协议、使用JSR 356 标准。这一篇就先只讲Spring 原生低层 API的使用案例。
关于webSocket的原理等基础知识在上一篇有讲到: 【网络协议】WebSocket讲解-CSDN博客
使用Spring 原生低层 API
这种方式的使用步骤就是:
- 引入依赖:spring-boot-starter-websocket
- 按需编写webSocket的拦截器(与springMVC提供的拦截器不同)
- 编写消息处理器类
- 注册 WebSocket 处理器
1、引入依赖
<!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
2、按需编写webSocket的拦截器
拦截器示例:
握手前的方法形参中有一个attributes的map参数。这里就可以往里面塞一些需要进行业务处理的数据了,在后续可以通过session取出来。
@Component public class exampleHandshakeInterceptor implements HandshakeInterceptor {//这里可以注入其他的service类,辅助业务的完成 @Resourceprivate UserService userService;@Resourceprivate PictureService pictureService;@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {//校验权限、、、return false; //false表示不放行、返回true表示放行}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {} }
3、编写消息处理器类
让类继承一个父类TextWebSocketHandler ,其实更加原生的是WebSocketHandler,但我们最终要发的是文本消息嘛,所以就使用TextWebSocketHandler;
然后重写里面的三个方法,分别表示建立连接时、客户端发送消息时、连接关闭时执行的方法。
示例:项目背景为一个共同编辑一张图片的场景,但是同一时间只有一个用户能正在编辑,在同一张图片的编辑室内的其他用户可以同步看到被修改后的图片,当正在编辑的用户退出编辑到编辑室里时,编辑室内的其他用户才可以进入编辑。
可以看到下面的代码成员变量的map都是使用ConcurrentHashMap,这是一个线程安全的map,避免出现并发问题。
userEditing:表示正在编辑的用户和图片,key表示正在被编辑的图片,value表示正在编辑的用户
pictureEditRome:编辑室内的用户,key表示某个图片的编辑室,value表示这个编辑室内的用户的session。(对用户发消息是基于session来发的)
@Component
@Slf4j
public class exampleHandle extends TextWebSocketHandler {@Resourceprivate UserService userService;//正在编辑图片的用户,key:pictureId、value:userIdprivate static final ConcurrentHashMap<Long,Long> userEditing = new ConcurrentHashMap<>();//各个图片的编辑室 key:pictureId、value:当前图片编辑室的用户sessionprivate static final ConcurrentHashMap<Long, Set<WebSocketSession>> pictureEditRome = new ConcurrentHashMap<>();/*** 连接建立时,加入编辑室,并广播给所有用户*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {//具体业务。。。/*** 给用户放到对应的编辑室,* 用户和图片的数据并没有现成的,但可以从上面的拦截器中携带过来* 拦截器通过attributes设置的属性可以通过形参的session获取* 如果拦截器设置了:* attributes.put("user", loginUser);* attributes.put("picture", picture);* 这里就可以通过这样获取:* User user = (User) session.getAttributes().get("user");* Picture picture = (Picture) session.getAttributes().get("picture");* 下面的两个方法同理,不再过多赘述*/super.afterConnectionEstablished(session);}/*** 客户端给服务端发消息时*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {//具体业务。。。super.handleTextMessage(session, message);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {super.afterConnectionClosed(session, status);}
具体的实现代码就不占用大家的阅读时间了,这里只提供一下广播消息(给用户发消息)是如何进行的。
因为给用户发消息的场景比较常用,可以单独抽取出来一个方法:
/*** 广播消息* @param excludeSession 被排除的session,表示不需要广播的用户* @param responseMessage 这是一个自定义的实体,用于封装返回消息,可根据业务的不同自定义* @param picture 可以取出pictureId,表示广播给哪一张图片的编辑室用户* @throws IOException*/public void broadcastPictureToAll(WebSocketSession excludeSession, PictureEditResponseMessage responseMessage, Picture picture) throws IOException {//构造广播消息ObjectMapper objectMapper = new ObjectMapper();SimpleModule module = new SimpleModule();module.addSerializer(Long.class, ToStringSerializer.instance);module.addSerializer(Long.TYPE, ToStringSerializer.instance);objectMapper.registerModule(module); //自定义objectMapper转换规则,解决long类型在前端精度丢失问题String respMessageJson = objectMapper.writeValueAsString(responseMessage);TextMessage textMessage = new TextMessage(respMessageJson);//在该图片编辑室广播消息Set<WebSocketSession> sessions = pictureEditRome.get(picture.getId());for (WebSocketSession session : sessions) {//如果是要排除的session,那就不用对这个session广播消息if (excludeSession != null && excludeSession.equals(session)) {continue;}//如果这个session是开启的才发送if (session.isOpen()) {session.sendMessage(textMessage);}}}
重点讲解:
4、注册 WebSocket 处理器
我们怎么让请求发过来时,执行的是哪一个webSocket处理类呢?以及执行哪一个拦截器呢?
这就要将他们注册了,规定一些路径、排除一些路径。与webMVC注册拦截器相似。
@Component
@EnableWebSocket //启用webSocket的重要注解
public class PictureWebSocketRoute implements WebSocketConfigurer {@Resourceprivate PictureEditHandle pictureEditHandle;@Resourceprivate WsHandshakeInterceptor wsHandshakeInterceptor;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(pictureEditHandle,"/ws/picture/edit")//注册处理类,已经选择开发的路径.addInterceptors(wsHandshakeInterceptor)//注册拦截器.setAllowedOrigins("*");//允许跨域}
}
使用低层 API的总结
完成上述的操作后就大功告成了,讲讲使用这种方法的特点:
特性 | 类要继承 TextWebSocketHandler |
---|---|
协议层级 | 直接处理 WebSocket 原始帧(文本/二进制) |
消息格式 | 需自行解析消息内容(如 JSON、XML) |
连接管理 | 需手动管理连接状态(如在线用户列表、心跳检测) |
消息路由 | 需自行实现消息路由逻辑(如根据消息类型分发) |
广播与点对点 | 需手动实现(如遍历所有 Session 发送消息) |
鉴权与拦截 | 需手动实现(如通过 HandshakeInterceptor 拦截连接) |
集群支持 | 需自行处理多节点消息同步(如通过 Redis 广播) |
适用场景:
- 需要完全自定义 WebSocket 协议(如私有二进制协议)。
- 对性能有极致要求(避免 STOMP 协议解析开销)。
- 项目规模小,消息逻辑简单,无需复杂路由。