You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:07:01