将JSR-356 WebSocket @ServerEndpoint与Spring 3 beans集成

8

我正在使用不带完整新JSR-356 WebSockets支持的Spring 3.2.5。

我想在我的@ServerEndpoint WebSocket服务器中引用单例bean,该bean由servlet容器本身实例化,而不是在Spring上下文中实例化。

有什么干净的方法可以做到这一点吗?

我目前的解决方案:我使用了一个具有静态字段实例的@Service单例bean:

@Service
public class WebSocketSupportBean {
    private volatile static WebSocketSupportBean instance = null;

    public static WebSocketSupportBean getInstance() {
        return instance;
    }

    public WebSocketSupportBean() {
        instance = this;
    }

只需通过静态方法在@ServerEndpoint中获取它,并在返回null时断开用户连接(如果bean在服务器启动期间尚未创建但用户已连接):

5个回答

7

您可以使用Spring框架3.x设置Websockets。

我开发了一个小的概念验证应用程序,以演示如何基于Rossen Stoyanchev发布的SpringConfiguration(发布于spring-core 4.0)进行操作。

该应用程序通过URI /wstest 设置了一个websocket服务器端点,并使用@Autowired的Spring bean来选择一个问候词并回复WebSocket消息。

WebSocket连接由运行在支持WebSockets的浏览器中的html页面(index.html)发起和发送消息。

端点注册是通过ServletContextListener在上下文初始化时完成的,当端点被实例化时,它将与Spring进行连接:

@WebListener
public class MyApplication implements ServletContextListener {

    private final static String SERVER_CONTAINER_ATTRIBUTE = "javax.websocket.server.ServerContainer";

    @Override
    public void contextInitialized(ServletContextEvent sce) {

        ServletContext container = sce.getServletContext();

        final ServerContainer serverContainer = (ServerContainer) container.getAttribute(SERVER_CONTAINER_ATTRIBUTE);
        try {
            serverContainer.addEndpoint(new MyEndpointConfig(MyEndpoint.class, "/wstest"));
        } catch (DeploymentException e) {
            e.printStackTrace();
        }
    }
}

终端点是:

@Component
public class MyEndpoint extends Endpoint {

    @Autowired
    MyService myService;

    @Override
    public void onOpen(Session session, EndpointConfig config) {

        session.addMessageHandler(new MyMessageHandler(session));
    }


    class MyMessageHandler implements MessageHandler.Whole<String> {

        final Session session;

        public MyMessageHandler(Session session) {
            this.session = session;
        }

        @Override
        public void onMessage(String message) {
            try {
                String greeting = myService.getGreeting();
                session.getBasicRemote().sendText(greeting + ", got your message (" + message + "). Thanks ! (session: " + session.getId() + ")");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

请查看我的Github页面,获取完整的源代码和可直接运行的示例。


1
很好的例子!作为一个好的实践,我也会在try catch中包含(ServerContainer)的转换。我(懒惰地)复制了你的片段,在Tomcat上部署时由于依赖问题出现了完全静默的类转换异常。花了我几个小时才找到问题。 - DiegoSahagun
你能帮我在 GitHub 上检查一下我的问题吗?这段代码可能存在 bug。 - Xiaokun

2
你需要在Spring的配置中添加bean定义。
我发现集成JSR 356 Websocket @ServerEndpoint的解决方案是关闭Spring中Servlet容器对Websocket端点的扫描,这可以通过在Spring配置中注册@Bean来实现。这样 Spring 就不会覆盖普通的JSR 356 Websocket,而是使用 Spring STOMP Websocket,它是Websocket的一部分。
@ServerEndpoint(value="/chatMessage")
public class ChatEndpoint{
// Normal websocket body goes here.
}

将Bean添加到您的配置中,方法如下:
@Configuration
public class WebsocketConfig{
  @Bean
  public ChatEndpoint chatEndpoint(){
     return new ChatEndpoint();
  }
  // main one is ServerEndpointExporter which prevents Servlet container's scan for WebSocket
  @Bean
  public ServerEndpointExporter endpointExporter(){
     return new ServerEndpointExporter();
  }
}

这一切都为您完成了。但是,您需要从@ServerEndpoint中删除configurator = SpringConfigurator.class。 我使用的是Spring Websocket 4.0.0版本,它运行得很好。 您还可以查看此链接:Link
如果您没有问题,请继续查看Link以获取更多概念。
请注意,通常应将websocket配置与spring的主配置分开处理。

1

试一下这个,它对我有效。

@Component
@ServerEndpoint(value = "/instantMessageServer",configurator = SpringConfigurator.class)
public class InstantMessageServer{
private static IChatService chatService;
    @Autowired
    public InstantMessageServer(IChatService chatService){
        this.chatService = chatService;
    }
    public InstantMessageServer(){}
}

我在https://spring.io/blog/2013/05/23/spring-framework-4-0-m1-websocket-support上找到了这个解决方案,但还有一个小问题,使用@ServerEndpoint注释的类无法通过SpringConfigurator获取httpsession,因为它没有覆盖modifyhandler方法。也许我们可以创建一个单独的Configurator扩展SpringConfigurator并覆盖该方法来解决这个问题。我认为最好使用spring-websocket和消息API构建实时Web应用程序。


public class ModifiedServerEndpointConfigurator extends SpringConfigurator{
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
        super.modifyHandshake(sec, request, response);
    }
}

我不知道未来是否会有更多的错误。 - 何德福

1
你可以让你的 @ServerEndpoint 对象扩展 SpringBeanAutowiringSupport。然后通过这种方式使它意识到在基于Spring的Web应用程序中构造的bean:
  @PostConstruct
    public void init() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
    }

这样,@Autowired注释将正确工作:
@Autowired MyService myService;

1

尝试

@ServerEndpoint(value = "/ws", configurator = SpringConfigurator.class)

并添加Maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
</dependency>

spring-websocket 是 4.0 版本之后的,不是 spring 3。 - CodeMonkey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接