如何在Spring中配置Websocket

来源:互联网 发布:java截最后一次出现 编辑:程序博客网 时间:2024/06/16 09:41

Websocket是HTML5的一项新技术,可以让服务端和客户端进行实时的通信,主要的使用场景有: 实时的聊天系统,对实时性要求比较高的游戏,或者金融行业对股票市场数据的及时获取等。在Spring3的时候就已经有了对Websocket的支持,不过需要一些高版本的web容器来运行,比如Tomcat7.0.47+,Jetty9等。

在Spring的官网上有关于Websocket的示例工程,https://spring.io/guides/gs/messaging-stomp-websocket/,里面简单介绍了如何通过Spring-boot来进行Websocket系统的构建。我们的例子将基于这个例子进行修改,但是是使用传统的Spring的方式进行配置。

依赖包

首先我们需要添加相关的依赖包:

  • Websocket需要servlet3.1的版本
  • spring-websocket和spring-messaging是Spring关于Websocket的组件
  • 使用Jackson进行json数据的处理
build.gradle
123456789101112131415161718
String springVersion = "4.1.4.RELEASE"String jacksonDatabindVersion = "2.5.0"String jacksonVersion = "1.9.13"dependencies {    //websocket    compile("javax.websocket:javax.websocket-api:1.1")    compile("javax.servlet:javax.servlet-api:3.1.0")  //spring    compile("org.springframework:spring-messaging:" + springVersion)    compile("org.springframework:spring-websocket:" + springVersion)    //json    compile "com.fasterxml.jackson.core:jackson-databind:" + jacksonDatabindVersion    compile "org.codehaus.jackson:jackson-mapper-asl:" + jacksonVersion    compile "org.codehaus.jackson:jackson-core-asl:" + jacksonVersion}

xml配置(类配置)

我们有两种方式进行Websocket的配置,一种是通过xml文件的方式,在这里我们定义了websocket的配置信息,这样服务器往客户端发送消息就可以通过/topic/xx来发送,客户端则可以通过/app/hello来发送消息到服务端。

123456789101112131415
<beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:websocket="http://www.springframework.org/schema/websocket"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd        http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">    ...... // other configurations    <websocket:message-broker application-destination-prefix="/app">        <websocket:stomp-endpoint path="/hello">            <websocket:sockjs/>        </websocket:stomp-endpoint>        <websocket:simple-broker prefix="/topic"/>    </websocket:message-broker></beans>

另外一种方式是通过类的方式,代码如下,功能与上面的xml配置相同:

WebSocketConfig.java
123456789101112131415161718192021
import org.springframework.context.annotation.Configuration;import org.springframework.messaging.simp.config.MessageBrokerRegistry;import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;import org.springframework.web.socket.config.annotation.StompEndpointRegistry;@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {  @Override  public void configureMessageBroker(MessageBrokerRegistry config) {      config.enableSimpleBroker("/topic");      config.setApplicationDestinationPrefixes("/app");  }  @Override  public void registerStompEndpoints(StompEndpointRegistry registry) {      registry.addEndpoint("/hello").withSockJS();  }}

消息类和Controller定义

Controller定义:

WebSocketConfig.java
123456789101112131415
import com.zzm.wechat.model.Greeting;import com.zzm.wechat.model.HelloMessage;import org.springframework.messaging.handler.annotation.MessageMapping;import org.springframework.messaging.handler.annotation.SendTo;import org.springframework.stereotype.Controller;@Controllerpublic class GreetingController {    @MessageMapping("/hello")    @SendTo("/topic/greetings")    public Greeting greeting(HelloMessage message) throws Exception {        Thread.sleep(3000); // simulated delay        return new Greeting("Hello, " + message.getName() + "!");    }}

消息model的定义:

WebSocketConfig.java
12345678910111213141516171819
public class Greeting {    private String content;    public Greeting(String content) {        this.content = content;    }    public String getContent() {        return content;    }}public class HelloMessage {    private String name;    public String getName() {        return name;    }}

在web.xml中设置controller的url前缀,这样可以避免一些页面的url被controller拦截。

web.xml
12345678910
    <servlet>      <servlet-name>mvc-dispatcher</servlet-name>      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <load-on-startup>1</load-on-startup>  </servlet>  <servlet-mapping>      <servlet-name>mvc-dispatcher</servlet-name>      <url-pattern>/api/*</url-pattern>  </servlet-mapping>

客户端页面

首先下载stomp.jssockjs.js,然后编写一个html页面进行客户端websocket的连接,并实现发送消息和接收消息的功能。我们使用SockJS的方式来创建Websocket连接,注意url要加上domain名称(这里是server)和api前缀。

demo.html
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
<!DOCTYPE html><html><head>    <title>Hello WebSocket</title>    <script src="resources/sockjs-0.3.4.js"></script>    <script src="resources/stomp.js"></script>    <script type="text/javascript">        var stompClient = null;        function setConnected(connected) {            document.getElementById('connect').disabled = connected;            document.getElementById('disconnect').disabled = !connected;            document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';            document.getElementById('response').innerHTML = '';        }        function connect() {            var socket = new SockJS('/server/api/hello');            stompClient = Stomp.over(socket);            stompClient.connect({}, function(frame) {                setConnected(true);                console.log('Connected: ' + frame);                stompClient.subscribe('/topic/greetings', function(greeting){                    showGreeting(JSON.parse(greeting.body).content);                });            });        }        function disconnect() {            if (stompClient != null) {                stompClient.disconnect();            }            setConnected(false);            console.log("Disconnected");        }        function sendName() {            var name = document.getElementById('name').value;            stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));        }        function showGreeting(message) {            var response = document.getElementById('response');            var p = document.createElement('p');            p.style.wordWrap = 'break-word';            p.appendChild(document.createTextNode(message));            response.appendChild(p);        }    </script></head><body onload="disconnect()"><noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable    Javascript and reload this page!</h2></noscript><div>    <div>        <button id="connect" onclick="connect();">Connect</button>        <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>    </div>    <div id="conversationDiv">        <label>What is your name?</label><input type="text" id="name" />        <button id="sendName" onclick="sendName();">Send</button>        <p id="response"></p>    </div></div></body></html>

运行结果:

浏览器console信息:

1234567891011121314151617181920212223242526272829303132333435
Disconnectedchrome-extension://fhhdlnnepfjhlhilgmeepgkhjmhhhjkh/js/detector.js:505 detectorchrome-extension://fhhdlnnepfjhlhilgmeepgkhjmhhhjkh/js/detector.js:506 Objectstomp.js:130 Opening Web Socket...stomp.js:130 Web Socket Opened...stomp.js:130 >>> CONNECTaccept-version:1.1,1.0heart-beat:10000,10000<<< CONNECTEDversion:1.1heart-beat:0,0connected to server undefineddemo.html:22 Connected: CONNECTEDheart-beat:0,0version:1.1>>> SUBSCRIBEid:sub-0destination:/topic/greetings>>> SENDdestination:/app/hellocontent-length:14{"name":"zzm"}<<< MESSAGEdestination:/topic/greetingscontent-type:application/json;charset=UTF-8subscription:sub-0message-id:3657pj5u-0content-length:25{"content":"Hello, zzm!"}

gradle运行jetty9

gradle内置的Jetty版本是Jetty6,由于版本较低不支持websocket,所以我们测试的话需要打包并部署到Jetty9或Tomcat7.0.47+上,但我们可以通过其他gradle插件来把我们的本地服务运行到Jetty9上。这里介绍2个插件,Gretty和Cargo。

Gretty

build.gradle中添加如下脚本:

build.gradle
12345678910111213141516171819
buildscript {    repositories {        maven {            url "http://maven.oschina.net/content/groups/public/"        }    }    dependencies {        classpath 'org.akhikhl.gretty:gretty:+'    }}apply plugin: 'org.akhikhl.gretty'// apply plugin: 'jetty' 注意要注释掉原来的jetty插件gretty {    httpPort = 9898 // 指定web服务的http端口    servletContainer = 'jetty9' // 这里可以指定tomcat,jetty的几个版本}

然后运行gradle appRun即可。

Cargo

build.gradle中添加如下脚本,注意要先下载jetty9的安装包并解压:

build.gradle
1234567891011121314151617181920
buildscript {    repositories {        maven {            url "http://maven.oschina.net/content/groups/public/"        }    }    dependencies {        classpath 'com.bmuschko:gradle-cargo-plugin:2.1'    }}apply plugin: 'com.bmuschko.cargo'cargo {    containerId = 'jetty9x'    port = 9898    local {        homeDir = file('/Users/zhaozhiming/tools/jetty-distribution-9.2.10.v20150310')    }}

然后运行gradle war CargoRunLocal,注意首先要打出war包,然后插件会自动部署war包到Jetty9的安装目录下,这种方式不大灵活,比如一些页面的修改都需要重新部署才能进行测试。

最后附上Spring关于Websocket的文档链接,请见这里。

0 0