Spring整合WebSocket应用示例(上)
以下教程是小编在参与开发公司的一个crm系统,整理些相关资料,在该系统中有很多消息推送功能,在其中用到了websocket技术。下面小编整理分享到平台供大家参考
1. maven依赖
<dependency> <groupid>javax.servlet</groupid> <artifactid>javax.servlet-api</artifactid> <version>3.1.0</version> </dependency> <dependency> <groupid>com.fasterxml.jackson.core</groupid> <artifactid>jackson-core</artifactid> <version>2.3.0</version> </dependency> <dependency> <groupid>com.fasterxml.jackson.core</groupid> <artifactid>jackson-databind</artifactid> <version>2.3.0</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-websocket</artifactid> <version>4.0.1.release</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-messaging</artifactid> <version>4.0.1.release</version> </dependency>
2. spring-servlet的配置
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:websocket="http://www.springframework.org/schema/websocket" xsi:schemalocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd"> ...... <!-- websocket --> <bean id="websocket" class="cn.bridgeli.websocket.websocketendpoint"/> <websocket:handlers> <websocket:mapping path="/websocket" handler="websocket"/> <websocket:handshake-interceptors> <bean class="cn.bridgeli.websocket.handshakeinterceptor"/> </websocket:handshake-interceptors> </websocket:handlers> </beans>
其中,path对应的路径就是前段通过ws协议调的接口路径
3. handshakeinterceptor的实现
package cn.bridgeli.websocket; import cn.bridgeli.utils.usermanager; import cn.bridgeli.util.dateutil; import cn.bridgeli.sharesession.userinfo; import org.apache.commons.lang.stringutils; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.http.server.serverhttprequest; import org.springframework.http.server.serverhttpresponse; import org.springframework.web.context.request.requestcontextholder; import org.springframework.web.context.request.servletrequestattributes; import org.springframework.web.socket.websockethandler; import org.springframework.web.socket.server.support.httpsessionhandshakeinterceptor; import java.util.date; import java.util.map; /** * @description :创建握手(handshake)接口 * @date : 16-3-3 */ public class handshakeinterceptor extends httpsessionhandshakeinterceptor{ private static final logger logger = loggerfactory.getlogger(handshakeinterceptor.class); @override public boolean beforehandshake(serverhttprequest request, serverhttpresponse response, websockethandler wshandler, map<string, object> attributes) throws exception { logger.info("建立握手前..."); servletrequestattributes attrs = (servletrequestattributes) requestcontextholder.getrequestattributes(); userinfo curruser = usermanager.getsessionuser(attrs.getrequest()); usersocketvo usersocketvo = new usersocketvo(); string email= ""; if(null != curruser){ email = curruser.getemail(); } if(stringutils.isblank(email)){ email = dateutil.date2string(new date()); } usersocketvo.setuseremail(email); attributes.put("session_user", usersocketvo); return super.beforehandshake(request, response, wshandler, attributes); } @override public void afterhandshake(serverhttprequest request, serverhttpresponse response, websockethandler wshandler, exception ex) { logger.info("建立握手后..."); super.afterhandshake(request, response, wshandler, ex); } }
因为老夫不是很懂,所以最大限度的保留原代码,这其实就是从单点登录中取出当前登录用户,转成usersocketvo对象,放到map中。所以接下来我们看看usersocketvo对象的定义
4. usersocketvo的定义
package cn.bridgeli.websocket; import org.springframework.web.socket.websocketsession; import java.util.date; /** * @description : 用户socket连接实体 * @date : 16-3-7 */ public class usersocketvo { private string useremail; //用户邮箱 private date connectiontime; //成功连接时间 private date prerequesttime; //上次请求时间 private date newrequesttime; //新请求时间 private date lastsendtime = new date(); //下架消息最近一次发送时间 private date lasttasksendtime = new date(); //待处理任务最近一次发送时间 private websocketsession websocketsession; //用户对应的wssession 默认仅缓存一个 // getxx and setxx }
其中最重要的就是这个websocketsession这个属性了,后面我们要用到
5. websocketendpoint的实现
package cn.bridgeli.websocket; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.autowired; import org.springframework.web.socket.closestatus; import org.springframework.web.socket.textmessage; import org.springframework.web.socket.websocketsession; import org.springframework.web.socket.handler.textwebsockethandler; /** * @description : websocket处理类 * @date : 16-3-3 */ public class websocketendpoint extends textwebsockethandler{ private static final logger logger = loggerfactory.getlogger(websocketendpoint.class); @autowired private newslistenerimpl newslistener; @override protected void handletextmessage(websocketsession session, textmessage message) throws exception { super.handletextmessage(session, message); textmessage returnmessage = new textmessage(message.getpayload()+" received at server"); session.sendmessage(returnmessage); } /** * @description : 建立连接后 * @param session * @throws exception */ @override public void afterconnectionestablished(websocketsession session) throws exception{ usersocketvo usersocketvo = (usersocketvo)session.getattributes().get("session_user"); if(null != usersocketvo){ usersocketvo.setwebsocketsession(session); if(wssessionlocalcache.exists(usersocketvo.getuseremail())){ wssessionlocalcache.remove(usersocketvo.getuseremail()); } wssessionlocalcache.put(usersocketvo.getuseremail(), usersocketvo); newslistener.afterconnectionestablished(usersocketvo.getuseremail()); } logger.info("socket成功建立连接..."); super.afterconnectionestablished(session); } @override public void afterconnectionclosed(websocketsession session,closestatus status) throws exception{ usersocketvo usersocketvo = (usersocketvo)session.getattributes().get("session_user"); if(null != usersocketvo){ wssessionlocalcache.remove(usersocketvo.getuseremail()); } logger.info("socket成功关闭连接..."); super.afterconnectionclosed(session, status); } }
6. wssessionlocalcache的实现
package cn.bridgeli.websocket; import java.io.serializable; import java.util.arraylist; import java.util.hashmap; import java.util.list; import java.util.map; /** * @description :本地缓存websocketsession实例 * @date : 16-3-7 */ public class wssessionlocalcache implements serializable { private static map<string, usersocketvo> wssessioncache = new hashmap<>(); public static boolean exists(string useremail){ if(!wssessioncache.containskey(useremail)){ return false; }else{ return true; } } public static void put(string useremail, usersocketvo usersocketvo){ wssessioncache.put(useremail, usersocketvo); } public static usersocketvo get(string useremail){ return wssessioncache.get(useremail); } public static void remove(string useremail){ wssessioncache.remove(useremail); } public static list<usersocketvo> getallsessions(){ return new arraylist<>(wssessioncache.values()); } }
看了其实现,作用就比较明显了吧,存放每个usersocketvo的最新数据,其实到这里我们websocket的实现已经算完了,但还有一个核心类(关于业务逻辑查理的类)没有实现,下篇spring整合websocket应用示例(下),我们就看怎么实现这个类。
websocket协议介绍
websocket协议是rfc-6455规范定义的一个web领域的重要的功能:全双工,即客户端和服务器之间的双向通信。它是一个令人兴奋的功能,业界在此领域上已经探索很久,使用的技术包括java applet、xmlhttprequest、adobe flash、activexobject、各种comet技术、服务器端的发送事件等。
需要理解一点,在使用websocket协议前,需要先使用http协议用于构建最初的握手。这依赖于一个机制——建立http,请求协议升级(或叫协议转换)。当服务器同意后,它会响应http状态码101,表示同意切换协议。假设通过tcp套接字成功握手,http协议升级请求通过,那么客户端和服务器端都可以彼此互发消息。
spring框架4.0以上版本引入了一个新模块,即spring-websocket模块。它对websocket通信提供了支持。它兼容java websocket api规范jsr-356,同时提供了额外的功能。
什么场景下该使用websocket
在web应用中,客户端和服务器端需要以较高频率和较低延迟来交换事件时,适合用websocket。因此websocket适合财经、游戏、协作等应用场景。
对于其他应用场景则未必适合。例如,某个新闻订阅需要显示突发新闻,使用间隔几分钟的长轮询也是可以的,这里的延迟可以接受。
即使在要求低延迟的应用场景,如果传输的消息数很低(比如监测网络故障的场景),那么应该考虑使用长轮询技术。
而只有在低延迟和高频消息通信的场景下,选用websocket协议才是非常适合的。即使是这样的应用场景,仍然存在是选择websocket通信呢?又或者是选择rest http通信呢?
答案是会根据应用程序的需求而定。但是,也可能同时使用这两种技术,把需要频繁交换的数据放到websocket中实现,而把rest api作为过程性的业务的实现技术。另外,当rest api的调用中需要把某个信息广播给多个客户端是,也可以通过websocket连接来实现。
spring框架提供了@controller注释和@restcontroller注释,两者都可以用于http请求的处理以及websocket消息的处理。另外,spring mvc的请求处理方法,或其它应用程序的请求处理方法,都可以很容易地使用websocket协议来广播消息到所有感兴趣的客户端或指定用户。
推荐阅读
-
Spring整合WebSocket应用示例(上)
-
Spring整合websocket整合应用示例(下)
-
java WebSocket的实现以及Spring WebSocket示例代码
-
Spring整合Quartz实现动态定时器的示例代码
-
详解WebSocket+spring示例demo(已使用sockJs库)
-
spring WebSocket示例详解
-
java WebSocket的实现以及Spring WebSocket示例代码
-
Spring整合Quartz实现动态定时器的示例代码
-
详解WebSocket+spring示例demo(已使用sockJs库)
-
spring 整合mybatis后用不上session缓存的原因分析