Spring环境下Jakarta WebSocket Handler依赖注入失效问题求助
问题描述
基于Jakarta WebSocket实现了点对点通信的客户端与服务端,但遇到两个核心问题:
MessageHandler中通过@Autowired注入的ApplicationEventPublisher始终为null- WebSocket容器要求
MessageHandler必须提供无参构造,这和依赖注入的需求冲突
客户端与服务端代码
package network; import jakarta.websocket.ContainerProvider; import jakarta.websocket.Session; import jakarta.websocket.WebSocketContainer; import java.net.URI; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.web.socket.client.WebSocketClient; import org.springframework.web.socket.client.standard.StandardWebSocketClient; @Service public class WebsocketNetwork { private final MessageHandler messageHandler; @Bean public WebSocketClient webSocketClient() { return new StandardWebSocketClient(); } @Autowired public WebsocketNetwork(MessageHandler messageHandler) { this.messageHandler = messageHandler; } @EventListener(value = ApplicationReadyEvent.class) public void establishConnection() throws Exception { WebSocketContainer container = ContainerProvider.getWebSocketContainer(); URI address = new URI("ws://localhost:15000/websocket"); Session session = container.connectToServer(this.messageHandler, address); } }
Handler代码
package network; import jakarta.websocket.ClientEndpoint; import jakarta.websocket.OnClose; import jakarta.websocket.OnError; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import jakarta.websocket.server.ServerEndpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; @Component @ServerEndpoint(value = "/websocket", decoders = MessageDecoder.class, encoders = MessageEncoder.class) @ClientEndpoint(decoders = MessageDecoder.class, encoders = MessageEncoder.class) @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class MessageHandler { // 无参构造缺失时的报错信息 /* Caused by: java.lang.NoSuchMethodException: network.MessageHandler.<init>() at java.base/java.lang.Class.getConstructor0(Class.java:3641) ~[na:na] at java.base/java.lang.Class.getConstructor(Class.java:2324) ~[na:na] at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:135) ~[tomcat-embed-core-10.1.7.jar:10.1.7] at org.apache.tomcat.websocket.WsSession.<init>(WsSession.java:275) ~[tomcat-embed-websocket-10.1.7.jar:10.1.7] */ @Autowired private ApplicationEventPublisher eventPublisher; // 此处始终为Null @OnClose public void onClose(Session session) { System.out.println("Disconnected from server: " + session.getId()); } @OnOpen public void onOpen(Session session) { System.out.println("Connected to server: " + session.getId()); } @OnMessage public void receive(NetworkMessage message) { // 处理收到的消息 } @OnError public void onError(Session session, Throwable throwable) { System.err.println(throwable.getMessage()); } }
问题根源:WebSocket容器通过newInstance()创建MessageHandler实例,完全绕过Spring的依赖注入机制,导致Spring管理的Bean无法注入;同时容器要求类必须有无参构造,而@RequiredArgsConstructor生成的是带参构造方法,引发冲突。
解决方案
1. 解决无参构造与依赖注入的冲突
移除@RequiredArgsConstructor注解,手动添加无参构造方法,保留@Autowired字段注入的写法。注意:仅这一步无法解决注入为null的问题,因为WebSocket容器创建的实例不在Spring容器管理范围内。
2. 让WebSocket容器使用Spring管理的Bean实例
方案一:服务端专用 - 使用ServerEndpointExporter
通过Spring提供的ServerEndpointExporter让Spring接管WebSocket端点的实例创建:
- 添加配置类注册
ServerEndpointExporter:
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
- 修改
MessageHandler:确保无参构造存在,@Autowired字段能被Spring正常注入。此时Spring会负责创建MessageHandler实例并管理依赖,WebSocket容器会直接使用Spring创建的实例,而非自行new。
方案二:客户端+服务端通用 - 从Spring上下文手动获取Bean
创建Spring上下文工具类,在需要时手动获取依赖:
- 实现上下文持有类:
@Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { applicationContext = context; } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } }
- 修改
MessageHandler:
@Component @ServerEndpoint(value = "/websocket", decoders = MessageDecoder.class, encoders = MessageEncoder.class) @ClientEndpoint(decoders = MessageDecoder.class, encoders = MessageEncoder.class) public class MessageHandler { private ApplicationEventPublisher eventPublisher; // 手动添加无参构造,满足WebSocket容器要求 public MessageHandler() {} @OnOpen public void onOpen(Session session) { // 在连接建立时从Spring上下文获取依赖 eventPublisher = SpringContextHolder.getBean(ApplicationEventPublisher.class); System.out.println("Connected to server: " + session.getId()); } // 其他方法保持不变 }
这种方式既满足了WebSocket容器对无参构造的要求,又能获取到Spring管理的依赖。
方案三:客户端专用 - 传入Spring管理的实例
在WebsocketNetwork的establishConnection方法中,已经注入了Spring管理的messageHandler实例,直接传入connectToServer即可(这部分代码已实现),但服务端部分仍需用方案一或方案二处理。
3. 验证依赖注入有效性
在使用eventPublisher前添加非空判断,确保依赖已正确获取:
@OnMessage public void receive(NetworkMessage message) { if (eventPublisher != null) { // 执行事件发布逻辑 eventPublisher.publishEvent(new MessageReceivedEvent(message)); } }
内容的提问来源于stack exchange,提问作者Vishrant
相关产品推荐
相关产品推荐

