今天尝试了通过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 {
@Bean
public 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<>();
/**
* 链接成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathVariable String userId) {
this.session = session;
this.userId = userId;
webSockets.add(this);
hashMap.put(userId, session);
log.debug("和用户{}创建连接。", this.userId);
}
/**
* 链接关闭调用的方法
*/
@OnClose
public void onClose() {
webSockets.remove(this);
hashMap.remove(this.userId);
log.debug("用户{}关闭了websocket连接。", this.userId);
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message) {
log.debug("收到用户{}发送的消息:", this.userId);
}
/**
* 发送错误时的处理
* @param error Throwable
*/
@OnError
public 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.WebSocket
at 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 annotation
at 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 omitted
Disconnected 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<>();
/**
* 链接成功调用的方法
*/
@OnOpen
public 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);
}
/**
* 链接关闭调用的方法
*/
@OnClose
public void onClose() {
webSockets.remove(this);
hashMap.remove(this.userId);
log.debug("用户{}关闭了websocket连接。", this.userId);
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message) {
log.debug("收到用户{}发送的消息:", this.userId);
}
/**
* 发送错误时的处理
* @param error Throwable
*/
@OnError
public 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);
}
}
}
}
修改之后能正常启动了。