利用SpringBoot实现websocket通信
Spring WebSocket实现实时通信的详细教程
WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。
我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的,必须是客户端主动触发的行为,服务端只是做好接口被动等待请求。而在某些场景下的动作,是需要服务端主动触发的,比如向客户端发送消息、实时通讯、远程控制等。客户端是不知道这些动作几时触发的,假如用HTTP的方式,那么设备端需要不断轮询服务端,这样的方式对服务器压力太大,同时产生很多无效请求,且具有延迟性。于是才采用可以建立双向通讯的长连接协议。通过握手建立连接后,服务端可以实时发送数据与指令到设备端,服务器压力小。
理解java spring boot框架
工厂模式
在学习Spring WebSocket的时候,发现很多教程都是使用工厂模式来创建WebSocketHandler和HandshakeInterceptor,但是不知道工厂模式的具体作用。
所以专门学习了一下
在Spring Boot中,工厂模式主要是通过BeanFactory接口及其实现来体现的。BeanFactory是Spring的核心接口,它是一个高级的工厂,能够维护不同Bean的定义,并负责Bean的创建和管理。ApplicationContext是BeanFactory的一个子接口,提供了更多与Spring整合的功能,比如事件传递、消息解析等。
在Spring Boot中,工厂模式可以通过几种方式实现:
通过FactoryBean接口实现:
FactoryBean是一个专门的工厂接口,用于生成其他Bean的实例。开发者可以自定义FactoryBean,通过实现getObject()方法来返回Bean的实例。这种方式适用于创建复杂的Bean,或者Bean的创建需要进行复杂的初始化过程。
通过@Bean注解配置方法实现:
在Spring配置类中,可以使用@Bean注解标注一个方法,这个方法返回一个对象的实例。Spring容器调用这个方法,并将返回的对象注册为一个Bean。这允许开发者编程方式控制Bean的创建逻辑。
通过@Configuration类的方法实现:
类似于@Bean注解,@Configuration注解的类中定义的方法可以返回Bean的实例。这些方法可以依赖注入其他Bean,实现更复杂的配置逻辑。
BeanFactory
方法有很多,比如 获取别名呀,类型呀,是否是单例,原型等
通过 getBean 去获取对象
主要作用
根据 BeanDefinition 生成相应的 Bean 对象
FactoryBean
FactoryBean是一个专门的工厂接口,用于生成其他Bean的实例。开发者可以自定义FactoryBean,通过实现getObject()方法来返回Bean的实例。这种方式适用于创建复杂的Bean,或者Bean的创建需要进行复杂的初始化过程。
通过 getObject 方法来返回一个对象
获取对象时:
如果 beanName 没有加 & 号,则获取的是泛型T 的对象。
如果添加了 & 号,获取的是实现了 FactoryBean 接口本身的对象,如 EhCacheFactoryBean
而正因为它的小巧,它也被广泛的应用在Spring内部,以及Spring与第三方框架或组件的整合过程中。
BeanFactory 和 FactoryBean 的区别是什么?
BeanFactory 是一个大工厂,是IOC容器的根基,有繁琐的 bean 生命周期处理过程,可以生成出各种各样的 Bean
FactoryBean 是一个小工厂,它自己也是一个 Bean ,但是可以生成其他 Bean
引入依赖
WebSocketConfigurer实例
WebSocketConfig.java:
1 | package org.example.websocket.config; |
其中:
1 |
|
@SuppressWarnings(“ALL”):抑制所有编译器警告。这通常不是最佳实践,但在这里可能是为了避免某些类型转换或未使用的警告。
@Configuration:这是一个 Spring 的注解,表示该类是一个配置类,Spring 会扫描它并处理其中的 @Bean 定义。
@Bean
表示这个方法会返回一个 Spring Bean,Spring 容器会管理这个 Bean 并将其注入到需要的地方。
这里返回的是一个 WebSocketConfigurer 类型的对象。
之前没接触过@Bean注解,所以不知道这个注解的作用。这里详细了解一下:
@Bean 的含义
在 Spring 框架中,@Bean 是一个方法级别的注解,通常用在 @Configuration 标注的配置类中。它的作用是告诉 Spring 容器:“这个方法会返回一个对象,我希望你(Spring)把这个对象注册为一个 Bean,并由你来管理它。”
Bean 是什么?
在 Spring 中,Bean 是一个由 Spring 容器管理的对象。Spring 容器负责创建这些对象、配置它们、注入依赖关系,并在需要时销毁它们。
简单来说,Bean 就是 Spring 帮你管理的“组件”或“实例”
@Bean 的作用:
当你在一个方法上加上 @Bean 注解时,Spring 会在应用启动时调用这个方法,并将方法返回的对象注册为一个 Bean。
这个 Bean 会被存储在 Spring 的应用上下文(Application Context)中,之后可以通过依赖注入(例如 @Autowired)在其他地方使用。
在代码中:
@Bean 的作用:
这个方法 webSocketConfigurer 返回一个 WebSocketConfigurer 类型的对象。
@Bean 告诉 Spring:“请调用这个方法,把返回的 WebSocketConfigurer 对象注册为一个 Bean,并由你(Spring)来管理它。”
接着学习函数的具体内容:
1 |
|
其中
1 | ChatHandler chatHandler, |
@Override 是 Java 中的一个注解(Annotation),用于标记一个方法是 重写(Override) 了父类或接口中定义的方法。我来详细解释它的作用、用法和意义。
@Autowired 的作用:
这是 Spring 提供的一个注解,用于实现 依赖注入(Dependency Injection, DI)。
依赖注入是 Spring 核心特性之一,它允许开发者不必手动创建对象,而是让 Spring 容器自动将需要的对象(Bean)注入到指定位置。
在这里,@Autowired 标注在方法参数上,告诉 Spring:“请自动为我提供 ChatHandler 和 ChatHandshakeInterceptor 的实例。”
注入的时机:
当 Spring 调用 webSocketConfigurer 方法来创建 WebSocketConfigurer Bean 时,会先解析方法参数上的 @Autowired。
Spring 会从它的容器(Application Context)中查找是否有符合类型的 Bean(即 ChatHandler 和 ChatHandshakeInterceptor 的实例),然后将它们注入到方法参数中。
工作原理:
Spring 容器在启动时会扫描所有标注了 @Component(或其他衍生注解,如 @Service、@Repository 等)的类,或者通过 @Bean 定义的 Bean。
如果 ChatHandler 和 ChatHandshakeInterceptor 是 Spring 管理的 Bean(例如,它们可能在其他地方被定义为 @Component 或 @Bean),Spring 就会找到它们并注入。
以及
return new WebSocketConfigurer() {…} 的用法:是 Java 中创建 匿名类(Anonymous Inner Class) 的一种方式。
因为在 Java 中,接口(例如 WebSocketConfigurer)不能直接实例化,必须通过实现它的类来创建对象。
这里没有显式定义一个独立的类(比如 class MyConfigurer implements WebSocketConfigurer),而是直接在代码中通过匿名类实现接口并实例化。
这种方式适用于只需要一次性使用的简单实现,避免了单独定义一个类的麻烦。
registry.addHandler((WebSocketHandler) chatHandler, “/chat”):
addHandler 是 WebSocketHandlerRegistry 的方法,用于注册一个 WebSocket 处理器。
参数:
(WebSocketHandler) chatHandler:将 chatHandler 转换为 WebSocketHandler 类型,表示它负责处理 WebSocket 请求。
“/chat”:定义 WebSocket 的端点路径,例如客户端可以通过 ws://hostname/chat 连接。
作用:告诉 Spring,当客户端连接到 /chat 时,使用 chatHandler 处理连接和消息。
.addInterceptors((HandshakeInterceptor) chatInterceptor):
addInterceptors 是 addHandler 返回的配置对象(WebSocketHandlerRegistration)的方法,用于添加握手拦截器。
参数:
(HandshakeInterceptor) chatInterceptor:将 chatInterceptor 转换为 HandshakeInterceptor 类型,表示它会在握手阶段拦截请求。
作用:在连接建立的握手阶段,执行 chatInterceptor 的逻辑(例如验证身份)。
代码执行流程
Spring 调用方法:
Spring 容器在启动时发现 @Bean 注解,调用 webSocketConfigurer 方法。
通过 @Autowired,注入 chatHandler 和 chatInterceptor。
创建匿名类实例:
new WebSocketConfigurer() {…} 创建一个实现了 WebSocketConfigurer 的匿名类实例。
返回给 Spring:
这个实例被返回并注册为 Bean。
Spring 使用配置:
Spring 的 WebSocket 基础设施调用这个实例的 registerWebSocketHandlers 方法,将 chatHandler 和 chatInterceptor 注册到 /chat 端点。
运行时行为:
当客户端连接到 /chat 时,先触发 chatInterceptor 的握手逻辑,然后由 chatHandler 处理连接。
处理WebSocket连接
和处理普通HTTP请求不同,没法用一个方法处理一个URL。Spring提供了TextWebSocketHandler和BinaryWebSocketHandler分别处理文本消息和二进制消息,这里我们选择文本消息作为聊天室的协议,因此,ChatHandler需要继承自TextWebSocketHandler
1 |
|
当浏览器请求一个WebSocket连接后,如果成功建立连接,Spring会自动调用afterConnectionEstablished()方法,任何原因导致WebSocket连接中断时,Spring会自动调用afterConnectionClosed方法,因此,覆写这两个方法即可处理连接成功和结束后的业务逻辑:
ChatHandler.java
1 | package org.example.websocket.hander; |
@Component:这是 Spring 的注解,告诉 Spring 框架这是一个需要管理的组件(bean)。Spring 会自动创建这个类的实例并管理它。
public class ChatHandler:定义了一个名为 ChatHandler 的公开类。
extends TextWebSocketHandler:这个类继承了 Spring 的 TextWebSocketHandler,这是一个专门用来处理基于文本的 WebSocket 消息的抽象类。它提供了处理 WebSocket 连接的方法。
简单理解:这个类是一个 Spring 管理的组件,专门用来处理 WebSocket 连接(比如实时聊天功能)。
- 成员变量Map<String, WebSocketSession>:定义了一个映射(Map),键是字符串类型(String),值是 WebSocketSession 对象。
1
private Map<String, WebSocketSession> clients = new ConcurrentHashMap<>();
Map 就像一个字典,键是唯一的,可以通过键找到对应的值。
WebSocketSession 是 Spring 提供的一个类,表示一个 WebSocket 会话(客户端和服务端之间的连接)。
new ConcurrentHashMap<>():创建了一个线程安全的 Map 实现。
ConcurrentHashMap 是 Java 提供的一种并发集合,适合多线程环境(比如服务器中多个客户端同时连接)。
clients:这个变量用来存储所有当前连接的客户端会话,键是会话 ID,值是会话对象。
简单理解:clients 是一个存储所有连接客户端的“名单表”,可以用会话 ID 找到对应的会话。@Override:表示这个方法重写了父类(TextWebSocketHandler)的方法。1
2
3
4
5
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
clients.put(session.getId(), session);
session.getAttributes().put("name", "Guest1");
}
public void afterConnectionEstablished(WebSocketSession session):
这是一个生命周期方法,当一个新的 WebSocket 连接建立时自动被调用。
参数 session 是新建立的会话对象。
throws Exception:表示这个方法可能会抛出异常,需要调用者处理。
clients.put(session.getId(), session);:
session.getId():获取这个会话的唯一 ID(字符串类型)。
clients.put(key, value):将键值对放入 clients 这个 Map 中。
这里是将新会话的 ID 和会话对象存入 clients,方便后续查找。
session.getAttributes().put("name", "Guest1");:
session.getAttributes():获取会话的属性集合(类似 Map),可以存储会话相关的自定义数据。
.put(“name”, “Guest1”):在这个会话的属性中添加一个键值对,键是 “name”,值是”Guest1”。
这相当于给这个客户端起了一个默认名字 “Guest1”。
简单理解:当有新客户端连上时,把它的会话存到 clients 名单里,并且给它一个默认名字 “Guest1”。
1 |
|
@Override:同样是重写父类的方法。
public void afterConnectionClosed(WebSocketSession session, CloseStatus status):
这是一个生命周期方法,当 WebSocket 连接关闭时自动被调用。
参数 session 是关闭的会话对象。
参数 status 是关闭状态(包含关闭原因和代码),这里没用到。
throws Exception:可能会抛出异常。
clients.remove(session.getId());:
session.getId():获取关闭会话的 ID。
clients.remove(key):从 clients 这个 Map 中删除指定键对应的条目。
这里是将关闭的会话从 clients 名单中移除。
简单理解:当客户端断开连接时,把它从 clients 名单里删掉。
理解@Bean和@Component
- 什么是 Bean?
在 Spring 中,Bean 是一个被 Spring 容器(Spring IoC 容器)管理的对象。简单来说:
Bean 是一个普通的 Java 对象(比如你的 ChatHandler 类的实例)。
但它不是由你手动用 new 创建的,而是由 Spring 框架自动创建、管理和销毁的。
Bean 的生命周期(创建、初始化、使用、销毁)都由 Spring 容器控制。
Bean 通常会配置一些属性(比如依赖注入),Spring 会帮你把这些依赖关系组装好。
类比:想象 Spring 是一个大厨,Bean 就是一道菜。大厨会按照菜谱(配置)来准备这道菜,你只需要告诉他做什么菜,不用自己动手。
- 什么是 @Component?
@Component 是 Spring 提供的一个注解,作用是标记一个类,表示这个类应该被 Spring 容器识别并管理为一个 Bean。换句话说:
当你给一个类加了 @Component,Spring 会在启动时扫描这个类,自动创建它的实例,并将其注册为一个 Bean。
你不需要显式地写配置文件(比如 XML)来定义这个 Bean,@Component 是一种“自动配置”的方式。
类比:@Component 就像是你告诉大厨:“这个是我要的菜,请你帮我做出来。” 大厨(Spring)看到这个标记后,就会自动把这道菜(Bean)准备好。
- @Component 和 Bean 的区别
虽然 @Component 和 Bean 关系密切,但它们的概念和使用场景有以下区别:
方面 Bean @Component
定义 Bean 是 Spring 容器管理的对象实例。 @Component 是一个注解,用来标记类,让 Spring 将其变为 Bean。
范围 Bean 是一个更广义的概念,任何被 Spring 管理的对象都是 Bean。 @Component 是创建 Bean 的一种具体方式。
创建方式 可以通过多种方式定义 Bean:
- 注解(如 @Component)
- XML 配置
- Java 配置(@Bean 注解) 只能通过在类上加 @Component 注解,由 Spring 自动扫描生成 Bean。
灵活性 更灵活,可以通过配置文件或方法手动指定 Bean 的创建逻辑。 相对简单,适合标准的类,Spring 自动管理。
使用场景 Bean 是结果,描述的是对象本身。 @Component 是手段,用来声明类。
4. 代码中的例子
1 |
|
@Component:告诉 Spring,“ChatHandler 这个类需要你来管理,请创建它的实例”。
Bean:Spring 扫描到 @Component 后,会创建一个 ChatHandler 的实例,这个实例就是 Bean,被 Spring 容器管理。
Spring 的工作流程:
启动时扫描所有带有 @Component 的类。
为 ChatHandler 创建一个实例(Bean)。
将这个 Bean 放入容器中,供其他地方使用(比如依赖注入)。
处理握手拦截器
ChatHandshakeInterceptor.java:
1 | package org.example.websocket.hander; |
这是一个自定义的类 ChatHandshakeInterceptor,继承了 Spring 的 HttpSessionHandshakeInterceptor。
它通过构造函数调用父类的构造方法,传递了一个包含 “user” 的列表
什么是握手拦截器(Handshake Interceptor)?
在 WebSocket 连接中,客户端和服务端建立连接时会进行一次“握手”(Handshake),类似于 TCP 的三次握手,但基于 HTTP 协议。握手拦截器是 Spring 提供的一种机制,允许你在握手过程中插入自定义逻辑,比如:
检查请求头、参数。
从 HTTP 会话(HttpSession)中提取数据。
修改 WebSocket 会话(WebSocketSession)的属性。
简单理解:握手拦截器就像一个“门卫”,在客户端敲门(建立 WebSocket 连接)时检查身份或传递信息。
前端部署
1 | <!DOCTYPE html> |
部署成功
可以看到浏览器a发送的消息会广播到浏览器b
总结
感觉学到了一些关于java spring boot的框架的知识,以及关于websocket的基础,收获很大,也算是着手用spring boot来写一个项目了