java使用websocket,并且获取HttpSession 源码分析(推荐)
一:本文使用范围
此文不仅仅局限于springboot,普通的spring工程,甚至是servlet工程,都是一样的,只不过配置一些监听器的方法不同而已。
本文经过作者实践,确认完美运行。
二:Springboot使用websocket
2.1:依赖包
websocket本身是servlet容器所提供的服务,所以需要在web容器中运行,像我们所使用的tomcat,当然,springboot中已经内嵌了tomcat。
websocket遵循了javaee规范,所以需要引入javaee的包
javax javaee-api 7.0 provided
当然,其实tomcat中已经自带了这个包。
如果是在springboot中,还需要加入websocket的starter
org.springframework.boot spring-boot-starter-websocket 1.4.0.RELEASE
2.2:配置websocket
如果不是springboot项目,那就不需要进行这样的配置,因为如果在tomcat中运行的话,tomcat会扫描带有@ServerEndpoint的注解成为websocket,而springboot项目中需要由这个bean来提供注册管理。
@Configuration publicclassWebSocketConfig{ @Bean publicServerEndpointExporterserverEndpointExporter(){ returnnewServerEndpointExporter(); } }
2.3:websocket的java代码
使用websocket的核心,就是一系列的websocket注解,@ServerEndpoint是注册在类上面开启。
@ServerEndpoint(value="/websocket") @Component publicclassMyWebSocket{ //与某个客户端的连接会话,需要通过它来给客户端发送数据 privateSessionsession; /** *连接成功*/ @OnOpen publicvoidonOpen(Sessionsession){ this.session=session; } /** *连接关闭调用的方法 */ @OnClose publicvoidonClose(){ } /** *收到消息 * *@parammessage */ @OnMessage publicvoidonMessage(Stringmessage,Sessionsession){ System.out.println("来自浏览器的消息:"+message); //群发消息 for(MyWebSocketitem:webSocketSet){ try{ item.sendMessage(message); }catch(IOExceptione){ e.printStackTrace(); } } } /** *发生错误时调用 */ @OnError publicvoidonError(Sessionsession,Throwableerror){ System.out.println("发生错误"); error.printStackTrace(); } publicvoidsendMessage(Stringmessage)throwsIOException{ this.session.getBasicRemote().sendText(message);//同步 //this.session.getAsyncRemote().sendText(message);//异步 } }
其实我也感觉很奇怪,为什么不使用接口来规范。即使是因为@ServerEndpoint注解中其它属性中可以定义出一些额外的参数,但相信也是可以抽象出来的,不过想必javaee这样做,应该是有它的用意吧。
2.4:浏览器端的代码
浏览器端的代码需要浏览器支持websocket,当然,也有socket.js可以支持到ie7,但是这个我没用过。毕竟ie基本上没人用的,市面上的浏览器基本上全部都支持websocket。
varwebsocket=null; //判断当前浏览器是否支持WebSocket if('WebSocket'inwindow){ websocket=newWebSocket("ws://localhost:9999/websocket"); } else{ alert('不支持websocket') } //连接发生错误 websocket.onerror=function(){ }; //连接成功 websocket.onopen=function(event){ } //接收到消息 websocket.onmessage=function(event){ varmsg=event.data; alert("收到消息:"+msg); } //连接关闭 websocket.onclose=function(){ } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload=function(){ websocket.close(); } //发送消息 functionsend(message){ websocket.send(message); }
如此就连接成功了。
三:获取HttpSession,源码分析
获取HttpSession是一个很有必要讨论的问题,因为java后台需要知道当前是哪个用户,用以处理该用户的业务逻辑,或者是对该用户进行授权之类的,但是由于websocket的协议与Http协议是不同的,所以造成了无法直接拿到session。但是问题总是要解决的,不然这个websocket协议所用的场景也就没了。
3.1:获取HttpSession的工具类,源码详细分析
我们先来看一下@ServerEndpoint注解的源码
packagejavax.websocket.server; importjava.lang.annotation.ElementType; importjava.lang.annotation.Retention; importjava.lang.annotation.RetentionPolicy; importjava.lang.annotation.Target; importjavax.websocket.Decoder; importjavax.websocket.Encoder; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public@interfaceServerEndpoint{ /** *URIorURI-templatethattheannotatedclassshouldbemappedto. *@returnTheURIorURI-templatethattheannotatedclassshouldbemapped *to. */ Stringvalue(); String[]subprotocols()default{}; Class[]decoders()default{}; Class[]encoders()default{}; publicClassconfigurator() defaultServerEndpointConfig.Configurator.class; }
我们看到最后的一个方法,也就是加粗的方法。可以看到,它要求返回一个ServerEndpointConfig.Configurator的子类,我们写一个类去继承它。
importjavax.servlet.http.HttpSession; importjavax.websocket.HandshakeResponse; importjavax.websocket.server.HandshakeRequest; importjavax.websocket.server.ServerEndpointConfig; importjavax.websocket.server.ServerEndpointConfig.Configurator; publicclassHttpSessionConfiguratorextendsConfigurator{ @Override publicvoidmodifyHandshake(ServerEndpointConfigsec,HandshakeRequestrequest,HandshakeResponseresponse){ //怎么搞? } }
当我们覆盖modifyHandshake方法时,可以看到三个参数,其中后面两个参数让我们感觉有点见过的感觉,我们查看一HandshakeRequest的源码
packagejavax.websocket.server; importjava.net.URI; importjava.security.Principal; importjava.util.List; importjava.util.Map; /** *RepresentstheHTTPrequestthataskedtobeupgradedtoWebSocket. */ publicinterfaceHandshakeRequest{ staticfinalStringSEC_WEBSOCKET_KEY="Sec-WebSocket-Key"; staticfinalStringSEC_WEBSOCKET_PROTOCOL="Sec-WebSocket-Protocol"; staticfinalStringSEC_WEBSOCKET_VERSION="Sec-WebSocket-Version"; staticfinalStringSEC_WEBSOCKET_EXTENSIONS="Sec-WebSocket-Extensions"; Map>getHeaders(); PrincipalgetUserPrincipal(); URIgetRequestURI(); booleanisUserInRole(Stringrole); /** *GettheHTTPSessionobjectassociatedwiththisrequest.Objectisused *toavoidadirectdependencyontheServletAPI. *@returnThejavax.servlet.http.HttpSessionobjectassociatedwiththis *request,ifany. */ ObjectgetHttpSession(); Map >getParameterMap(); StringgetQueryString(); }
我们发现它是一个接口,接口中规范了这样的一个方法
/** *GettheHTTPSessionobjectassociatedwiththisrequest.Objectisused *toavoidadirectdependencyontheServletAPI. *@returnThejavax.servlet.http.HttpSessionobjectassociatedwiththis *request,ifany. */ ObjectgetHttpSession();
上面有相应的注释,说明可以从ServletAPI中获取到相应的HttpSession。
当我们发现这个方法的时候,其实已经松了一口气了。
那么我们就可以补全未完成的代码
importjavax.servlet.http.HttpSession; importjavax.websocket.HandshakeResponse; importjavax.websocket.server.HandshakeRequest; importjavax.websocket.server.ServerEndpointConfig; importjavax.websocket.server.ServerEndpointConfig.Configurator; /** *从websocket中获取用户session * * */ publicclassHttpSessionConfiguratorextendsConfigurator{ @Override publicvoidmodifyHandshake(ServerEndpointConfigsec,HandshakeRequestrequest,HandshakeResponseresponse){ HttpSessionhttpSession=(HttpSession)request.getHttpSession(); sec.getUserProperties().put(HttpSession.class.getName(),httpSession); } }
其实通过上面的源码分析,你们应该知道了HttpSession的获取。但是下面又多了一行代码
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
这行代码又是什么意思呢?
我们看一下ServerEnpointConfig的声明
publicinterfaceServerEndpointConfigextendsEndpointConfig
我们发现这个接口继承了EndpointConfig的接口,好,我们看一下EndpointConfig的源码:
packagejavax.websocket; importjava.util.List; importjava.util.Map; publicinterfaceEndpointConfig{ List>getEncoders(); List >getDecoders(); Map getUserProperties(); }
我们发现了这样的一个方法定义
Map
可以看到,它是一个map,从方法名也可以理解到,它是用户的一些属性的存储,那既然它提供了get方法,那么也就意味着我们可以拿到这个map,并且对这里面的值进行操作,
所以就有了上面的
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
那么到此,获取HttpSession的源码分析,就完成了。
3.2:设置HttpSession的类
我们之前有说过,由于HTTP协议与websocket协议的不同,导致没法直接从websocket中获取协议,然后在3.1中我们已经写了获取HttpSession的代码,但是如果真的放出去执行,那么会报空指值异常,因为这个HttpSession并没有设置进去。
好,这一步,我们来设置HttpSession。这时候我们需要写一个监听器。
importjavax.servlet.ServletRequestEvent; importjavax.servlet.ServletRequestListener; importjavax.servlet.http.HttpServletRequest; importorg.springframework.stereotype.Component; @Component publicclassRequestListenerimplementsServletRequestListener{ publicvoidrequestInitialized(ServletRequestEventsre){ //将所有request请求都携带上httpSession ((HttpServletRequest)sre.getServletRequest()).getSession(); } publicRequestListener(){ } publicvoidrequestDestroyed(ServletRequestEventarg0){ } }
然后我们需要把这个类注册为监听器,如果是普通的Spring工程,或者是servlet工程,那么要么在web.xml中配置,要么使用@WebListener注解。
因为本文是以Springboot工程来演示,所以这里只写Springboot配置Listener的代码,其它的配置方式,请自行百度。
这是使用@Bean注解的方式
@Autowird privateRequestListenerrequestListener; @Bean publicServletListenerRegistrationBeanservletListenerRegistrationBean(){ ServletListenerRegistrationBean servletListenerRegistrationBean=newServletListenerRegistrationBean<>(); servletListenerRegistrationBean.setListener(requestListener); returnservletListenerRegistrationBean; }
或者也可以使用@WebListener注解
然后使用@ServletComponentScan注解,配置在启动方法上面。
3.3:在websocket中获取用户的session
然后刚才我们通过源码分析,是知道@ServerEndpoint注解中是有一个参数可以配置我们刚才继承的类。好的,我们现在进行配置。
@ServerEndpoint(value="/websocket",configurator=HttpSessionConfigurator.class)
接下来就可以在@OnOpen注解中所修饰的方法中,拿到EndpointConfig对象,并且通过这个对象,拿到之前我们设置进去的map
@OnOpen publicvoidonOpen(Sessionsession,EndpointConfigconfig){ HttpSessionhttpSession=(HttpSession)config.getUserProperties().get(HttpSession.class.getName()); Useruser=(User)httpSession.getAttribute(SessionName.USER); if(user!=null){ this.session=session; this.httpSession=httpSession; }else{ //用户未登陆 try{ session.close(); }catch(IOExceptione){ e.printStackTrace(); } } }
这下我们就从java的webscoket中拿到了用户的session。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助~如果有疑问大家可以留言交流,谢谢大家对毛票票的支持!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。