java使用websocket,并且获取HttpSession 源码分析(推荐)

作者:朱小杰 时间:2023-08-04 17:38:05 

一:本文使用范围

此文不仅仅局限于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。

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

标签:java,websocket,HttpSession
0
投稿

猜你喜欢

  • SpringBoot集成Swagger2的方法

    2023-11-26 13:15:42
  • SpringBoot项目部署到腾讯云的实现步骤

    2023-01-01 16:58:55
  • 详细解读Java Spring AOP

    2022-10-09 11:06:06
  • java基于JDBC连接Oracle 11g Release2实例分析

    2022-06-06 02:36:36
  • 拉钩网java笔试题分享

    2022-02-13 08:48:25
  • java实现滑动验证解锁

    2023-06-02 12:16:36
  • 详解Lombok安装及Spring Boot集成Lombok

    2023-11-28 23:39:55
  • Spring集成Quartz的简单配置的方法

    2023-08-24 02:52:27
  • SpringMVC 限流的示例代码

    2022-08-21 09:48:51
  • SpringBoot下使用定时任务的方式全揭秘(6种)

    2022-06-13 01:34:48
  • 详解java_ 集合综合案例:斗地主

    2022-02-08 04:14:49
  • Java数据结构之栈与队列实例详解

    2021-05-29 03:25:13
  • Android TextView控件文字添加下划线的实现方法

    2022-03-11 06:01:43
  • 保持Android Service在手机休眠后继续运行的方法

    2022-01-01 20:36:12
  • Java检查非空的三种方法总结

    2023-10-03 19:58:16
  • SpringBoot整合RedisTemplate实现缓存信息监控的步骤

    2023-10-14 10:17:13
  • c# 引用类型和值类型

    2023-10-11 08:20:48
  • PowerShell 定时执行.Net(C#)程序的方法

    2023-07-09 14:10:39
  • 一文了解Java中record和lombok的使用对比

    2022-08-02 06:39:05
  • Android图片色彩变换实现方法

    2022-03-21 07:23:32
  • asp之家 软件编程 m.aspxhome.com