天天看点

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

接上一节的内容。上一节,使用简单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处理等)

@Override
public 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
{

	@Override
	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception e)
	{}

	@Override
	public 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();
	
	@Override
	protected 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);
	}

	@Override
	public 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);
		}
	}

	@Override
	public 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());
		}
	}

	@Override
	protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception
	{
		super.handlePongMessage(session, message);
		logger.info("pongmsg");
	}

	@Override
	public 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()。让数据修改后立即刷新到页面上。

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

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

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

点我下载源码

继续阅读