今天尝试了通过springboot整合websocket来初步学习使用websocket,然后发现启动的时候报错了,发这篇文章分享一下。
springboot整合websocket的步骤很简单:
第一步:创建一个springboot项目,在这里命名为websocket
在IntelliJ IDEA里创建一个springboot项目,创建过程中只需要修改项目名和选择java的版本为8, 然后点击Next等待片刻就创建好了。
第二步:pom.xml中添加websocket的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
不需要指定版本,默认和springboot版本一致,修改springboot版本为2.5.9,完整的配置文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.9</version><relativePath/></parent><groupId>com.example</groupId><artifactId>websocket</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>1.8</java.version><lombok.version>1.18.22</lombok.version><fastjson.version>2.0.8</fastjson.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
第三步:添加websocket的配置类
在项目的根目录下创建config包,在config包下创建一个配置类WebSocketConfig
package com.example.websocket.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author heyunlin* @version 1.0*/
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
第四步:使用websocket
在项目根目录下创建一个endpoint包,在endpoint包下创建一个WebSocket类,在类上添加@Component和@ServerEndpoint注解,并通过@ServerEndpoint的value属性指定请求路径,用法类似于@RequestMapping。
package com.example.websocket.endpoint;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;/*** websocket访问路径:ws://localhost:8082/websocket/用户ID* @author heyunlin* @version 1.0*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {private Session session;private String userId;private final CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();private final ConcurrentHashMap<String, Session> hashMap = new ConcurrentHashMap<>();/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathVariable String userId) {this.session = session;this.userId = userId;webSockets.add(this);hashMap.put(userId, session);log.debug("和用户{}创建连接。", this.userId);}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {webSockets.remove(this);hashMap.remove(this.userId);log.debug("用户{}关闭了websocket连接。", this.userId);}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message) {log.debug("收到用户{}发送的消息:", this.userId);}/*** 发送错误时的处理* @param error Throwable*/@OnErrorpublic void onError(Throwable error) {error.printStackTrace();}/*** 发送单条消息* @param userId 用户ID* @param message 消息内容*/public void sendMessage(String userId, String message) {if (hashMap.containsKey(userId)) {Session session = hashMap.get(userId);if (session.isOpen()) {session.getAsyncRemote().sendText(message);}}}}
第五步:启动项目
然后启动报错了ovo
java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.example.websocket.endpoint.WebSocketat org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:159) ~[spring-websocket-5.3.15.jar:5.3.15]at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoints(ServerEndpointExporter.java:134) ~[spring-websocket-5.3.15.jar:5.3.15]at org.springframework.web.socket.server.standard.ServerEndpointExporter.afterSingletonsInstantiated(ServerEndpointExporter.java:112) ~[spring-websocket-5.3.15.jar:5.3.15]at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:972) ~[spring-beans-5.3.15.jar:5.3.15]at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.15.jar:5.3.15]at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.15.jar:5.3.15]at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.9.jar:2.5.9]at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767) [spring-boot-2.5.9.jar:2.5.9]at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:447) [spring-boot-2.5.9.jar:2.5.9]at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) [spring-boot-2.5.9.jar:2.5.9]at org.springframework.boot.SpringApplication.run(SpringApplication.java:1356) [spring-boot-2.5.9.jar:2.5.9]at org.springframework.boot.SpringApplication.run(SpringApplication.java:1345) [spring-boot-2.5.9.jar:2.5.9]at com.example.websocket.WebsocketApplication.main(WebsocketApplication.java:10) [classes/:na]
Caused by: javax.websocket.DeploymentException: A parameter of type [class java.lang.String] was found on method[onOpen] of class [java.lang.reflect.Method] that did not have a @PathParam annotationat org.apache.tomcat.websocket.pojo.PojoMethodMapping.getPathParams(PojoMethodMapping.java:347) ~[tomcat-embed-websocket-9.0.56.jar:9.0.56]at org.apache.tomcat.websocket.pojo.PojoMethodMapping.<init>(PojoMethodMapping.java:221) ~[tomcat-embed-websocket-9.0.56.jar:9.0.56]at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:155) ~[tomcat-embed-websocket-9.0.56.jar:9.0.56]at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:279) ~[tomcat-embed-websocket-9.0.56.jar:9.0.56]at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:229) ~[tomcat-embed-websocket-9.0.56.jar:9.0.56]at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:156) ~[spring-websocket-5.3.15.jar:5.3.15]... 12 common frames omittedDisconnected from the target VM, address: '127.0.0.1:53445', transport: 'socket'Process finished with exit code 1
重点看这行
Caused by: javax.websocket.DeploymentException: A parameter of type [class java.lang.String] was found on method[onOpen] of class [java.lang.reflect.Method] that did not have a @PathParam annotation
意思是onOpen()方法上的一个String类型的参数上没有用@PathParam注解,检查了一下,确实没有用这个注解,用的是@PathVariable,这两个注解的作用是类似的,都是获取rest风格请求的参数。修改一下onOpen()方法
package com.example.websocket.endpoint;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;/*** websocket访问路径:ws://localhost:8082/websocket/用户ID* @author heyunlin* @version 1.0*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {private Session session;private String userId;private final CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();private final ConcurrentHashMap<String, Session> hashMap = new ConcurrentHashMap<>();/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {this.session = session;this.userId = userId;webSockets.add(this);hashMap.put(userId, session);log.debug("和用户{}创建连接。", this.userId);}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {webSockets.remove(this);hashMap.remove(this.userId);log.debug("用户{}关闭了websocket连接。", this.userId);}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message) {log.debug("收到用户{}发送的消息:", this.userId);}/*** 发送错误时的处理* @param error Throwable*/@OnErrorpublic void onError(Throwable error) {error.printStackTrace();}/*** 发送单条消息* @param userId 用户ID* @param message 消息内容*/public void sendMessage(String userId, String message) {if (hashMap.containsKey(userId)) {Session session = hashMap.get(userId);if (session.isOpen()) {session.getAsyncRemote().sendText(message);}}}}
修改之后能正常启动了。