Skip to content

websocket请求用自定义注解@WSRequestMapping访问,类似springmvc @RequestMapping访问。

Notifications You must be signed in to change notification settings

luxiangzhou/AnnoWebsocket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AnnoWebsocket

websocket请求用自定义注解@WSRequestMapping访问,类似springmvc @RequestMapping访问。 源码github地址:https://github.com/luxiangzhou/AnnoWebsocket

1、ajax长轮询

web异步请求一般用ajax实现,但是如果后端请求返回时间慢,而web异步请求又非常多,如果浏览器有超过6个ajax请求不能返回处于pending状态,就会导致浏览器卡死。这时又想有多个异步请求同时发出,又不想浏览器卡死,可以让后端立马返回一个token给ajax,然后js定时循环掉直到返回数据,这样可以保证请求不在pending状态,浏览器不会卡死。用token方式可以解决浏览器请求过多问题,但是还是要先返回token、还要轮询,很是麻烦,也不是真正的异步解决方法。可以用websocket代替。

2、websocket

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。 websocket的优越性不言自明,长连接的连接资源(线程资源)随着连接数量的增多,必会耗尽, 客户端轮询会给服务器造成很大的压力,而websocket是在物理层非网络层建立一条客户端至服务器的长连接, 以此来保证服务器向客户端的即时推送,既不耗费线程资源,又不会不断向服务器轮询请求。

3、spring+websocket整合

在实际开发中websocket请求到controller层按照websocket原生态方式访问还是比较麻烦的, 可以参照spring mvc注解方式进行访问、返回数据。

4、自定义注解 @WSRequestMapping

使用@WSRequestMapping注解,让websocket请求类似于springmvc @RequestMapping注解方式访问

4.1浏览器访问例子

在浏览器中打开:IP:端口/项目名 ws

4.2Controller访问例子

@RestController
@RequestMapping(value = { "/api/springmvc" })
@WSRequestMapping(value = { "/api/websocket" })
public class TestController {
	@Autowired
	private TestService testService;

	@RequestMapping(value = { "/test" }, method = RequestMethod.GET)
	@WSRequestMapping(value = { "/test" })
	public String test(String param) {
		return testService.helloWebscoket(param);
	}

}

其中@RequestMapping(value = { "/api/springmvc" })是spring mvc用于get/post的访问, @WSRequestMapping(value = { "/api/websocket" })是仿springmvc @RequestMapping用于websocket的访问。

4.3大体思想

  1. 先写一个@WSRequestMapping注解用于写在类和方法上面;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface WSRequestMapping {
	public abstract String[] value();
}
  1. 项目启动的时候扫描所有的@WSRequestMapping注解,将扫描到的@WSRequestMapping注解及对应的类、方法保存到一个Map里面;
public class WSApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
	private static final Log LOGGER = LogFactory.getLog(WSApplicationListener.class);
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		ApplicationContext context = event.getApplicationContext();
		// 判断不为空,防止进来两次;保证spring启动好了再扫描WSRequestMapping注解
		if (event != null && context.getParent() != null) {
			LOGGER.info("AnnoWebsocket mvc注解扫描 begin...");
			SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
			long beginTime = new Date().getTime();
			LOGGER.info("begin time : " + dateFormatter.format(new Date()));

			Set<Class<?>> clazzs = getClasses(WSConstant.WS_SCAN_PACKAGE);
			int beanIndex = 1;
			for (Class<?> clazz : clazzs) {
				boolean annoFlag = clazz.isAnnotationPresent(WSRequestMapping.class);
				if (annoFlag) {
					// 从spring容器里面获取bean,不要自己反射获取bean,因为自己反射获取的bean中的注解对象是null,比如controller里面的注解获取的service为null
					Object bean = context.getBean(clazz);
					Class<?> beanClazz = bean.getClass();
					// 获取类上WSRequestMapping注解url
					WSRequestMapping clazzAnno = clazz.getAnnotation(WSRequestMapping.class);
					String[] clazzWsUrls = clazzAnno.value();
					for (String clazzWsUrl : clazzWsUrls) {
						// 获取方法上WSRequestMapping注解url
						Method[] methodAnnos = beanClazz.getDeclaredMethods();
						for (int i = 0; i < methodAnnos.length; i++) {
							Method method = methodAnnos[i];
							WSRequestMapping methodAnno = method.getAnnotation(WSRequestMapping.class);
							if (methodAnno != null) {
								String[] methodWsUrls = methodAnno.value();
								for (String methodWsUrl : methodWsUrls) {
									WSRequestMappingBean clazzBean = new WSRequestMappingBean();
									clazzBean.setBean(bean);
									clazzBean.setClazzWsUrl(clazzWsUrl);
									clazzBean.setMethod(method);
									clazzBean.setMethodWsUrl(methodWsUrl);
									// 使用ApplicationContext获取项目根目录
									String wsUrl = event.getApplicationContext().getApplicationName() + clazzWsUrl
											+ methodWsUrl;
									WSConstant.WS_CLAZZ_MAP.put(wsUrl, clazzBean);
									LOGGER.info("AnnoWebsocket Bean-" + (beanIndex++) + ":" + wsUrl);
								}
							}
						}
					}
				}
			}

			long endTime = new Date().getTime();
			LOGGER.info("end time : " + dateFormatter.format(new Date()));
			LOGGER.info("总耗时 : " + (endTime - beginTime) + "ms");
			LOGGER.info("AnnoWebsocket mvc注解扫描 done!");
		}

	}
	...
}
  1. 用拦截器拦截websocket请求,到Map里面查看是否有这个websocket请求,有这个webscoket请求就用反射调用controlller层类、方法;
public class WSHandler implements WebSocketHandler {
	...

	@Override
	public void handleMessage(final WebSocketSession session, final WebSocketMessage<?> message) throws Exception {
		LOGGER.info("AnnoWebsocket handleMessage begin ....");
		EXECUTOR_SERVICE.submit(new Runnable() {
			@Override
			public void run() {
				try {
					handle(session, message);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		LOGGER.info("AnnoWebsocket handleMessage end!");
	}

	private void handle(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
		// websocket url
		URI uri = session.getUri();
		String wsUrl = uri.toString();
		// websocket message
		String wsMessage = (String) message.getPayload();

		// 反射调用业务方法,并将业务方法返回的数据返回给websocket
		if (WSConstant.WS_CLAZZ_MAP.containsKey(wsUrl)) {
			WSRequestMappingBean wsBean = WSConstant.WS_CLAZZ_MAP.get(wsUrl);
			Object bean = wsBean.getBean();
			// 方法
			Method wsBeanMethod = wsBean.getMethod();

			// 方法参数
			Class<?>[] parameterTypes = wsBeanMethod.getParameterTypes();
			LocalVariableTableParameterNameDiscoverer paramterDiscover = new LocalVariableTableParameterNameDiscoverer();
			String[] parameterNames = paramterDiscover.getParameterNames(wsBeanMethod);
			Object[] args = new Object[parameterTypes.length];

			// websocket message值
			JSONObject jsonObject = null;
			boolean isJSON = false;
			try {
				jsonObject = JSONObject.fromObject(wsMessage);
				isJSON = true;
			} catch (Exception e) {
				isJSON = false;
			}

			for (int i = 0; i < parameterTypes.length; i++) {
				Class<?> pClazz = parameterTypes[i];
				Object pValue = null;
				if ("java.lang.String".equals(pClazz.getName())) {
					if (isJSON) {
						pValue = jsonObject.get(parameterNames[i]);
					} else {
						pValue = wsMessage;
					}
				} else if ("java.lang.Booealn".equals(pClazz.getName()) || "boolean".equals(pClazz.getName())
						|| "int".equals(pClazz.getName())) {
					pValue = jsonObject.get(parameterNames[i]);
				} else {
					pValue = JSONObject.toBean(jsonObject, pClazz);
				}
				args[i] = pValue;
			}
			// 反射调用业务方法
			Object resObj = wsBeanMethod.invoke(bean, args);
			// 将业务方法返回的数据返回给websocket
			WSUtils.sendMessage(wsUrl, resObj.toString());
		}
	}
	...
}
  1. 最后返回数据到前端websocket。
// 将业务方法返回的数据返回给websocket
WSUtils.sendMessage(wsUrl, resObj.toString());

5、Spring @MessageMapping

后来发现Spring对于WebSocket请求也做了封装,提供了一个@MessageMapping注解,功能类似@RequestMapping,它是存在于Controller中的,定义一个消息的基本请求,功能也跟@RequestMapping类似。

About

websocket请求用自定义注解@WSRequestMapping访问,类似springmvc @RequestMapping访问。

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published