spring mvc整合spring websocket

一.背景

目前管理的一个应用系统中,原有的消息机制是通过ajax轮询来进行的,一方面效率不高,再一个消息产生和消费的时候,系统通知也会有延迟,造成用户体验并不是很好。基于这一背景,对应用系统的消息通知机制进行了改造,使用websocket来实时进行消息的通知。

spring和spring mvc环境的搭建就不讲了,这里主要讲怎样把spring websocket整合到spring mvc web工程中,并解决遇到的问题。

二. 获取spring websocket依赖包

这里,使用maven来进行依赖包的管理,只需要在pom.xml里添加以下依赖包配置信息即可:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
</dependency>

三. 服务端Websocket程序编写

编写MyWebSocketConfig实现WebSocketConfigurer接口:

package cn.xxx.rmwlgzpt.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;

/**
 * websocket配置
 * 
 * @author huangyuanmu

 * @date 2018年6月13日 下午3:35:35
 * @version 1.0
 */
@Configuration
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig implements WebSocketConfigurer {
	
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
	registry.addHandler(webSocketHandler(),"/websocket").addInterceptors(new MyWebSocketHandlerInterceptor());
        registry.addHandler(webSocketHandler(), "/sockjs").addInterceptors(new MyWebSocketHandlerInterceptor()).withSockJS();
    }

    @Bean
    public WebSocketHandler webSocketHandler() {
        return new MyWebSocketHandler();
    }
    
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }
}

因为并不是所有的浏览器都实现了websocket,所以这里也设置拦截器,对socketjs进行支持。

编写MyWebSocketHandlerInterceptor拦截器,处理websocket session相关事项:

package cn.xxx.rmwlgzpt.websocket;

import java.util.Map;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import cn.xxx.util.common.CommonUtil;

/**
 * 拦截器
 * 
 * @author huangyuanmu

 * @date 2018年6月13日 下午3:53:42
 * @version 1.0
 */
public class MyWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {
            String user = CommonUtil.getSpringSecurityUser();
            if(CommonUtil.isNotEmpty(user)) {
            	attributes.put("WEBSOCKET_USERNAME", user);
            }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }
}

编写websocket消息处理类MyWebSocketHandler:

package cn.xxx.rmwlgzpt.websocket;

import java.io.IOException;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * 消息处理
 * 
 * @author huangyuanmu
 * @date 2018年6月13日 下午3:37:36
 * @version 1.0
 */
public class MyWebSocketHandler extends TextWebSocketHandler {

	private final static Logger log = LoggerFactory.getLogger(MyWebSocketHandler.class);

	private static final ArrayList<WebSocketSession> users;

	static {
		users = new ArrayList<WebSocketSession>();
	}

	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		users.add(session);
	}

	public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
		users.remove(session);
	}

	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		super.handleTextMessage(session, message);
	}

	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		if (session.isOpen()) {
			session.close();
		}
		users.remove(session);
	}

	@Override
	public boolean supportsPartialMessages() {
		return false;
	}

	/**
	 * 发送消息给某个用户
	 * 
	 * @author huangyuanmu

	 * @date 2018年6月13日 下午4:03:58
	 * @param userName
	 * @param message
	 */
	public void sendMessageToUser(String userName, TextMessage message) {
		for (WebSocketSession user : users) {
			if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
				try {
					if (user.isOpen()) {
						user.sendMessage(message);
					}
				} catch (IOException e) {
					log.error("发送消息出错!", e);
				}
				break;
			}
		}
	}
}

因为在我的应用场景下,只需要websocket服务端发送消息,所以仅实现了发送消息的代码。接收消息的代码略去。

应用场景中,进行消息发送:

package cn.xxx.rmwlgzpt.web.shell.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;

import cn.tohot.psyco.web.base.domain.SqlObj;
import cn.tohot.psyco.web.base.service.CommonCrudService;
import cn.tohot.rmwlgzpt.websocket.MyWebSocketHandler;
import cn.tohot.swim.comm.PageData;
import cn.tohot.swim.spring.dao.SqlDAO;
import cn.tohot.util.common.CommonUtil;
import cn.tohot.util.exception.LazyAppException;

/**
 * 消息查询的服务类
 * 
 * @author huangyuanmu
 * @date 2015年9月22日 上午10:06:31
 * @version 1.0
 */
@Service
public class SysAlertService extends CommonCrudService {

      @Bean
      public MyWebSocketHandler webSocketHandler() {
          return new MyWebSocketHandler();
      }
      /**
	 * 发消息通知客户端
		 * @author huangyuanmu
	 * @date 2018年6月13日 下午4:46:59
	 */
     private void notifyClient() {
         // 发websocket消息通知客户端
	 webSocketHandler().sendMessageToUser(CommonUtil.getSpringSecurityUser(), new TextMessage("Alerts updated."));
     }
/** * 产生消息 * * @author huangyuanmu * @date 2015年11月7日 下午4:13:53 * @param title * 消息标题 * @param content * 消息内容 * @param bizType * 消息业务类型 * @param bizId * 消息业务id * @param receivers * 消息的接受者 * @param type * 消息类型 */public void generateAlertSave(String title, String content, String bizType, String bizId, List receivers, String type) {
	    // 业务代码略

	    // 发消息通知客户端
	    notifyClient();
	}
	
	/**
	 * 处理消息
	 * 
	 * @author huangyuanmu
	 * @date 2015年11月7日 下午4:29:51
	 * @param bizType 
         *        消息业务类型
	 * @param bizId
         *        业务id
         * @param userId
         *        用户id
         */
  public void dealAlertSave(String bizType, String bizId, String userId) {
            // 业务代码略
                
          // 发消息通知客户端
	  notifyClient();
     }
}

四. 客户端代码编写

  var options = {};
  options.url = "desktop";
  options.serviceName = "desktopService";
  options.methodName = "getWsUrl";
  options.isMask = false;
  options.func = function(data) {
      var websocket = null;
      if ('WebSocket' in window) {
          websocket = new WebSocket(data.wsUrl);
    } 
    else if ('MozWebSocket' in window) {
        websocket = new MozWebSocket(data.wsUrl);
    } else {
        websocket = new SockJS(data.wsJsUrl);
    }
    websocket.onopen = onOpen;
    websocket.onmessage = onMessage;
    websocket.onerror = onError;
    websocket.onclose = onClose;
  
    function onOpen(openEvt) {}
    // 接收websocket消息,更新系统消息显示	    
    function onMessage(evt) {
        $.head.loadAlerts();
    }
	   
    function onError() {}
	    
    function onClose() {}
  }
  $.serviceCall(options);

因为业务和技术上做了一定的封装,我也没有必要将全部代码列出,所以这里的代码看起来不是那么清晰,但是大体流程是清楚的,可供在spring mvc和spring websocket的整合过程中参考。

五. 写在最后

按照以上流程,应该就可以实现spring websocket的成功整合了,但还有最后一个问题需要解决。在我的整合过程中,发生了ajax调用中文乱码的问题,话费了不少时间。究其原因,关键的地方在于要在spring mvc的配置文件中增加一些配置:

  
 <bean  
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">  
        <property name="messageConverters">  
            <list>  
                <ref bean="stringHttpMessageConverter" />  
            </list>  
        </property>
  </bean>
<bean id="stringHttpMessageConverter"  
        class="org.springframework.http.converter.StringHttpMessageConverter">  
        <property name="supportedMediaTypes">
            <list>  
                <value>text/plain;charset=UTF-8</value>
            </list>
        </property>
  </bean>  
<!-- 增加上边的配置 --> 

  <context:component-scan base-package="cn.xxx.rmwlgzpt.web">
      <context:exclude-filter type="annotation"  expression="org.springframework.stereotype.Service" />
  </context:component-scan>
	
  <context:component-scan base-package="cn.xxx.rmwlgzpt.websocket"/>
	
  <task:annotation-driven/>


 

猜你喜欢

转载自blog.csdn.net/huangyuanmu/article/details/80729382