SpringMVC RESTful 性能优化
使用RESTful风格的接口有如下优势:
RESTful:
@RequestMapping(path = "/list/cityId/{cityId}", method = RequestMethod.GET) @ResponseBody public String getJsonByCityId(@PathVariable Integer cityId)
非RESTful:
@RequestMapping(path = "/list/cityId", method = RequestMethod.GET) @ResponseBody public String getJsonByCityId(@RequestParam Integer cityId)
我们使用Apache JMeter对SpringMVC RESTful接口与非RESTful接口进行了性能测试:
*并发量为200
*测试在同一台机器上进行,执行业务逻辑相同,仅接口不同。
*为了证明的确是SpringMVC造成的问题,我们使用了最简单的业务逻辑,直接返回字符串。
由结果可见,非RESTful接口的性能是RESTful接口的两倍,且请求的最大响应时间是35毫秒,有99%的请求在20毫秒内完成。相比之下,RESTful接口的最大响应时间是436毫秒。
由于ModelService是一个对并发性能要求极高的系统,且被多个上层业务系统所依赖,所有请求需在50ms内返回,若超时则会引起上层系统的read timeout,进而导致502。所以需要对这一情况进行优化。
方案一:将所有的url修改为非RESTful风格(不使用@PathVariable)
这是最直接的方式,也是最能保证效果的方式。但是这么做需要修改的是ModelService中已有的全部100+个接口,同时也要修改客户端相应的调用。修改量太大,而且极有可能由于写错URL导致404。更令人不爽的是这种修改会导致接口没有了RESTful风格。故该方案只能作为备选。
方案二:对SpringMVC进行改造
根据实际现象以及测试的结果,几乎可以确定的是问题出在SpringMVC的RESTful路径查找中。所以我们对SpringMVC中的相关代码进行了调查。
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod (spring-webmvc-4.2.3.RELEASE)
路径匹配的过程中有如下代码:
List<Match> matches = new ArrayList<Match>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // No choice but to go through all mappings... addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); }
SpringMVC首先对HTTP请求中的path与已注册的RequestMappingInfo(经解析的@RequestMapping)中的path进行一个完全匹配来查找对应的HandlerMethod,即处理该请求的方法,这个匹配就是一个Map#get方法。若找不到则会遍历所有的RequestMappingInfo进行查找。
这个查找是不会提前停止的,直到遍历完全部的RequestMappingInfo。在遍历过程中,SpringMVC首先会根据@RequestMapping中的headers, params, produces, consumes, methods与实际的HttpServletRequest中的信息对比,剔除掉一些明显不合格的RequestMapping。如果以上信息都能够匹配上,那么SpringMVC会对RequestMapping中的path进行正则匹配,剔除不合格的。
接下来会对所有留下来的候选@RequestMapping进行评分并排序。最后选择分数最高的那个作为结果。所以使用非RESTful风格的URL时,SpringMVC可以立刻找到对应的HandlerMethod来处理请求。但是当在URL中存在变量时,即使用了@PathVariable时,SpringMVC就会进行上述的复杂流程。
从结果可见,这段匹配逻辑对性能的影响很大,URL数量越多,SpringMVC的性能越差,初步验证了我们从源码中得出的结论。在最近一次ModelService的更新中,接口数量翻了一倍,导致性能下降了一半,这也符合我们的结论。考虑到未来ModelService的接口必定会持续增加,我们肯定不能容忍在请求压力不断增加的情况下ModelService的性能反而不断下降的情况。所以现在我们要做的就是防止SpringMVC执行这种复杂的匹配逻辑,找到一种方式可以绕过它。
通过继承
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
我们可以实现自己的匹配逻辑。由于ModelService已经服务化,所以每个接口都有一个服务名,通过这个服务名即可直接找到对应的方法,并不需要通过@RequestMapping匹配的方式。而在服务消费端,由于服务消费端是通过服务名进行的方法调用,所以在服务消费端可以很直接地获取到服务名,把服务名加到HTTP请求的header中并不需要对代码进行大量的修改。
最终方案:
服务端:
- 在每个@RequestMapping中添加接口对应服务名的信息。
- 实现自己定义的HandlerMethod查询逻辑,在HandlerMethod注册时记录与之对应的服务名,在查询时通过HTTP请求头中的服务名查表获得HandlerMethod。
客户端:
- 调用服务时将服务名加入到HTTP请求头中
分析:
- 这样的查询时间复杂度是O(1)的,典型的空间换时间。理论上使用这样的查找逻辑的效率和非RESTful接口的效率是一样的。
- 由于HandlerMethod的注册是在服务启动阶段完成的,且在运行时不会发生改变,所以不用考虑注册的效率以及并发问题。
- SpringMVC提供了一系列的方法可以让我们替换它的组件,所以该方案的可行性很高。
实现细节:
我们要建立一个HandlerMethod与服务名的映射,保存在一个Map中。注意到在@RequestMapping中有一个name属性,这个属性并没有被SpringMVC用在匹配逻辑中。该属性是用来在JSP中直接生成接口对应的URL的,但是在AbstractHandlerMethodMapping.MappingRegistry中已经提供了一个name与Handler Method的映射,直接拿来用即可。所以我们只需要在每个接口的@RequestMapping中添加name属性,值为接口的服务名。在SpringMVC启动时会自动帮我们建立起一个服务名与Handler Method的映射。我们只要在匹配时从HTTP请求头中获取请求的服务名,然后从该Map中查询到对应的HandlerMethod返回。如果没有查询到则调用父类中的原匹配逻辑,这样可以保证不会对现有的系统造成问题。
*小细节:
因为RESTful接口存在@PathVariable,我们还需要调用handleMatch方法来将HTTP请求的path解析成参数。然而这个方法需要的参数是RequestMappingInfo,并不是HandlerMethod,SpringMVC也没有提供任何映射,所以我们还是要自己实现一个HandlerMethod => RequestMappingInfo的反向查询表。重写AbstractHandlerMethodMapping#registerMapping方法即可在@RequestMapping的注册阶段完成映射的建立。
1:自定义的MappingHandlerMapping
public class actMappingHandlerMapping extends RequestMappingHandlerMapping { private static Map<String, HandlerMethod> NAME_HANDLER_MAP = new HashMap<String, HandlerMethod>(); private static Map<HandlerMethod, RequestMappingInfo> MAPPING_HANDLER_MAP = new HashMap<HandlerMethod, RequestMappingInfo>(); @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { HandlerMethod handlerMethod = createHandlerMethod(handler, method); RequestMapping rMapping = AnnotationUtils.getAnnotation(method, RequestMapping.class); NAME_HANDLER_MAP.put(rMapping.name(), handlerMethod); MAPPING_HANDLER_MAP.put(handlerMethod, mapping); System.out.println("======================name=" + rMapping.name() + "=handlerMethod=" + handlerMethod.toString()); super.registerHandlerMethod(handler, method, mapping); } @Override protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { String api = request.getHeader("api"); HandlerMethod handlerMethod = NAME_HANDLER_MAP.get(api); if (StringUtils.isNotBlank(api) && handlerMethod != null) { handleMatch(MAPPING_HANDLER_MAP.get(handlerMethod), lookupPath, request); return handlerMethod; } return super.lookupHandlerMethod(lookupPath, request); } }2:Spring-servlet.xml配置添加
<bean name="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer"> <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService" /> </bean> </property> <property name="messageConverters"> <list> <ref bean="stringHttpMessageConverter" /> <ref bean="fastJsonHttpMessageConverter" /> </list> </property> </bean> <bean name="conversionService" class="org.springframework.format.support.DefaultFormattingConversionService" /> <bean name="handlerMapping" class="com.mact.flter.actMappingHandlerMapping" />
3:对ajax支持可能需要这个拦截器
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", "*"); httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); httpResponse.setHeader("Access-Control-Allow-Headers", "api"); if ("OPTIONS".equals(httpRequest.getMethod())) { httpResponse.setStatus(204); httpResponse.setHeader("Cache-Control", "no-cache"); } filterChain.doFilter(request, response); }
感谢达达技术分析
相关推荐
在实际开发中,还需要考虑安全性、性能优化、版本控制等问题,确保RESTful服务的质量和稳定性。 总结来说,Spring MVC提供了强大的工具和支持,使得实现RESTful服务变得简单而高效。通过合理的资源设计和HTTP方法的...
在IT行业中,Spring MVC是一个广泛使用的Java Web框架,它为构建高效、可维护的Web应用程序提供了强大的支持。本文将深入探讨如何在Spring ...记得在实际开发中根据项目需求进行调整和优化,保持代码简洁和可维护性。
在Spring 3.0框架中,Spring MVC是一个强大...`RestUrlRewriteFilter.java`可能是一个定制的过滤器,用于进一步优化和管理RESTful URL。通过合理地利用这些特性,开发者可以构建出高效、灵活且易于维护的RESTful服务。
在本教程中,我们将深入探讨如何...这只是一个简化的示例,实际项目中还需要考虑更多因素,如性能优化、异常处理、安全性等。通过不断学习和实践,你可以掌握更高级的RESTful设计原则和技巧,提升你的Web服务开发能力。
【前后端分离SpringMVC和RESTful理解】 1. **MVC模式详解** - MVC全称为Model-View-Controller,是一种将业务逻辑、数据处理和用户界面分离开的设计模式。 - Model(模型)是数据载体,通常用Java中的POJO类表示...
最后,SpringMVC是Spring框架的一个模块,专门用于构建Web应用,特别是RESTful风格的API。在本项目中,SpringMVC被用来构建RESTful接口,提供HTTP服务,使得外部系统可以通过HTTP请求调用服务。SpringMVC的模型-视图...
下面将详细介绍SpringMVC中RESTful CRUD的相关知识点。 首先,理解REST(Representational State Transfer,表述性状态转移)的概念。REST是一种网络应用程序的设计风格和开发方式,强调通过统一的资源标识符(URI...
3. **优化与最佳实践** - **使用FastJson配置**:可以通过FastJson提供的`JSONConfig`进行配置,比如设置日期格式、忽略空值等。 - **错误处理**:在处理JSON序列化和反序列化异常时,应捕获`JSONException`并给...
4. **优化静态资源加载**: - 使用CDN(内容分发网络)服务可以提高静态资源的加载速度,尤其是对于地理位置分布广泛的用户。 - 压缩和合并CSS与JavaScript文件可以减少HTTP请求次数,提高页面加载速度。 - 使用...
此外,它可能还会讨论最佳实践,如如何有效地测试SpringMVC应用,以及如何优化性能。 通过学习这些文档,你可以全面了解SpringMVC的工作原理,掌握如何利用它来构建高效、可维护的Web应用程序。理解MVC模式能帮助你...
6. **RESTful API增强**:SpringMVC 5.0进一步完善了对RESTful风格的支持,如使用`@GetMapping`、`@PostMapping`等简化HTTP方法的映射,以及`@RequestBody`和`@ResponseBody`用于JSON数据的交换。 7. **模板引擎...
5. **异常处理**:提供了一套统一的异常处理机制,将系统异常转化为用户友好的错误页面,便于进行异常管理和用户体验优化。 6. **国际化**:内置了便捷的国际化支持,可以轻松实现多语言环境下的应用开发。 7. **...
在实际开发中,我们需要考虑如何高效地管理和检索这些图片,例如使用文件名哈希来避免重名,以及采用适当的文件夹结构以优化存储和检索性能。 总的来说,构建一个类似七牛的图床服务,需要结合RESTful API的设计...
- **RESTful 支持**:SpringMVC 对 RESTful 风格的 URL 请求提供了良好的支持,这使得构建 RESTful Web 服务变得非常简单。 #### SpringMVC 与 Struts2 的比较 - **入口机制**:SpringMVC 使用 Servlet 作为入口,...
11. **MVC配置优化**:在SpringMVC 4.0中,可以使用Java配置替代XML配置,通过@Configuration和@Bean注解来定义bean和它们之间的关系,使得配置更清晰、更易于维护。 12. **Servlet 3.0支持**:SpringMVC 4.0充分...
Spring 3.1引入了一些重要的改进和优化,使得开发更高效,性能更卓越。 1. **SpringMVC架构** SpringMVC采用Model-View-Controller设计模式,负责处理HTTP请求,分发到相应的控制器,执行业务逻辑,并将结果渲染到...
3.1.1版本可能包含了一些bug修复和性能优化,让开发者在实际项目中能更好地利用MyBatis进行数据操作。 4. **RESTful接口**: REST(Representational State Transfer)是一种网络应用程序的设计风格和开发方式,基于...
- **RESTful风格的URL**:通过配置SpringMVC,可以轻松实现RESTful风格的URL,以提供更加清晰的API。 以上就是关于自定义SpringMVC的一些关键知识点。在实际开发中,理解并灵活运用这些组件,可以极大地提高Web应用...
此外,SpringMVC还支持RESTful风格的URL设计,方便构建Web服务。 总的来说,“精通SpringMVC”这本书很可能会涵盖SpringMVC的基本概念、配置、控制器设计、模型数据管理、视图解析、请求处理机制以及与其他Spring...