在做前端页面优化时,大厂都会通过将多个资源文件以逗号分隔,一次性下载下来,减少请求数,从而提升页面加载速度。除了静态资源请求外,在页面初始化时,有时我们会用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 如何将多个请求封装成一个请求的实现原理,至于本文提供的代码不能直接运行,仅供参考。