springMVC结合websocket小结

3/8/2017来源:ASP.NET技巧人气:1590

由于浏览器的历史遗留性问题,不是所有的浏览器都是支持WebSocket的,尤其是IE10以下,所以才出现了SockJS这样一个框架,它的原理也很简单,就是如果你的浏览器支持WebSocket那么他就使用webSocket协议通信,入股不支持就使用流传输或者轮询的方式,这样也保证了资源的最大利用率。

1、websocket相关pom文件配置

<!--websocket-->
<!-- PRovided Websocket API, because tomcat has its own implementation -->
<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.3.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<!--相关jar-->
<!-- Json -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.1</version>

2、WebSocketConfig----配置WebSocket访问的地址

package com.bms.web.notice.websocket.config;


import com.bms.web.notice.websocket.handler.SystemWebSocketHandler;
import com.bms.web.notice.websocket.interceptor.WebsocketHandshakeInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
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.HandshakeInterceptor;

/**
 * Created by zhu_kai1 on 2017/2/23.
 */
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebsocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    // webSocket
    private static final String WEBSOCKET_SERVER ="/webSocketServer";
    private static final String ECHO ="/echo";
    // 不支持webSocket的话用sockjs
    private static final String SOCKJS ="/sockjs/webSocketServer";
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //支持websocket 的访问链接
        registry.addHandler(systemWebSocketHandler(), WEBSOCKET_SERVER).addInterceptors(handshakeInterceptor());
        registry.addHandler(systemWebSocketHandler(), ECHO).addInterceptors(handshakeInterceptor());
        //不支持websocket的访问链接
        registry.addHandler(systemWebSocketHandler(), SOCKJS).addInterceptors(handshakeInterceptor()).withSockJS();
    }
    @Bean
    public WebSocketHandler systemWebSocketHandler(){
        return new SystemWebSocketHandler();
    }
    @Bean
    public HandshakeInterceptor handshakeInterceptor(){
        return new WebsocketHandshakeInterceptor();
    }

    // Allow serving HTML files through the default Servlet

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

3、HandshakeInterceptor---握手拦截器

package com.bms.web.notice.websocket.interceptor;

import com.msk.sso.client.bean.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * Created by zhu_kai1 on 2017/2/23.
 */
public class WebsocketHandshakeInterceptor implements HandshakeInterceptor{
    private static Logger logger = LoggerFactory.getLogger(WebsocketHandshakeInterceptor.class);

    public WebsocketHandshakeInterceptor() {
    }

    // 初次握手访问前
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            //使用userName区分WebSocketHandler,以便定向发送消息
            User loginUser= (User) servletRequest.getsession().getAttribute("loginUser");
            //存入数据,方便在hander中获取,这里只是在方便在webSocket中存储了数据,并不是在正常的httpSession中存储,想要在平时使用的session中获得这里的数据,需要使用session 来存储一下
            if(null !=loginUser){
                map.put("userName", loginUser.getUserLogin());
                logger.info("当前的登陆者为:{}", loginUser.getUserLogin());
            }else{
                logger.error("没有获取session中的当前登陆者信息");
            }
        }

        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}

4、SystemWebSocketHandler----消息处理

package com.bms.web.notice.websocket.handler;

import com.bms.web.notice.bean.result.NoticeResult;
import com.bms.web.notice.service.CommonService;
import com.framework.base.rest.result.BaseRestPaginationResult;
import com.framework.core.utils.StringUtils;
import com.framework.exception.SystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

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

/**
 * Created by zhu_kai1 on 2017/2/23.
 */
@Component
public class SystemWebSocketHandler implements WebSocketHandler {

    @Autowired
    private  CommonService commonService;
    public static final String USERNAME = "userName";
    private static Logger logger = LoggerFactory.getLogger(SystemWebSocketHandler.class);
    protected final static List<WebSocketSession> sessions = Collections.synchronizedList(new ArrayList<WebSocketSession>());

    public SystemWebSocketHandler() {
    }

    // 连接建立后处理
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        logger.info("webSocket连接已建立");
        sessions.add(webSocketSession);
        String userLogin = (String) webSocketSession.getAttributes().get(USERNAME);
        BaseRestPaginationResult<NoticeResult> result = null;
        if(StringUtils.isNotEmpty(userLogin)){
            result = commonService.getNoticeInfo(userLogin);
        }
        if(null !=result){
            sendMessageToAll(new TextMessage(StringUtils.toString(result.getTotal())));
        }
    }

    // 接收客户端消息,并发送出去
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        logger.info("发送消息" + webSocketMessage.toString());
    }

    // 抛出异常时处理
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        if (webSocketSession.isOpen()) {
            webSocketSession.close();
        }
        sessions.remove(webSocketSession);
        logger.info("webSocket异常处理" + throwable.getMessage());
        throw new SystemException(throwable);
    }

    // 连接关闭后处理
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        logger.info("webSocket连接已关闭......" + closeStatus.getReason());
        sessions.remove(webSocketSession);
    }

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


    /**
     * 给所有在线用户发送消息
     *
     * @param message
     */
    public void sendMessageToAll(TextMessage message) {
        for (WebSocketSession session : sessions) {
            try {
                if (session.isOpen()) {
                    session.sendMessage(message);
                }
            } catch (IOException e) {
                throw new SystemException(e.getMessage());
            }
        }
    }
}

5、在对应的spring-mvc.xml配置文件中需要扫描websocketConfig

 <context:component-scan base-package="com.bms.web" use-default-filters="false">
        <context:include-filter type="regex" expression="com\.bms\.web\.notice\.websocket\..*"/>
    </context:component-scan>

6、jsp需要引用对应的sockjs

<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>

7、js调用方法

webSocket: function () {
        var host = window.location.host;
        var websocket;
        var url = null;
        if ('WebSocket' in window) {
            url = "ws://" + host + Main.contextPath + "/webSocketServer";
            websocket = new WebSocket(url);
        } else if ('MozWebSocket' in window) {
            url = "ws://" + host + Main.contextPath + "/echo";
            websocket = new MozWebSocket(url);
        } else {
            url = Main.contextPath + "/sockjs/webSocketServer";
            websocket = new SockJS(url);//建立连接
        }
        websocket.onopen = function (event) {
        };
        websocket.onmessage = function (event) {
            $("span#noticeTotal").text(event.data);
            $("span#notice").text("共" + event.data + "条通知");
        };
        websocket.onerror = function (event) {
        };
        websocket.onclose = function (event) {
            $.alertMessage.error("与webSocket服务器断开了连接");
        }

    }ok,到此大致的websocket已搭建好,但是各位可能会遇到以下问题,请找到对应的错误解决,以下解决方法也是从网上查询到的。

问题1:统计了一下大家遇到第一个问题就是连接websocket时候报404错误

先检查连接websocket的url格式:ws://localhost:8080/web/webSocketServer,这个webSocketServer要匹配websocketConfig中的

registry.addHandler(systemWebSocketHandler(), "/webSocketServer").addInterceptors(handshakeInterceptor());

其次检查下Spring配置文件是否有加这个tag:<mvc:annotation-driven/>(加这个会出现中文乱码,下面会讲到),使用Spring websocket需要这个tag支持

当Spring配置文件有使用<context:component-scan/>扫描包,这个tag<context:annotation-config/>可以不去掉。

问题2:连接websocket时候报200,说明已经进入拦截器握手成功,但是没连接上websocket

如果websocket有配置自己定义的拦截器,先检查下拦截器beforeHandshake这个方法,这个方法有个参数Map<String, Object> attributes,不能给这个map的value设成null,否则进不到自己Handler下的这个方法afterConnectionEstablished,就会报200

问题3:连接websocket时候,如果缺少配置会报415 Unsupported Media Type请求的格式不受请求页面的支持错误

当用户发送请求后,@Requestbody 注解会读取请求body中的数据,默认的请求转换器HttpMessageConverter通过获取请求头Header中的Content-Type来 确认请求头的数据格式,从而来为请求数据适配合适的转换器。例如contentType:applicatin/json,那么转换器会适配 MappingJacksonHttpMessageConverter。响应时候的时候同理,@Responsebody注解会启用 HttpMessageConverter,通过检测Header中Accept属性来适配的响应的转换器。

当在使用SpringMVC做服务器数据接收时,尤其是在做Ajax请求的时候,尤其要注意contentType属性,和accepte 属性的设置,在springmvc-config.xml中配置好相应的转换器。

添加相应转换器:

<bean id="stringHttpMessageConverter"         class="org.springframework.http.converter.StringHttpMessageConverter">         <property name="supportedMediaTypes">             <list>                 <value>text/plain;charset=UTF-8</value>             </list>         </property> </bean>

<bean         class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">         <property name="messageConverters">             <list>                 <ref bean="stringHttpMessageConverter" />新增的                 <ref bean="byteArrayHttpMessageConverter" />                 <ref bean="jsonHttpMessageConverter" />                 <ref bean="jsonHttpMessageConverter4JS" />             </list>         </property>     </bean>

问题4:网上例子都有说要在web.xml下的servlet和filter里面加上<async-supported>true</async-supported>

没有影响websocket是可以连接成功的(小编就是这种方式实现的),可以不用加,这个是用来支持异步的servlet3.x,建议不用加,除非有用到这个特性(小编没有试,这个可以自行对比下)

问题5:添加<mvc:annotation-driven/>这个出现中文乱码

刚开始时候,所有浏览器都出现中文乱码,后来解决在Chrome浏览不会出现中文乱码,但是在FF下会出现。原因有三个: 第一,MessageConverter转换器没配置相应的<property name="supportedMediaTypes">属性, 第二,bean和tag的先后顺序不对, 第三,当使用<mvc:annotation-driven/>这个tag时候,请求处理器就会变成RequestMappingHandlerAdapter,跟进代码就会发现不是采用AnnotationMethodHandlerAdapter,所以配置时候要改成:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 统一解决办法是:要注意bean和tag的先后顺序 <bean         class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">//这是根本原因         <property name="messageConverters">             <list>                 <ref bean="stringHttpMessageConverter" />                 <ref bean="byteArrayHttpMessageConverter" />                 <ref bean="jsonHttpMessageConverter" />                 <ref bean="jsonHttpMessageConverter4JS" />             </list>         </property>     </bean> <context:component-scan base-package="扫描Spring controller包路径" /> <context:component-scan base-package="扫描websocket包路径"/> <context:annotation-config />  //这个标注可以不加 <mvc:annotation-driven/>//这个tag一定要放在上面代码最后,这是也是乱码根源之一

问题6:启动时候出现这个Factory method 'webSocketHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No suitable default RequestUpgradeStrategy found

 

说明你的容器不支持websocket协议Tomcat7,0.26之后才支持websocket

Jboss as 7不支持websocket,需要要安装插件,可以直接升级到wildfly8支持websocket