JavaEE实现WebSocket(二)使用SpringMvc和AngularJS

来源:互联网 发布:mac zookeeper 客户端 编辑:程序博客网 时间:2024/06/06 14:04

接上一节的内容。上一节,使用简单JavaEE的注解方法,创建了一个基于Websocket的简易聊天室。在实际开发中,可以直接使用上节的方法。当然,有童鞋会问了,如果我是基于框架开发的,那怎么办?

下面呢,我们一步一步来,构造一个整合了SpringMVC和AngularJS的另一个聊天室吧。

这里,笔者使用的是Spring4.2.3以及AngularJS 1.5.8。

环境的搭建这里不详细描述了,可以在文章的最后下载源码,导入到Eclipse仔细研究。

【服务器端】

这里研究的是SpringMVC框架下的WebSocket服务器的构建方法。从Spring4开始,提供了WebSocket模块的支持。如此,我们可以使用SpringMVC加入WebSocket的支持。

本例使用Spring的注解配置方法,代码如下:


@Configuration@EnableWebMvc@EnableWebSocket@ComponentScan(        basePackages = "lvhb.test.controller",        useDefaultFilters = false,        includeFilters = @ComponentScan.Filter(Controller.class))public class WebServletContextConfiguration extends WebMvcConfigurerAdapter implements WebSocketConfigurer

这里,新加入了一个注解——@EnableWebSocket(没加也没啥大碍),并且实现了WebSocketConfigurer。这里需实现一个注册方法如下:

返回类型

方法和描述

void

registerWebSocketHandlers(WebSocketHandlerRegistry registry)

注册一个WebSocket处理器

本例中,我们注册一个WebSocketHandler,并且加入一个拦截器。(为什么要加拦截器?在WebSocket握手完成前,处理一些必要的准备工作,如:参数的获取,session处理等)

@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry){    registry.addHandler(wsHandler(), "/chat").addInterceptors(new HandInterceptor());}

registerWebSocketHandlers方法中,使用addHandler加入了WebSocket处理器。这里wsHandler()方法实际上是new了一个处理器对象。之后,添加了拦截器,拦截器的代码如下:

/** * WebSocket的一个拦截器 * 此处,在握手前,获取请求中的nickname参数,并将其写入session的Attribute中,方便WsHandler处理session * @author lvhb * */public class HandInterceptor implements HandshakeInterceptor{@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception e){}@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler,Map<String, Object> map) throws Exception{if (request instanceof ServletServerHttpRequest) {            HttpServletRequest req = ((ServletServerHttpRequest) request).getServletRequest();            String userName = req.getParameter("nickname");            map.put("WEBSOCKET_USERNAME", userName);            req.getSession().setAttribute("WEBSOCKET_USERNAME", userName);        }        return true;}}

拦截器要实现HandshakeInterceptor接口,这里提供了两个方法,一个是在握手前的处理,另一个是在握手后的处理。本例中,我们使用握手前的处理,将request请求转为HttpServletRequest,并从中获取到客户端发送过来的用户昵称。之后,我们将用户名加入session的Attribute内。

之后,我们看重头戏,处理器类的实现,先上代码:

/** * WebSocket 处理器 * @author lvhb * */public class WsHandler extends TextWebSocketHandler{private static final Logger logger = LogManager.getLogger("WsHandler");/** * 用于记录当前连接的客户端Session */private static Map<String, WebSocketSession> clients;/** * 用于将JSON映射成对象 */private static ObjectMapper mapper = new ObjectMapper();@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception{super.handleTextMessage(session, message);logger.info("onmessage:"+message.getPayload());//使用ObjectMapper将获取到的json数据转换成对象ChatMsgModel msgModel = WsHandler.mapper.readValue(message.getPayload(), ChatMsgModel.class);UserMessage uMsg = new UserMessage(msgModel.getNickname(), msgModel.getText(), LocalDateTime.now());broadcastMsg(uMsg);}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception{super.afterConnectionEstablished(session);if(clients == null)clients = new HashMap<String, WebSocketSession>();Map<String, Object> attrs = session.getAttributes();if(attrs != null){String nickname = attrs.get("WEBSOCKET_USERNAME").toString();clients.put(nickname, session);SystemMessage sysMsg = new SystemMessage((nickname.equals("") ? "有人":nickname) + "进入了聊天室", LocalDateTime.now());broadcastMsg(sysMsg);}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception{super.afterConnectionClosed(session, status);if(clients != null && !clients.isEmpty()){Map<String, Object> attrs = session.getAttributes();if(attrs != null){String nickname = attrs.get("WEBSOCKET_USERNAME").toString();if(clients.containsKey(nickname)){removeSession(nickname);logger.info("ws removed");SystemMessage sysMsg = new SystemMessage((nickname.equals("") ? "有人":nickname) + "离开了聊天室", LocalDateTime.now());broadcastMsg(sysMsg);}}}}private synchronized void removeSession(String nickname){clients.remove(nickname);}/** * 广播消息 * @param msg */private void broadcastMsg(Message msg){if(clients == null || clients.isEmpty())return;try{for(Map.Entry<String, WebSocketSession> entry:clients.entrySet()){WebSocketSession session = entry.getValue();if(session.isOpen())session.sendMessage(msg.parseToTextMessage());}}catch(IOException ioe){logger.error(ioe.getMessage());}}@Overrideprotected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception{super.handlePongMessage(session, message);logger.info("pongmsg");}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception{super.handleTransportError(session, exception);logger.info("errormsg:"+exception.getMessage());}}

这里WsHandler类继承自TextWebSocketHandler(Spring推荐的方法,用于处理文本类型的数据处理。当然,您也可以实现WebSocketHandler接口)。

我们重写handleTextMessage、afterConnectionEstablished及afterConnectionClosed方法。对应于WebSocket的OnMessage、OnOpen、OnClose。这里具体聊天室业务逻辑处理不做过多介绍。

在每个重写的方法中,都有WebSocketSession对象传入,这个是客户端与服务器端的会话实例。当我们想给客户端发送内容时,可以使用sendMessage方法,将一个实现了WebSocketMessage的对象发送给客户端。当然,这里推荐使用TextMessage的对象。

服务器的内容就介绍到这里。下面简单说一下Angular部分的内容。

【客户端】

Angular部分的内容和上一节的JavaScript操作WebSocket类似。核心代码如下:

/** =============== WebSocket处理 ===================*/try {$scope.server = new WebSocket(WSURL+"?nickname="+$scope.nickname);} catch(error) {console.log("ws connection error");}        $scope.server.onopen = function(event){        console.log("ws onopen");}$scope.server.onmessage = function(event){console.log("ws onmessage");var message = event.data;appendLine(message);}$scope.server.onclose = function(event){console.log("ws onclose");}$scope.server.onerror = function(event){console.log("ws onerror");}/** ================= WebSocket =====================*/

这里注意WebSocket在创建的时候如HTTP连接一样,可以带参数的。所以这里传入了nickname表示聊天人的昵称。之后,在处理onmessage时,appendLine方法为将接受到的数据加入到聊天界面中。

function appendLine(text){if(angular.isDefined(text)){$scope.$apply($scope.chatData.push(text));}}

这里注意,笔者使用了$scope.$apply()。让数据修改后立即刷新到页面上。

这个例子实现的截图如下。


好,这一节就到这结束了。大家可以下载源码,运行一下,就会明白SpringMVC的WebSocket如何实现。

点我下载源码

原创粉丝点击