SpringBoot + WebSocket完美整合

本文整合的意义在于,后台处理websocket请求能像Controller处理请求一样简便,同样能在Controller层定义接口,返回数据,使用Spring注解对象等。

1.      pom引用websocket

        org.springframework.boot

        spring-boot-starter-websocket

 

2.      websocket配置

@Configuration

@EnableWebSocket

publicclass WebSocketConfig implements WebSocketConfigurer {

    @Bean

public ServerEndpointExporter serverEndpointExporter(ApplicationContextcontext){

        return new ServerEndpointExporter();

    }

//注册websocket处理类(TWSHandler),以及访问路径(/xxx/main)

    @Override

    public void registerWebSocketHandlers(WebSocketHandlerRegistryregistry) {

        registry.addHandler(new TWSHandler(),"/xxx/main").addInterceptors(newWebSocketInterceptor()).setAllowedOrigins("*");

    }

 

}

3.      websocket消息统一处理

@Component

publicclass TWSHandler extends TextWebSocketHandler {

 

private final String charset ="UTF-8";

 

@Override

public void handleTextMessage(WebSocketSessionsession,

                    TextMessage message) throwsException {

           // TODO Auto-generated method stub

           String msg = message.getPayload();

           System.out.println("handlerText===========>"+ msg);

           JSONObject result = null;

 

           Object stamp = null;         //时间戳,用来标识返回结果

           Pattern pattern =Pattern.compile("^\\{(\"\\w+\":\\S+,{0,1})+\\}$");

           if(pattern.matcher(msg).matches()){

                    JSONObject json = JSONObject.fromObject(message.getPayload());

                    stamp =json.get("stamp");

                    result =WSDispatcher.dispatch(json, session);

           }

          

           String response = "";

           if(result == null)        response = "404";

           else{

                    result.put("stamp",stamp);

                    response = String.valueOf(result);

           }

           session.sendMessage(newTextMessage(response.getBytes(charset)));

 

}

@Override

public voidafterConnectionClosed(WebSocketSession session,

                    CloseStatus status) throwsException {

           // TODO Auto-generated method stub

           super.afterConnectionClosed(session,status);

           WSServer.instance().disconnect(session);

}

 

@Override

public voidhandleTransportError(WebSocketSession session, Throwable exception) throwsException {

           super.handleTransportError(session,exception);

           WSServer.instance().disconnect(session);

}

}

这里用到了两个关键类:

WSDispatcher用于请求转发,将访问路径和参数传递给WSController方法;

WSServer保存了所有websocket session信息,并且封装了相关处理方法。

需要注意的是:

前后端交互的数据格式采用json;

stamp参数有很多用途,需要前端配合,比如前端多个请求共用同一个websocket连接时(节省资源),可以标识返回结果属于哪个请求,实现类似ajax的功能;也可以用于标识广播消息的类型,前端根据不同stamp状态进行不同处理。

本项目配套的前端插件ws_client.js就实现了前端的消息统一处理。

 

4.      WSDispatcher请求转发

@Component

publicclass WSDispatcher  {

 

public static MapwebSocketMapping = new HashMap();

 

public static voidinit(RequestMappingHandlerMapping requestMappingHandlerMapping){

           webSocketMapping.clear();

           Map map = requestMappingHandlerMapping.getHandlerMethods();

           for (Map.Entry m : map.entrySet()) {

                    RequestMappingInfo info =m.getKey();

            HandlerMethod method = m.getValue();

            //无WSController注解的过滤掉

           if(method.getBeanType().getDeclaredAnnotationsByType(WSController.class).length== 0)

            continue;

            Set patterns =info.getPatternsCondition().getPatterns();

            if(patterns.size()>0){

            System.out.println(patterns.toArray()[0]);

            webSocketMapping.put(patterns.toArray()[0],method);

            }

           }

}

 

 

/*

 * 接口分发

 */

public static Object dispatch(String url,Object parameter, WebSocketSession session){

 

           HandlerMethod method =webSocketMapping.get(url);

           if(method != null){

                    try{

                             Class cls= method.getMethod().getDeclaringClass();

                             ObjectcontrollerObj = SpringContextUtil.getBean(cls);

                             Object[] args = newObject[method.getMethod().getParameterCount()];

                             Class[]argTypes = method.getMethod().getParameterTypes();

                             for(int i=0;i

                                       if(argTypes[i].equals(Map.class))    args[i] = parameter;

                                       elseif(argTypes[i].equals(WebSocketSession.class))       args[i]= session;

                             }

                             if(args.length ==0)

                                       returnmethod.getMethod().invoke(controllerObj);

                             else

                                       returnmethod.getMethod().invoke(controllerObj, args);

                    }catch(Exception e){

                             e.printStackTrace();

                    }

           }

           return null;

}

 

/*

 * 接口分发

 * 注意:json参数必须包含url和params

 */

public static JSONObject dispatch(JSONObjectjson, WebSocketSession session){

           Object response =dispatch(String.valueOf(json.get("url")),json.get("params"), session);

           JSONObject jsonObj = newJSONObject();

           jsonObj.put("data",response);

           return jsonObj;

}

}

 

请求转发首先需要获取所有WSController注解的接口,再调用init方法初始化,本文是在springboot启动时进行初始化。

@SpringBootApplication

@EnableAutoConfiguration

publicclass AppStarter {

 

    @Autowired

    private RequestMappingHandlerConfigrequestMappingHandlerConfig;

 

    public static void main(String[] args) {

        SpringApplication springApplication=new SpringApplication(AppStarter.class);

        springApplication.run(args);

    }

 

   @PostConstruct

    public void detectHandlerMethods(){

       WSDispatcher.init(requestMappingHandlerConfig.requestMappingHandlerMapping());

       System.out.println(WSDispatcher.webSocketMapping.size());

    }

}

         接下来就是构造参数,委托调用访问接口对应的方法。

        注意:

         参数匹配没有springmvc做的那么完美,只能匹配Map和WebSocketSession,个人觉得有点Low,有待改善。

获取WSController实例对象不能使用反射的方法,必须要用Spring容器的方法SpringContextUtil.getBean。这是因为Spring容器能实例化WSController组件内部的@Autowired对象,而反射不能。如果WSController组件内部没有Spring容器对象,两种方法都行。

 

5.      websocket登录

为了结合用户操作,文本单独做了session注册

@Component

@WSController()

@RequestMapping("/log")

publicclass LoginController {

    @RequestMapping("/login")

    public String login(Map map, WebSocketSessionsession){

        if(map.get("uid") == null)

            return "parameter withoutuserId";

       WSServer.instance().connect(String.valueOf(map.get("uid")),session);

        return "login success!";

    }

 

}

 

6.      WSController方法

@Component

@WSController()

@RequestMapping("/msg")

publicclass MessageController {

      @RequestMapping("/hello")

      public String hello(Mapmap, WebSocketSession session){

               System.out.println(session.getId());

               System.out.println("msg-----hello---------"+ map);

               return"from hello";

      }

}

         这个跟Controller没啥区别,返回类型任意,最终都会JSON化,不需要@ResponseBody注解。参数只能匹配Map和WebSocketSession,够用了。

 

7.      总结一下

上篇博客有人要源码,过了N久才看到,尴尬

 SpringBoot + WebSocket完美整合_第1张图片

这次先留个联系方式QQ:1733073247

源码上传到github,https://github.com/WangbaishiLibi/notice.git

源码项目是SpringBoot项目,将其它项目写到Redis的消息推送到前端,这部分凑合着能用吧。

 

本来只是想混个积分下东西,发现好多东西要整,瞬间没动力了

 SpringBoot + WebSocket完美整合_第2张图片

下篇有空把ws_client.js插件的用法说明一下(唉~,下次不知道什么时候)

懒惰是原罪,我就想静静的躺着


你可能感兴趣的:(SpringBoot)