一:本文使用范圍
潞州網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、響應(yīng)式網(wǎng)站開(kāi)發(fā)等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站從2013年創(chuàng)立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專(zhuān)注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。
此文不僅僅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一樣的,只不過(guò)配置一些監(jiān)聽(tīng)器的方法不同而已。
本文經(jīng)過(guò)作者實(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的包
javax javaee-api 7.0 provided
當(dāng)然,其實(shí)tomcat中已經(jīng)自帶了這個(gè)包。
如果是在spring boot中,還需要加入websocket的starter
org.springframework.boot spring-boot-starter-websocket 1.4.0.RELEASE
2.2:配置websocket
如果不是spring boot項(xiàng)目,那就不需要進(jìn)行這樣的配置,因?yàn)槿绻趖omcat中運(yùn)行的話,tomcat會(huì)掃描帶有@ServerEndpoint的注解成為websocket,而spring boot項(xiàng)目中需要由這個(gè)bean來(lái)提供注冊(cè)管理。
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
2.3:websocket的java代碼
使用websocket的核心,就是一系列的websocket注解,@ServerEndpoint是注冊(cè)在類(lèi)上面開(kāi)啟。
@ServerEndpoint(value = "/websocket") @Component public class MyWebSocket { //與某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(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("來(lái)自瀏覽器的消息:" + 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);//異步 } }
其實(shí)我也感覺(jué)很奇怪,為什么不使用接口來(lái)規(guī)范。即使是因?yàn)锧ServerEndpoint注解中其它屬性中可以定義出一些額外的參數(shù),但相信也是可以抽象出來(lái)的,不過(guò)想必javaee這樣做,應(yīng)該是有它的用意吧。
2.4:瀏覽器端的代碼
瀏覽器端的代碼需要瀏覽器支持websocket,當(dāng)然,也有socket.js可以支持到ie7,但是這個(gè)我沒(méi)用過(guò)。畢竟ie基本上沒(méi)人用的,市面上的瀏覽器基本上全部都支持websocket。
如此就連接成功了。
三:獲取HttpSession,源碼分析
獲取HttpSession是一個(gè)很有必要討論的問(wèn)題,因?yàn)閖ava后臺(tái)需要知道當(dāng)前是哪個(gè)用戶,用以處理該用戶的業(yè)務(wù)邏輯,或者是對(duì)該用戶進(jìn)行授權(quán)之類(lèi)的,但是由于websocket的協(xié)議與Http協(xié)議是不同的,所以造成了無(wú)法直接拿到session。但是問(wèn)題總是要解決的,不然這個(gè)websocket協(xié)議所用的場(chǎng)景也就沒(méi)了。
3.1:獲取HttpSession的工具類(lèi),源碼詳細(xì)分析
我們先來(lái)看一下@ServerEndpoint注解的源碼
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; }
我們看到最后的一個(gè)方法,也就是加粗的方法。可以看到,它要求返回一個(gè)ServerEndpointConfig.Configurator的子類(lèi),我們寫(xiě)一個(gè)類(lèi)去繼承它。
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) { //怎么搞? } }
當(dāng)我們覆蓋modifyHandshake方法時(shí),可以看到三個(gè)參數(shù),其中后面兩個(gè)參數(shù)讓我們感覺(jué)有點(diǎn)見(jiàn)過(guò)的感覺(jué),我們查看一HandshakeRequest的源碼
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> 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 > getParameterMap(); String getQueryString(); }
我們發(fā)現(xiàn)它是一個(gè)接口,接口中規(guī)范了這樣的一個(gè)方法
/** * 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();
上面有相應(yīng)的注釋?zhuān)f(shuō)明可以從Servlet API中獲取到相應(yīng)的HttpSession。
當(dāng)我們發(fā)現(xiàn)這個(gè)方法的時(shí)候,其實(shí)已經(jīng)松了一口氣了。
那么我們就可以補(bǔ)全未完成的代碼
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; /** * 從websocket中獲取用戶session * * */ public class HttpSessionConfigurator extends Configurator { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession) request.getHttpSession(); sec.getUserProperties().put(HttpSession.class.getName(), httpSession); } }
其實(shí)通過(guò)上面的源碼分析,你們應(yīng)該知道了HttpSession的獲取。但是下面又多了一行代碼
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
這行代碼又是什么意思呢?
我們看一下ServerEnpointConfig的聲明
public interface ServerEndpointConfig extends EndpointConfig
我們發(fā)現(xiàn)這個(gè)接口繼承了EndpointConfig的接口,好,我們看一下EndpointConfig的源碼:
package javax.websocket; import java.util.List; import java.util.Map; public interface EndpointConfig { List> getEncoders(); List > getDecoders(); Map getUserProperties(); }
我們發(fā)現(xiàn)了這樣的一個(gè)方法定義
Map
可以看到,它是一個(gè)map,從方法名也可以理解到,它是用戶的一些屬性的存儲(chǔ),那既然它提供了get方法,那么也就意味著我們可以拿到這個(gè)map,并且對(duì)這里面的值進(jìn)行操作,
所以就有了上面的
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
那么到此,獲取HttpSession的源碼分析,就完成了。
3.2:設(shè)置HttpSession的類(lèi)
我們之前有說(shuō)過(guò),由于HTTP協(xié)議與websocket協(xié)議的不同,導(dǎo)致沒(méi)法直接從websocket中獲取協(xié)議,然后在3.1中我們已經(jīng)寫(xiě)了獲取HttpSession的代碼,但是如果真的放出去執(zhí)行,那么會(huì)報(bào)空指值異常,因?yàn)檫@個(gè)HttpSession并沒(méi)有設(shè)置進(jìn)去。
好,這一步,我們來(lái)設(shè)置HttpSession。這時(shí)候我們需要寫(xiě)一個(gè)監(jiān)聽(tīng)器。
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; @Component public class RequestListener implements ServletRequestListener { public void requestInitialized(ServletRequestEvent sre) { //將所有request請(qǐng)求都攜帶上httpSession ((HttpServletRequest) sre.getServletRequest()).getSession(); } public RequestListener() { } public void requestDestroyed(ServletRequestEvent arg0) { } }
然后我們需要把這個(gè)類(lèi)注冊(cè)為監(jiān)聽(tīng)器,如果是普通的Spring工程,或者是servlet工程,那么要么在web.xml中配置,要么使用@WebListener注解。
因?yàn)楸疚氖且許pring boot工程來(lái)演示,所以這里只寫(xiě)Spring boot配置Listener的代碼,其它的配置方式,請(qǐng)自行百度。
這是使用@Bean注解的方式
@Autowird private RequestListener requestListener; @Bean public ServletListenerRegistrationBeanservletListenerRegistrationBean() { ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(); servletListenerRegistrationBean.setListener(requestListener); return servletListenerRegistrationBean; }
或者也可以使用@WebListener注解
然后使用@ServletComponentScan注解,配置在啟動(dòng)方法上面。
3.3:在websocket中獲取用戶的session
然后剛才我們通過(guò)源碼分析,是知道@ServerEndpoint注解中是有一個(gè)參數(shù)可以配置我們剛才繼承的類(lèi)。好的,我們現(xiàn)在進(jìn)行配置。
@ServerEndpoint(value = "/websocket" , configurator = HttpSessionConfigurator.class)
接下來(lái)就可以在@OnOpen注解中所修飾的方法中,拿到EndpointConfig對(duì)象,并且通過(guò)這個(gè)對(duì)象,拿到之前我們?cè)O(shè)置進(jìn)去的map
@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(); } } }
這下我們就從java的webscoket中拿到了用戶的session。
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助~如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)創(chuàng)新互聯(lián)的支持!