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