轉(zhuǎn)載請?jiān)陧撌鬃⒚髯髡吲c出處

 http://www.cnblogs.com/zhuxiaojie/p/6238826.html

一:本文使用范圍

此文不僅僅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一樣的,只不過配置一些監(jiān)聽器的方法不同而已。

 

本文經(jīng)過作者實(shí)踐,確認(rèn)完美運(yùn)行。

 

二:Spring boot使用websocket

2.1:依賴包

websocket本身是servlet容器所提供的服務(wù),所以需要在web容器中運(yùn)行,像我們所使用的tomcat,當(dāng)然,spring boot中已經(jīng)內(nèi)嵌了tomcat。

websocket遵循了javaee規(guī)范,所以需要引入javaee的包 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

<dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>7.0</version>
      <scope>provided</scope>
    </dependency>

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

當(dāng)然,其實(shí)tomcat中已經(jīng)自帶了這個(gè)包。

如果是在spring boot中,還需要加入websocket的starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>1.4.0.RELEASE</version>
        </dependency>

 

 

 

2.2:配置websocket

如果不是spring boot項(xiàng)目,那就不需要進(jìn)行這樣的配置,因?yàn)槿绻趖omcat中運(yùn)行的話,tomcat會(huì)掃描帶有@ServerEndpoint的注解成為websocket,而spring boot項(xiàng)目中需要由這個(gè)bean來提供注冊管理。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

@Configurationpublic class WebSocketConfig {
    @Bean    public ServerEndpointExporter serverEndpointExporter() {        return new ServerEndpointExporter();
    }

}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

 

 

 

2.3:websocket的java代碼

使用websocket的核心,就是一系列的websocket注解,@ServerEndpoint是注冊在類上面開啟。

 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

@ServerEndpoint(value = "/websocket")
@Componentpublic class MyWebSocket {    //與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
    private Session session;    /**
     * 連接成功*/
    @OnOpen    public void onOpen(Session session) {        this.session = session;

    }    /**
     * 連接關(guān)閉調(diào)用的方法     */
    @OnClose    public void onClose() {

    }    /**
     * 收到消息
     *
     * @param message 
    */
    @OnMessage    public void onMessage(String message, Session session) {
        System.out.println("來自瀏覽器的消息:" + message);        //群發(fā)消息
        for (MyWebSocket item : webSocketSet) {            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }    /**
     * 發(fā)生錯(cuò)誤時(shí)調(diào)用     */
    @OnError    public void onError(Session session, Throwable error) {
        System.out.println("發(fā)生錯(cuò)誤");
        error.printStackTrace();
    }    public void sendMessage(String message) throws IOException {        this.session.getBasicRemote().sendText(message);//同步        //this.session.getAsyncRemote().sendText(message);//異步    }

 


}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

其實(shí)我也感覺很奇怪,為什么不使用接口來規(guī)范。即使是因?yàn)锧ServerEndpoint注解中其它屬性中可以定義出一些額外的參數(shù),但相信也是可以抽象出來的,不過想必javaee這樣做,應(yīng)該是有它的用意吧。

 

 

 

 

2.4:瀏覽器端的代碼

瀏覽器端的代碼需要瀏覽器支持websocket,當(dāng)然,也有socket.js可以支持到ie7,但是這個(gè)我沒用過。畢竟ie基本上沒人用的,市面上的瀏覽器基本上全部都支持websocket。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

<!DOCTYPE HTML><html><head></head><body></body><script type="text/javascript">
    var websocket = null;    //判斷當(dāng)前瀏覽器是否支持WebSocket
    if('WebSocket' in window){
        websocket = new WebSocket("ws://localhost:9999/websocket");
    }    else{
        alert('不支持websocket')
    }    //連接發(fā)生錯(cuò)誤    websocket.onerror = function(){
        
    };    //連接成功    websocket.onopen = function(event){
        
    }    //接收到消息    websocket.onmessage = function(event){        var msg = event.data;
        alert("收到消息:" + msg);
    }    //連接關(guān)閉    websocket.onclose = function(){
        
    }    //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會(huì)拋異常。    window.onbeforeunload = function(){
        websocket.close();
    }   

  

    //發(fā)送消息
    function send(message){
        websocket.send(message);
    }</script></html>

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

如此就連接成功了。

 

 

 

 

 

三:獲取HttpSession,源碼分析

獲取HttpSession是一個(gè)很有必要討論的問題,因?yàn)閖ava后臺需要知道當(dāng)前是哪個(gè)用戶,用以處理該用戶的業(yè)務(wù)邏輯,或者是對該用戶進(jìn)行授權(quán)之類的,但是由于websocket的協(xié)議與Http協(xié)議是不同的,所以造成了無法直接拿到session。但是問題總是要解決的,不然這個(gè)websocket協(xié)議所用的場景也就沒了。

 

3.1:獲取HttpSession的工具類,源碼詳細(xì)分析

我們先來看一下@ServerEndpoint注解的源碼

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

package javax.websocket.server;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import javax.websocket.Decoder;import javax.websocket.Encoder;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)public @interface ServerEndpoint {    /**
     * URI or URI-template that the annotated class should be mapped to.
     * @return The URI or URI-template that the annotated class should be mapped
     *         to.     */
    String value();

    String[] subprotocols() default {};

    Class<? extends Decoder>[] decoders() default {};

    Class<? extends Encoder>[] encoders() default {};    public Class<? extends ServerEndpointConfig.Configurator> configurator()            default ServerEndpointConfig.Configurator.class;}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

我們看到最后的一個(gè)方法,也就是加粗的方法。可以看到,它要求返回一個(gè)ServerEndpointConfig.Configurator的子類,我們寫一個(gè)類去繼承它。

 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

import javax.servlet.http.HttpSession;import javax.websocket.HandshakeResponse;import javax.websocket.server.HandshakeRequest;import javax.websocket.server.ServerEndpointConfig;import javax.websocket.server.ServerEndpointConfig.Configurator;public class HttpSessionConfigurator extends Configurator {

    @Override    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {

    //怎么搞?
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

當(dāng)我們覆蓋modifyHandshake方法時(shí),可以看到三個(gè)參數(shù),其中后面兩個(gè)參數(shù)讓我們感覺有點(diǎn)見過的感覺,我們查看一HandshakeRequest的源碼

 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

package javax.websocket.server;import java.net.URI;import java.security.Principal;import java.util.List;import java.util.Map;/**
 * Represents the HTTP request that asked to be upgraded to WebSocket. */public interface HandshakeRequest {    static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";    static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";    static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";    static final String SEC_WEBSOCKET_EXTENSIONS= "Sec-WebSocket-Extensions";

    Map<String,List<String>> getHeaders();

    Principal getUserPrincipal();

    URI getRequestURI();    boolean isUserInRole(String role);    /**
     * Get the HTTP Session object associated with this request. Object is used
     * to avoid a direct dependency on the Servlet API.
     * @return The javax.servlet.http.HttpSession object associated with this
     *         request, if any.     */
    Object getHttpSession();

    Map<String, List<String>> getParameterMap();

    String getQueryString();
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

我們發(fā)現(xiàn)它是一個(gè)接口,接口中規(guī)范了這樣的一個(gè)方法

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

    /**
     * Get the HTTP Session object associated with this request. Object is used
     * to avoid a direct dependency on the Servlet API.
     * @return The javax.servlet.http.HttpSession object associated with this
     *         request, if any.     */
    Object getHttpSession();

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

上面有相應(yīng)的注釋,說明可以從Servlet API中獲取到相應(yīng)的HttpSession。

 

當(dāng)我們發(fā)現(xiàn)這個(gè)方法的時(shí)候,其實(shí)已經(jīng)松了一口氣了。

那么我們就可以補(bǔ)全未完成的代碼

 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

  HttpSessionConfigurator  =

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

其實(shí)通過上面的源碼分析,你們應(yīng)該知道了HttpSession的獲取。但是下面又多了一行代碼

 sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

這行代碼又是什么意思呢?

我們看一下ServerEnpointConfig的聲明

public interface ServerEndpointConfig extends EndpointConfig

我們發(fā)現(xiàn)這個(gè)接口繼承了EndpointConfig的接口,好,我們看一下EndpointConfig的源碼:

 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

package javax.websocket;import java.util.List;import java.util.Map;public interface EndpointConfig {

    List<Class<? extends Encoder>> getEncoders();

    List<Class<? extends Decoder>> getDecoders();    Map<String,Object> getUserProperties();}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

我們發(fā)現(xiàn)了這樣的一個(gè)方法定義

Map<String,Object> getUserProperties();

可以看到,它是一個(gè)map,從方法名也可以理解到,它是用戶的一些屬性的存儲(chǔ),那既然它提供了get方法,那么也就意味著我們可以拿到這個(gè)map,并且對這里面的值進(jìn)行操作,

所以就有了上面的

sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

 

 

那么到此,獲取HttpSession的源碼分析,就完成了。

 

 

 

 

3.2:設(shè)置HttpSession的類

我們之前有說過,由于HTTP協(xié)議與websocket協(xié)議的不同,導(dǎo)致沒法直接從websocket中獲取協(xié)議,然后在3.1中我們已經(jīng)寫了獲取HttpSession的代碼,但是如果真的放出去執(zhí)行,那么會(huì)報(bào)空指值異常,因?yàn)檫@個(gè)HttpSession并沒有設(shè)置進(jìn)去。

好,這一步,我們來設(shè)置HttpSession。這時(shí)候我們需要寫一個(gè)監(jiān)聽器。

 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

import javax.servlet.ServletRequestEvent;import javax.servlet.ServletRequestListener;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Component;

@Componentpublic class RequestListener implements ServletRequestListener {    public void requestInitialized(ServletRequestEvent sre)  {        //將所有request請求都攜帶上httpSession        ((HttpServletRequest) sre.getServletRequest()).getSession();

    }    public RequestListener() {
    }    public void requestDestroyed(ServletRequestEvent arg0)  {
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

然后我們需要把這個(gè)類注冊為監(jiān)聽器,如果是普通的Spring工程,或者是servlet工程,那么要么在web.xml中配置,要么使用@WebListener注解。

因?yàn)楸疚氖且許pring boot工程來演示,所以這里只寫Spring boot配置Listener的代碼,其它的配置方式,請自行百度。

 

這是使用@Bean注解的方式

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

RequestListener requestListener;
 ServletListenerRegistrationBean<RequestListener><RequestListener> servletListenerRegistrationBean =  ServletListenerRegistrationBean<>

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

或者也可以使用@WebListener注解

然后使用@ServletComponentScan注解,配置在啟動(dòng)方法上面。

 

 

 

 

3.3:在websocket中獲取用戶的session

然后剛才我們通過源碼分析,是知道@ServerEndpoint注解中是有一個(gè)參數(shù)可以配置我們剛才繼承的類。好的,我們現(xiàn)在進(jìn)行配置。

@ServerEndpoint(value = "/websocket" , configurator = HttpSessionConfigurator.class)

 

接下來就可以在@OnOpen注解中所修飾的方法中,拿到EndpointConfig對象,并且通過這個(gè)對象,拿到之前我們設(shè)置進(jìn)去的map

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

@OnOpen    public void onOpen(Session session,EndpointConfig config){
        HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        User user = (User)httpSession.getAttribute(SessionName.USER);        if(user != null){            this.session = session;            this.httpSession = httpSession;
        }else{            //用戶未登陸
            try {
                session.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

 

這下我們就從java的webscoket中拿到了用戶的session。

 



感謝您的閱讀,如果您覺得閱讀本文對您有幫助,請點(diǎn)一下“推薦”按鈕。本文歡迎各位轉(zhuǎn)載,但是轉(zhuǎn)載文章之后必須在文章頁面中給出作者和原文連接。