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的配置
<?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="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--> <beanid="websocket"class="cn.bridgeli.websocket.WebsocketEndPoint"/> <websocket:handlers> <websocket:mappingpath="/websocket"handler="websocket"/> <websocket:handshake-interceptors> <beanclass="cn.bridgeli.websocket.HandshakeInterceptor"/> </websocket:handshake-interceptors> </websocket:handlers> </beans>
其中,path对应的路径就是前段通过ws协议调的接口路径
3.HandshakeInterceptor的实现
packagecn.bridgeli.websocket; importcn.bridgeli.utils.UserManager; importcn.bridgeli.util.DateUtil; importcn.bridgeli.sharesession.UserInfo; importorg.apache.commons.lang.StringUtils; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.http.server.ServerHttpRequest; importorg.springframework.http.server.ServerHttpResponse; importorg.springframework.web.context.request.RequestContextHolder; importorg.springframework.web.context.request.ServletRequestAttributes; importorg.springframework.web.socket.WebSocketHandler; importorg.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; importjava.util.Date; importjava.util.Map; /** *@Description:创建握手(handshake)接口 *@Date:16-3-3 */ publicclassHandshakeInterceptorextendsHttpSessionHandshakeInterceptor{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(HandshakeInterceptor.class); @Override publicbooleanbeforeHandshake(ServerHttpRequestrequest, ServerHttpResponseresponse,WebSocketHandlerwsHandler, Map<String,Object>attributes)throwsException{ logger.info("建立握手前..."); ServletRequestAttributesattrs=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); UserInfocurrUser=UserManager.getSessionUser(attrs.getRequest()); UserSocketVouserSocketVo=newUserSocketVo(); Stringemail=""; if(null!=currUser){ email=currUser.getEmail(); } if(StringUtils.isBlank(email)){ email=DateUtil.date2String(newDate()); } userSocketVo.setUserEmail(email); attributes.put("SESSION_USER",userSocketVo); returnsuper.beforeHandshake(request,response,wsHandler,attributes); } @Override publicvoidafterHandshake(ServerHttpRequestrequest, ServerHttpResponseresponse,WebSocketHandlerwsHandler, Exceptionex){ logger.info("建立握手后..."); super.afterHandshake(request,response,wsHandler,ex); } }
因为老夫不是很懂,所以最大限度的保留原代码,这其实就是从单点登录中取出当前登录用户,转成UserSocketVo对象,放到Map中。所以接下来我们看看UserSocketVo对象的定义
4.UserSocketVo的定义
packagecn.bridgeli.websocket; importorg.springframework.web.socket.WebSocketSession; importjava.util.Date; /** *@Description:用户socket连接实体 *@Date:16-3-7 */ publicclassUserSocketVo{ privateStringuserEmail;//用户邮箱 privateDateconnectionTime;//成功连接时间 privateDatepreRequestTime;//上次请求时间 privateDatenewRequestTime;//新请求时间 privateDatelastSendTime=newDate();//下架消息最近一次发送时间 privateDatelastTaskSendTime=newDate();//待处理任务最近一次发送时间 privateWebSocketSessionwebSocketSession;//用户对应的wsSession默认仅缓存一个 //getXXandsetXX }
其中最重要的就是这个WebSocketSession这个属性了,后面我们要用到
5.WebsocketEndPoint的实现
packagecn.bridgeli.websocket; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.socket.CloseStatus; importorg.springframework.web.socket.TextMessage; importorg.springframework.web.socket.WebSocketSession; importorg.springframework.web.socket.handler.TextWebSocketHandler; /** *@Description:websocket处理类 *@Date:16-3-3 */ publicclassWebsocketEndPointextendsTextWebSocketHandler{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(WebsocketEndPoint.class); @Autowired privateNewsListenerImplnewsListener; @Override protectedvoidhandleTextMessage(WebSocketSessionsession, TextMessagemessage)throwsException{ super.handleTextMessage(session,message); TextMessagereturnMessage=newTextMessage(message.getPayload()+"receivedatserver"); session.sendMessage(returnMessage); } /** *@Description:建立连接后 *@paramsession *@throwsException */ @Override publicvoidafterConnectionEstablished(WebSocketSessionsession)throwsException{ UserSocketVouserSocketVo=(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 publicvoidafterConnectionClosed(WebSocketSessionsession,CloseStatusstatus)throwsException{ UserSocketVouserSocketVo=(UserSocketVo)session.getAttributes().get("SESSION_USER"); if(null!=userSocketVo){ WSSessionLocalCache.remove(userSocketVo.getUserEmail()); } logger.info("socket成功关闭连接..."); super.afterConnectionClosed(session,status); } }
6.WSSessionLocalCache的实现
packagecn.bridgeli.websocket; importjava.io.Serializable; importjava.util.ArrayList; importjava.util.HashMap; importjava.util.List; importjava.util.Map; /** *@Description:本地缓存WebSocketSession实例 *@Date:16-3-7 */ publicclassWSSessionLocalCacheimplementsSerializable{ privatestaticMap<String,UserSocketVo>wsSessionCache=newHashMap<>(); publicstaticbooleanexists(StringuserEmail){ if(!wsSessionCache.containsKey(userEmail)){ returnfalse; }else{ returntrue; } } publicstaticvoidput(StringuserEmail,UserSocketVoUserSocketVo){ wsSessionCache.put(userEmail,UserSocketVo); } publicstaticUserSocketVoget(StringuserEmail){ returnwsSessionCache.get(userEmail); } publicstaticvoidremove(StringuserEmail){ wsSessionCache.remove(userEmail); } publicstaticList<UserSocketVo>getAllSessions(){ returnnewArrayList<>(wsSessionCache.values()); } }
看了其实现,作用就比较明显了吧,存放每个UserSocketVo的最新数据,其实到这里我们websocket的实现已经算完了,但还有一个核心类(关于业务逻辑查理的类)没有实现,下篇Spring整合WebSocket应用示例(下),我们就看怎么实现这个类。
WebSocket协议介绍
WebSocket协议是RFC-6455规范定义的一个Web领域的重要的功能:全双工,即客户端和服务器之间的双向通信。它是一个令人兴奋的功能,业界在此领域上已经探索很久,使用的技术包括JavaApplet、XMLHttpRequest、AdobeFlash、ActiveXObject、各种Comet技术、服务器端的发送事件等。
需要理解一点,在使用WebSocket协议前,需要先使用HTTP协议用于构建最初的握手。这依赖于一个机制——建立HTTP,请求协议升级(或叫协议转换)。当服务器同意后,它会响应HTTP状态码101,表示同意切换协议。假设通过TCP套接字成功握手,HTTP协议升级请求通过,那么客户端和服务器端都可以彼此互发消息。
Spring框架4.0以上版本引入了一个新模块,即spring-websocket模块。它对WebSocket通信提供了支持。它兼容JavaWebSocketAPI规范JSR-356,同时提供了额外的功能。
什么场景下该使用WebSocket
在Web应用中,客户端和服务器端需要以较高频率和较低延迟来交换事件时,适合用WebSocket。因此WebSocket适合财经、游戏、协作等应用场景。
对于其他应用场景则未必适合。例如,某个新闻订阅需要显示突发新闻,使用间隔几分钟的长轮询也是可以的,这里的延迟可以接受。
即使在要求低延迟的应用场景,如果传输的消息数很低(比如监测网络故障的场景),那么应该考虑使用长轮询技术。
而只有在低延迟和高频消息通信的场景下,选用WebSocket协议才是非常适合的。即使是这样的应用场景,仍然存在是选择WebSocket通信呢?又或者是选择RESTHTTP通信呢?
答案是会根据应用程序的需求而定。但是,也可能同时使用这两种技术,把需要频繁交换的数据放到WebSocket中实现,而把RESTAPI作为过程性的业务的实现技术。另外,当RESTAPI的调用中需要把某个信息广播给多个客户端是,也可以通过WebSocket连接来实现。
Spring框架提供了@Controller注释和@RestController注释,两者都可以用于HTTP请求的处理以及WebSocket消息的处理。另外,SpringMVC的请求处理方法,或其它应用程序的请求处理方法,都可以很容易地使用WebSocket协议来广播消息到所有感兴趣的客户端或指定用户。