欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring整合WebSocket应用示例(上)

程序员文章站 2024-03-11 12:51:10
以下教程是小编在参与开发公司的一个crm系统,整理些相关资料,在该系统中有很多消息推送功能,在其中用到了websocket技术。下面小编整理分享到萬仟网平台供大家参考 1...

以下教程是小编在参与开发公司的一个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协议来广播消息到所有感兴趣的客户端或指定用户。