本文整合的意义在于,后台处理websocket请求能像Controller处理请求一样简便,同样能在Controller层定义接口,返回数据,使用Spring注解对象等。
1. pom引用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 Map
public static voidinit(RequestMappingHandlerMapping requestMappingHandlerMapping){
webSocketMapping.clear();
Map
for (Map.Entry
RequestMappingInfo info =m.getKey();
HandlerMethod method = m.getValue();
//无WSController注解的过滤掉
if(method.getBeanType().getDeclaredAnnotationsByType(WSController.class).length== 0)
continue;
Set
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久才看到,尴尬 这次先留个联系方式QQ:1733073247 源码上传到github,https://github.com/WangbaishiLibi/notice.git 源码项目是SpringBoot项目,将其它项目写到Redis的消息推送到前端,这部分凑合着能用吧。 本来只是想混个积分下东西,发现好多东西要整,瞬间没动力了 下篇有空把ws_client.js插件的用法说明一下(唉~,下次不知道什么时候) 懒惰是原罪,我就想静静的躺着