一:本文使用范圍
此文不僅僅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一樣的,只不過配置一些監聽器的方法不同而已。
本文經過作者實踐,確認完美運行。
二:Spring boot使用websocket
2.1:依賴包
websocket本身是servlet容器所提供的服務,所以需要在web容器中運行,像我們所使用的tomcat,當然,spring boot中已經內嵌了tomcat。
websocket遵循了javaee規范,所以需要引入javaee的包
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency>
當然,其實tomcat中已經自帶了這個包。
如果是在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項目,那就不需要進行這樣的配置,因為如果在tomcat中運行的話,tomcat會掃描帶有@ServerEndpoint的注解成為websocket,而spring boot項目中需要由這個bean來提供注冊管理。
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
2.3:websocket的java代碼
使用websocket的核心,就是一系列的websocket注解,@ServerEndpoint是注冊在類上面開啟。
@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {
//與某個客戶端的連接會話,需要通過它來給客戶端發送數據
private Session session;
/**
* 連接成功*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose() {
}
/**
* 收到消息
*
* @param message
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自瀏覽器的消息:" + message);
//群發消息
for (MyWebSocket item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 發生錯誤時調用
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發生錯誤");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);//同步
//this.session.getAsyncRemote().sendText(message);//異步
}
}
其實我也感覺很奇怪,為什么不使用接口來規范。即使是因為@ServerEndpoint注解中其它屬性中可以定義出一些額外的參數,但相信也是可以抽象出來的,不過想必javaee這樣做,應該是有它的用意吧。
2.4:瀏覽器端的代碼
瀏覽器端的代碼需要瀏覽器支持websocket,當然,也有socket.js可以支持到ie7,但是這個我沒用過。畢竟ie基本上沒人用的,市面上的瀏覽器基本上全部都支持websocket。
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:9999/websocket");
}
else{
alert('不支持websocket')
}
//連接發生錯誤
websocket.onerror = function(){
};
//連接成功
websocket.onopen = function(event){
}
//接收到消息
websocket.onmessage = function(event){
var msg = event.data;
alert("收到消息:" + msg);
}
//連接關閉
websocket.onclose = function(){
}
//監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。
window.onbeforeunload = function(){
websocket.close();
}
//發送消息
function send(message){
websocket.send(message);
}
</script>
</html>
如此就連接成功了。
三:獲取HttpSession,源碼分析
獲取HttpSession是一個很有必要討論的問題,因為java后臺需要知道當前是哪個用戶,用以處理該用戶的業務邏輯,或者是對該用戶進行授權之類的,但是由于websocket的協議與Http協議是不同的,所以造成了無法直接拿到session。但是問題總是要解決的,不然這個websocket協議所用的場景也就沒了。
3.1:獲取HttpSession的工具類,源碼詳細分析
我們先來看一下@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;
}
我們看到最后的一個方法,也就是加粗的方法??梢钥吹?,它要求返回一個ServerEndpointConfig.Configurator的子類,我們寫一個類去繼承它。
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) {
//怎么搞?
}
}
當我們覆蓋modifyHandshake方法時,可以看到三個參數,其中后面兩個參數讓我們感覺有點見過的感覺,我們查看一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<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();
}
我們發現它是一個接口,接口中規范了這樣的一個方法
/** * 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();
上面有相應的注釋,說明可以從Servlet API中獲取到相應的HttpSession。
當我們發現這個方法的時候,其實已經松了一口氣了。
那么我們就可以補全未完成的代碼
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);
}
}
其實通過上面的源碼分析,你們應該知道了HttpSession的獲取。但是下面又多了一行代碼
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
這行代碼又是什么意思呢?
我們看一下ServerEnpointConfig的聲明
public interface ServerEndpointConfig extends EndpointConfig
我們發現這個接口繼承了EndpointConfig的接口,好,我們看一下EndpointConfig的源碼:
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();
}
我們發現了這樣的一個方法定義
Map<String,Object> getUserProperties();
可以看到,它是一個map,從方法名也可以理解到,它是用戶的一些屬性的存儲,那既然它提供了get方法,那么也就意味著我們可以拿到這個map,并且對這里面的值進行操作,
所以就有了上面的
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
那么到此,獲取HttpSession的源碼分析,就完成了。
3.2:設置HttpSession的類
我們之前有說過,由于HTTP協議與websocket協議的不同,導致沒法直接從websocket中獲取協議,然后在3.1中我們已經寫了獲取HttpSession的代碼,但是如果真的放出去執行,那么會報空指值異常,因為這個HttpSession并沒有設置進去。
好,這一步,我們來設置HttpSession。這時候我們需要寫一個監聽器。
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請求都攜帶上httpSession
((HttpServletRequest) sre.getServletRequest()).getSession();
}
public RequestListener() {
}
public void requestDestroyed(ServletRequestEvent arg0) {
}
}
然后我們需要把這個類注冊為監聽器,如果是普通的Spring工程,或者是servlet工程,那么要么在web.xml中配置,要么使用@WebListener注解。
因為本文是以Spring boot工程來演示,所以這里只寫Spring boot配置Listener的代碼,其它的配置方式,請自行百度。
這是使用@Bean注解的方式
@Autowird
private RequestListener requestListener;
@Bean
public ServletListenerRegistrationBean<RequestListener> servletListenerRegistrationBean() {
ServletListenerRegistrationBean<RequestListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>();
servletListenerRegistrationBean.setListener(requestListener);
return servletListenerRegistrationBean;
}
或者也可以使用@WebListener注解
然后使用@ServletComponentScan注解,配置在啟動方法上面。
3.3:在websocket中獲取用戶的session
然后剛才我們通過源碼分析,是知道@ServerEndpoint注解中是有一個參數可以配置我們剛才繼承的類。好的,我們現在進行配置。
@ServerEndpoint(value = "/websocket" , configurator = HttpSessionConfigurator.class)
接下來就可以在@OnOpen注解中所修飾的方法中,拿到EndpointConfig對象,并且通過這個對象,拿到之前我們設置進去的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。
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助~如果有疑問大家可以留言交流,謝謝大家對億速云的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。