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

一:本文使用范围

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

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助~如果有疑问大家可以留言交流,谢谢大家对我们的支持!

(0)

相关推荐

  • java 在Jetty9中使用HttpSessionListener和Filter

    java 在Jetty9中使用HttpSessionListener和Filter HttpSessionListener 当Session创建或销毁的时候被调用 示例代码: class MyHttpSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { System.out.println("session

  • java使用HttpSession实现QQ访问记录

    java如何使用HttpSession实现QQ的访问记录,本文为大家揭晓答案,具体内容如下 1. 编写QQ空间数据类(QQS.java) public class QQS { private static LinkedHashMap<Integer, String> qqs = new LinkedHashMap<Integer, String>(); static{ qqs.put(10001, "张三"); qqs.put(10002, "李四&q

  • 详解spring boot实现websocket

    前言 QQ这类即时通讯工具多数是以桌面应用的方式存在.在没有websocket出现之前,如果开发一个网页版的即时通讯应用,则需要定时刷新页面或定时调用ajax请求,这无疑会加大服务器的负载和增加了客户端的流量.而websocket的出现,则完美的解决了这些问题. spring boot对websocket进行了封装,这对实现一个websocket网页即时通讯应用来说,变得非常简单.  一.准备工作 pom.xml引入 <dependency> <groupId>org.spring

  • express框架实现基于Websocket建立的简易聊天室

    最近想写点有意思的,所以整了个这个简单的不太美观的小玩意 首先你得确认你的电脑装了node,然后就可以按照步骤 搞事情了~~ 1.建立一个文件夹 2.清空当前文件夹地址栏,在文件夹地址栏中输入cmd.exe 3.我们需要下载点小东西 ,需要在命令行输入 npm install express 回车 等待一会 npm install express-session 回车 等待一会 npm install ejs 回车 等待一会 npm install socket.io 回车 等待一会 叮~~~

  • NetCore WebSocket即时通讯示例

    NetCore WebSocket 即时通讯示例,供大家参考,具体内容如下 1.新建Netcore Web项目 2.创建简易通讯协议 public class MsgTemplate { public string SenderID { get; set; } public string ReceiverID { get; set; } public string MessageType { get; set; } public string Content { get; set; } } Se

  • JavaWeb中HttpSession中表单的重复提交示例

    表单的重复提交 重复提交的情况: ①. 在表单提交到一个 Servlet,而 Servlet 又通过请求转发的方式响应了一个 JSP(HTML)页面,此时地址栏还保留着 Servlet 的那个路径,在响应页面点击 "刷新". ②. 在响应页面没有到达时,重复点击 "提交按钮" ③. 点击返回,再点击提交 不是重复提交的情况:点击 "返回","刷新" 原表单页面,再点击提交. 如何避免表单的重复提交:在表单中做一个标记,提交到

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

    一:本文使用范围 此文不仅仅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一样的,只不过配置一些监听器的方法不同而已. 本文经过作者实践,确认完美运行. 二:Spring boot使用websocket 2.1:依赖包 websocket本身是servlet容器所提供的服务,所以需要在web容器中运行,像我们所使用的tomcat,当然,spring boot中已经内嵌了tomcat. websocket遵循了javaee规范,所以需要引入javaee的包 <

  • Java中的InputStreamReader和OutputStreamWriter源码分析_动力节点Java学院整理

    InputStreamReader和OutputStreamWriter源码分析 1. InputStreamReader 源码(基于jdk1.7.40) package java.io; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import sun.nio.cs.StreamDecoder; // 将"字节输入流"转换成"字符输入流" public class

  • java编程Reference核心原理示例源码分析

    带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用ByteBuffer的allocateDirect方法,申请一块堆外内存创建一个DirectByteBuffer对象,然后利用它去操作堆外内存.这些申请完的堆外内存,我们可以回收吗?可以的话是通过什么样的机制回收的? 大家应该都知道WeakHashMap可以用来实现内存相对敏感的本地缓存,为什么WeakHashMap合适这种业务场景,其内部实现会做什么特殊处理呢?

  • Java 中的FileReader和FileWriter源码分析_动力节点Java学院整理

    FileReader和FileWriter源码分析 1. FileReader 源码(基于jdk1.7.40) package java.io; public class FileReader extends InputStreamReader { public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fil java io系列21之 InputStreamReade

  • Java Shutdown Hook场景使用及源码分析

    目录 背景 Shutdown Hook 介绍 关闭钩子被调用场景 注意事项 实践 Shutdown Hook 在 Spring 中的运用 背景 如果想在 Java 进程退出时,包括正常和异常退出,做一些额外处理工作,例如资源清理,对象销毁,内存数据持久化到磁盘,等待线程池处理完所有任务等等.特别是进程异常挂掉的情况,如果一些重要状态没及时保留下来,或线程池的任务没被处理完,有可能会造成严重问题.那该怎么办呢? Java 中的 Shutdown Hook 提供了比较好的方案.我们可以通过 Java

  • 关于Java Guava ImmutableMap不可变集合源码分析

    目录 Java Guava不可变集合ImmutableMap的源码分析 一.案例场景 二.ImmutableMap源码分析 Java Guava不可变集合ImmutableMap的源码分析 一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String> dayMap= new HashMap<>(); static { dayMap.put("

  • java 中modCount 详解及源码分析

    modCount到底是干什么的呢 在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中我们总能看到modCount的身影,modCount字面意思就是修改次数,但为什么要记录modCount的修改次数呢? 大家发现一个公共特点没有,所有使用modCount属性的全是线程不安全的,这是为什么呢?说明这个玩意肯定和线程安全有关系喽,那有什么关系呢 阅读源码,发现这玩意只有在本数据结构对应迭代器中才使用,以HashMap为例: private abstract clas

  • Java HashSet添加 遍历元素源码分析

    目录 HashSet 类图 HashSet 简单说明 HashSet 底层机制说明 模拟数组+链表的结构 HashSet 添加元素底层机制 HashSet 添加元素的底层实现 HashSet 扩容机制 HashSet 添加元素源码 HashSet 遍历元素底层机制 HashSet 遍历元素底层机制 HashSet 遍历元素源码 HashSet 类图 HashSet 简单说明 1.HashSet 实现了 Set 接口 2.HashSet 底层实际上是由 HashMap 实现的 public Has

  • java 源码分析Arrays.asList方法详解

    最近,抽空把java Arrays 工具类的asList 方法做了源码分析,在网上整理了相关资料,记录下来,希望也能帮助读者! Arrays工具类提供了一个方法asList, 使用该方法可以将一个变长参数或者数组转换成List . 其源代码如下: /** * Returns a fixed-size list backed by the specified array. (Changes to * the returned list "write through" to the arr

  • Java BufferedWriter BufferedReader 源码分析

    一:BufferedWriter 1.类功能简介: BufferedWriter.缓存字符输出流.他的功能是为传入的底层字符输出流提供缓存功能.同样当使用底层字符输出流向目的地中写入字符或者字符数组时.每写入一次就要打开一次到目的地的连接.这样频繁的访问不断效率底下.也有可能会对存储介质造成一定的破坏.比如当我们向磁盘中不断的写入字节时.夸张一点.将一个非常大单位是G的字节数据写入到磁盘的指定文件中的.没写入一个字节就要打开一次到这个磁盘的通道.这个结果无疑是恐怖的.而当我们使用Buffered

随机推荐