在做前端页面优化时,大厂都会通过将多个资源文件以逗号分隔,一次性下载下来,减少请求数,从而提升页面加载速度。除了静态资源请求外,在页面初始化时,有时我们会用ajax发起多个异步请求,若这些请求有先后顺序则更难处理,还要确保请求A调用结束才可调用请求B。如果对多个ajax异步请求不进行合并处理,则至少会有2点不足:
1)增加页面加载时间;
2)如果请求有先后顺序的话,会增加开发难度,代码质量不可控。
本文基于jquery ajax 和 springmvc 讨论如何实现将多个请求封装成一个请求。

1、先看个应用场景: 商品修改

访问商品修改页面,url&id=1001:
1)先会根据id请求商品信息,/goods?m=get&id=1001
2)商品信息返回后,请求商品所有可用单位列表,/goods/unit?m=list,然后将商品的单位id转成单位名称显示(如:箱、公斤);
3)商品信息返回后,请求商品分类列表,/goods/catg?m=list,然后将商品的分类id转成分类名称显示(如:596ml纯净水);
4)商品信息返回后,请求商品属性列表,/goods/prop?m=list,然后将商品的属性id转成属性名称显示(如:成品、原材料);
现在将获取商品单位列表、商品分类列表、商品属性列表请求合并成一个请求,如何实现?

2、jquery ajax发送请求特殊处理
$.ajax({
	url: "/multirquest",
	data: {requests:[
        	{url: "/goods/prop", key: "goods_types", para:{m:"list"}},
        	{url: "/goods/unit", key: "goods_units", para:{m:"list", status: "有效"}},
       		{url: "/goods/catg", key: "goods_catgs", para:{m:"list", status: "有效"}}
    ]},
	async: true,
	dataType: "json",
	success: function(data){
		// 服务器端返回的是统一数据结构{flag: boolean, data:object, errMsg:, errNo:}
		if (data.flag) {	// 业务上成功
			data.data['goods_types']	//物料类型:成品、原材料
			data.data['goods_units']	//物料单位:箱、件、公斤
			data.data['goods_catgs']	//物料分类:纯净水--596ml纯净水等
		}
	},
	error: function(jqXHR, textStatus, errorThrown) {
		// 错误处理
	},
	type: "POST"
});

说明:
1)合并请求统一发送到同一个url,/multirquest
2)关键是参数处理,将每个请求封装成参数对象,push到requests数组,每个请求对象格式如下:

{
	url: "/goods/prop", 	// 细分请求url
	key: "goods_types", 	// 读取统一返回结果的对象key,如:data.data['goods_types']
	para:{m:"list"}			// 细分请求具体参数
},
3、spingmvc处理
@RequestMapping(value = "/multirequest", method = RequestMethod.POST)
	@ResponseBody
	public JsonResult multiRequest(HttpServletRequest request, HttpServletResponse response) {
		JsonResult result = JsonResult.getInstance();
		try {
			Map<String, Object> data = new HashMap<String, Object>();
			// 解析参数requests,将ajax传的数组转换成java List<Map>对象.
			List<Map> list = TypeUtil.toObjct(request.getParameter("requests"), ArrayList.class);
			for (Map map : list) {
				if (TypeUtil.isEmpty(map.get("url")) || TypeUtil.isEmpty(map.get("key"))) {	// 验证细分请求的参数合法性,细分请求url,返回结果映射key不能为空
					throw new AppException("url或数据返回映射key没指定!");
				}
				// SpringMvcUrls 在web项目启动的时候就加载了所有springmvc url和对应的controller方法映射,加载方法参考loadSpringUrlMappings, 何时加载可以通过filter处理
				HandlerMethod method = SpringMvcUrls.getUrlHandlerMethod((String) map.get("url"));
				if (method == null) {
					throw new AppException("url: " + map.get("url") + "没找到!");
				}
				// 细分请求para,通过request attribute进行传递
				request.setAttribute("ATTR_KEY_PARAM", map.get("para"));
				// 反射调用
		        Object bean = method.getBeanType().newInstance();
				Object ir = method.getMethod().invoke(bean, method.getMethodParameters().length == 1
						? new Object[]{request} : new Object[]{request, response});
				JsonResult jrst = (JsonResult) ir;
				if (!jrst.isFlag()) {
					throw new AppException(result.getMsg());
				}
				data.put((String) map.get("key"), jrst.getData()); 
			}
			result.setData(data);
		} catch (Exception e) {
			ExceptionHelper.print(e);
			result.setErrorMsg(e.getMessage());
		}		
		return result;
	}

	//加载所有springmvc url mapping.
	protected synchronized void loadSpringUrlMappings(HttpServletRequest request) {
		if (SpringMvcUrls.getUrlMappings().isEmpty()) {
			WebApplicationContext wc = (WebApplicationContext)request
					.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	        //获取所有的RequestMapping
	        RequestMappingHandlerMapping handlerMapping = wc.getBean(RequestMappingHandlerMapping.class);
	        if (handlerMapping != null) {
	        	Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
                for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
                    RequestMappingInfo mappingInfo = entry.getKey();
                    HandlerMethod handlerMethod = entry.getValue();
                    PatternsRequestCondition patternsCondition = mappingInfo.getPatternsCondition();
                    Set<String> urlPatterns = patternsCondition.getPatterns();
                    Iterator<String> it = urlPatterns.iterator();
					while (it.hasNext()) {
						SpringMvcUrls.addUrl(it.next(), handlerMethod);
					}
		        }
			}
		}		
	}

代码说明:
1)代码不能直接运行,主要表达实现原理;
2)细分请求通过request.setAttribute(“ATTR_KEY_PARAM”, map.get(“para”))进行传参,那么对应目标controller方法进行参数处理时要注意:

  • 除了reqeust.getParameter获取参数外
  • 还要通过request.getAttribute(“ATTR_KEY_PARAM”)继续获取参数,并和getParameter参数进行合并;

3)一个关键步骤,通过url进行controller方法映射,并根据controller方法进行反射调用,这里有2个关键点:

  • 如何进行url和controller方法映射,我们可以通过配置一个filter,将其load-on-startup配置为1,使其在web项目启动的时候初始化;
  • 也可以通过在controller方法里,先判断url和controller方法映射是否建立,没有则参考loadSpringUrlMappings建立
4、总结

本文更多的是讲解jquery ajax springmvc 如何将多个请求封装成一个请求的实现原理,至于本文提供的代码不能直接运行,仅供参考。