功能:
一个页面需要在不同的PC端访问,在某一PC端对网页内容发生改变时,其他PC端页面数据实时更新显示.
实现:
采用webSocket+AOP通知的方式实现
思路:
当页面数据修改时,会通过后端保存方法存进数据库,这样我们就要一个入口,当数据保存方法被调用执行完后(AOP后置通知),触发webSocket消息机制,向前端发送更新提示,前端调用更新方法进行页面更新.
实现过程:
在网上有很多关于webSocket的实现和总结,这里我就不一一叙述,大家有兴趣可以在网上查阅,但是大致实现方式我总结有三种;
第一种:原生实现方式
第二种:spring管理的方式
第三种:springBoot管理的方式
我采用的是第三种,大家可以根据自己项目的结构和框架来选择使用哪种方式,但是原理都是一样的,只是代码的写法可能略有不同而已.
代码:
前端:
function webSocket() { var websocket = null; //判断当前浏览器是否支持WebSocket if (typeof(WebSocket) == "undefined") { alert('当前浏览器 Not support websocket'); } else { //建立连接,这里的/websocket ,是Servlet中注解中的那个值 console.log("support websocket"); var id =ground.userID; websocket = new WebSocket("ws://" + window.location.host + "/项目名/websocket/"+ id); } //连接发生错误的回调方法 websocket.onerror = function () { console.log("WebSocket连接发生错误"); }; //连接成功建立的回调方法 websocket.onopen = function () { console.log("WebSocket连接成功"); } //接收到消息的回调方法 websocket.onmessage = function () { console.log("数据更新啦"); loadfg(); } //连接关闭的回调方法 websocket.onclose = function () { console.log("WebSocket连接关闭"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭WebSocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { websocket.close(); } }
后端:核心类WebSocketServer用于连接前端,完成前后端通信
@Slf4j @ServerEndpoint(value = "/websocket/{id}", configurator = MyEndpointConfigure.class) @Controller public class WebSocketServer { private Map<String, Session> map; // 与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; private String id; /** * @param session websocket连接sesson * @DESC <p>注解{@link OnOpen} 声明客户端连接进入的方法</p> */ @OnOpen public void onOpen(@PathParam("id") String id, Session session) { log.debug("连接成功"); this.session = session; this.id = id; log.debug(this.id); map = WebSet.getInstance(); // 将连接session对象存入map map.put(this.id, session); } /** * <p>{@link OnClose} 关闭连接</p> */ @OnClose public void onClose() { log.debug("连接关闭---关闭id" + this.id); } /** * <p>{@link OnMessage} 消息监听处理方法</p> * * @throws IOException 异常 */ @OnMessage public void onMessage(String message) { log.debug(message); } /** * <p>{@link OnError} websocket系统异常处理</p> * * @param t 异常 */ @OnError public void onError(Throwable t) { log.error(t + "连接异常"); t.printStackTrace(); } public void send(String date) { log.debug("开始发送消息"); try { Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); if (map.get(key).isOpen()) { if (key.equals(date)) { map.get(key).getBasicRemote().sendText(JSON.toJSONString(ids[0])); log.debug("消息发送"); } } else { log.debug("已经断开连接的有..." + key); //删除断开的连接 iter.remove(); } } } catch (Exception e) { e.printStackTrace(); } } }
这个方法里面有的做法和网上的案例有一些细微的不同,因为我在测试的时候发现采用网上的关闭页面调用页面关闭触发方法,在方法内删除当前id的方式在实际中会有问题,因为他删除的总是最后一个访问websocket的页面的session,所以导致要么最后一个页面收不到消息,要么第二个人关闭页面时后台会报错,所以我在页面关闭触发方法里面没有做session删除的处理,而是在发送消息时做了isOpen()判断,为true时就是session处于连接状态,为false时就是处于断开状态,而在false时我做了删除session处理,将断开的session删除.这样就可以保证不会将处于连接状态的session删除
后端:MyEndpointConfigure这个类我没有太多去研究,也是网上拷的,但我个人理解的话这个类应该是用来生成webSocket对象的,如果我的理解有问题,各位大牛一定一定要私信我,拜谢!!
public class MyEndpointConfigure extends ServerEndpointConfig.Configurator implements ApplicationContextAware { private static volatile BeanFactory context; @Override public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException { return context.getBean(clazz); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { MyEndpointConfigure.context = applicationContext; } }
后端:WebSocketConfig类,这个类就很重要了,这个类是用来将webSocket注入到spring容器中的,如果不使用这个类的话,是没用办法在webSocket类中去使用@Autowired注解应用对象的
@Configuration public class WebSocketConfig { private MyEndpointConfigure myEndpointConfigure; @Bean public MyEndpointConfigure newConfigure() { return new MyEndpointConfigure(); } }
后端:AOP类,这个类就是一个AOP后置通知,具体的注解或者aop的通知机制,不清楚的小伙伴,可以去找相关资料回顾一下
@Aspect @Component @Slf4j public class AopLogging { @Autowired private WebSocketServer webSocketServer; @After("execution(*com.it.web.bs.t.i.controller.InspectorController.save(..))") public void after(){ log.debug("后置通知进入"); webSocketServer.onMessage("1"); } }
* 代表该方法为所有类型和所有返回值类型,括号里的..表示该方法的所有类型参数
意思就是只要在这个包下的这个类里面的所有的save方法,不管返回值类型和参数类型,只要执行这个类里面名为save的方法后置通知方法就会被触发,当然如果这个类里面有很多重载的save方法,而我们只需要其中某个方法去触发后置通知,就不能用*和..表示了
个人项目总结:
在实现功能期间,遇到过很多问题,首先是前后端连接不通,一定要注意加上项目名称,并且保证项目名称没有问题,还有就是连接地址没有问题大小写_和-等等,都是细心的问题;
遇到的最大的问题就是只有一个PC端页面能接收到更新的提示而其他页面没有反应,通过断点,查看日志等方式,发现同一个页面在不同浏览器和PC端打开,连接后端生成的webSocket都是同一个对象,所以在存储进集合(之前用的set)中时后面一个连接的对象会将前面一个对象覆盖掉(因为set的不可重复的),后来改用map集合,当前时间为键,webSocket对象为值,遍历集合才发现这一个问题,一直苦苦思索解决办法,也问了很多人,但是一直没有结果,后来研究代码发现,最终往前端发送消息是用webSocket中的session来完成的,突然灵光一闪,虽然webSocket是相同的,但是session肯定是不一样的(这个原因就不阐述了,相信大家都知道),既然最后的是通过session通信的,所以我就将之前的往集合里面添加webSocket对象改为往集合中添加当前Session对象,由于业务需求,就将用户名设置为了key.
结束语:
我也是第一次使用webSocket,如果有问题的地方大家可以以前研究探讨,如果我上述说的有问题或大家遇到什么问题,请私信我,拜谢!!