大家好,关于html5响应式网站案例源码分享很多朋友都还不太明白,今天小编就来为大家分享关于响应式网页设计代码的知识,希望对各位有所帮助!
本文主要根据两种常见的错误场景展开,深入解析SpringMVCHTTP请求与响应流程。而整个SpringMVCHTTP请求与响应过程涉及的内容远不止于此。主要内容如下:
常见的两种错误场景介绍;
HTTP请求与处理源码解析;
两种错误场景解决方案;
涉及的设计模式介绍。
相关版本:
Maven:apache-maven-3.3.9;
SpringMVC:4.1.1.RELEASE;
Tomcat-Maven-Plugin:2.2。
介绍方式:代码+文字说明+源码截图(为减小篇幅,因此源码部分采用截图的方式)。读者阅读时,结合前面列出的流程图/主要操作步骤,再浏览。
一、常见的两种错误场景
本文中涉及的代码下载:https://github.com/wlmshuaia/JsonDemo。
1.场景1
jQuery以ajax方式访问SpringMVC接口时,如未显示指定Content-Type,则会显示‘415(UnsupportedMediaType)’错误。如:
前端代码片段:
functionfSave(url){varobj={};\nobj[‘cateId’]=$(“input[name=cateId]”).val();\nobj[‘cateName’]=$(“input[name=cateName]”).val();\n$.ajax({\nurl:url,\nmethod:’post’,\ndata:JSON.stringify(obj),//以json字符串方式传递\nsuccess:function(data){console.log(data);\n},\nerror:function(data){console.log(“error…”);console.log(data);\n}\n});\n}
后端接口代码片段为:
@RequestMapping(value=”/save-by-model-2″,method=RequestMethod.POST)@ResponseBodypublicCategorysaveByModel2(@RequestBodyCategorycategory){\ncategoryService.save(category);returncategory;\n}
2.场景2
SpringMVC接口定义返回Json格式数据时,一般有字符串和对象两种方式。而相同条件下,返回Json格式字符串时,中文会出现乱码。如:
前端代码片段:
functionfSave(url){varobj={};\nobj[‘cateId’]=$(“input[name=cateId]”).val();\nobj[‘cateName’]=$(“input[name=cateName]”).val();\n$.ajax({\nurl:url,\nmethod:’post’,\ncontentType:’application/json’,\ndata:JSON.stringify(obj),//以json字符串方式传递\nsuccess:function(data){console.log(data);\n},\nerror:function(data){console.log(“error…”);console.log(data);\n}\n});\n}
后端接口片段:
@RequestMapping(value=”/save-by-map”,method=RequestMethod.POST)@ResponseBodypublicStringsaveByMap(@RequestBodyMap<String,Object>valMap){\ncategoryService.save(valMap);returnvalMap.toString();\n}@RequestMapping(value=”/save-by-map-2″,method=RequestMethod.POST)@ResponseBodypublicMap<String,Object>saveByMap2(@RequestBodyMap<String,Object>valMap){\ncategoryService.save(valMap);returnvalMap;\n}
二、HTTP请求与响应处理源码解析
SpringMVC中HTTP请求处理流程时序图如下:
从上图可以看出,所有的HTTP请求都会进入到DispatcherServlet类的doDispatch()方法。该方法中的主要工作为:
获取处理执行链对象:HandlerExecutionChainmappedHandler=getHandler(processedRequest);
获取处理适配器:HandlerAdapterha=getHandlerAdapter(mappedHandler.getHandler());
调用拦截器的preHandle方法;
调用具体的接口方法,并返回模型视图对象:mv=ha.handle(processedRequest,response,mappedHandler.getHandler());
调用拦截器的postHandle方法;
处理结果:processDispatchResult(processedRequest,response,mappedHandler,mv,dispatchException);。
注:本次源码解析为通过@EnableWebMvc方式启动,与配置文件方式启动时的源码略有不同(该方式在Spring3.2以后已被弃用),如HandlerMapping,HandlerAdapter的实现。
接下来深入SpringMVC处理HTTP请求的过程。
1.获取处理执行链对象
处理执行链类HandlerExecutionChain:由处理对象handler和拦截器列表interceptorList组成,通过HandlerMapping.getHandler()方法返回。
DispatcherServlet类中获取处理链对象的getHandler(HttpServletRequestrequest)方法实现为:
从上图可以看到,框架遍历所有的HandlerMapping对象,调用对应的hm.getHandler(request)方法,如果获取的处理执行链对象不为null则返回该处理执行链对象。
HandlerMapping:定义请求和处理对象之间的映射关系接口。HandlerMapping列表由SpringMVC初始化时寻找所有的HandlerMapping类组成。
SpringMVC中,默认的HandlerMapping实现有:
上图HandlerMapping列表中,RequestMappingHandlerMapping类为@Controller注解的类中的@RequestMapping注解方法创建RequestMappingInfo对象。而前面示例中定义的接口方法为@RequestMapping类型,因此此处取得的HandlerMapping类为该类。简化的类图如下:
RequestMappingInfo封装了@RequestMapping注解方法相关的状态如下:
而在RequestMappingHandlerMapping类中获取处理链对象则由父类AbstractHandlerMapping实现:
在AbstractHandlerMapping中,主要操作为2步:
获取处理器对象;
获取处理执行链。
1.1获取处理器对象
由子类AbstractHandlerMethodMapping实现:
上图实现中返回的处理器对象类型为处理方法(HandlerMethod)。主要操作为:
调用UrlPathHelper类从request中获取请求路径lookupPath;
根据请求路径获取处理方法。
在第2步中,SpringMVC会根据lookupPath作为key值从缓存的urlMap对象中获取HandlerMethod,如果根据key值未获取到对应的数据,则会遍历所有的urlMap数据列表。
注:urlMap中的数据在SpringMVC初始化时填入。
addMatchingMappings()方法中的实现为:
由于该机制的存在,如果定义的@RequestMapping与访问的路径不相等,则框架会遍历所有的@RequestMapping方法。如果数量很多时,则该部分会消耗较多时间。因此如该部分有性能问题,应尽量使@RequestMapping路径与访问路径匹配以减少遍历开销。
优化建议
SpringMVC-4.0.3以后的版本可通过WebMvcConfigurerAdapter.configurePathMatch(PathMatchConfigurerconfigurer)方法自定义实现UrlPathHelper,AntPathMatcher优化该问题;
修改RequestMappingHandlerMapping类中UrlPathHelper,AntPathMatcher的实现(代码参考);
定义@RequestMapping路径与访问路径一致,如访问路径为‘index.do’,则相应的@RequestMapping(value=”/index.do”);
减少单个项目中的@RequestMapping的数量。
1.2获取处理执行链
该方法根据传入的处理器对象建立处理执行链,将传入的处理对象和匹配的拦截器添加到处理执行链对象中,并返回:
2.获取处理适配器
HandlerAdapter:MVC框架的SPI接口,允许核心MVC工作流的参数化(这句话不是很懂…有会的读者欢迎指教)。
每一种处理请求的处理器类型必须实现该接口,DispatcherServlet类可根据该接口无限扩展,且DispatcherServlet根据该接口访问所有已安装的处理器对象。
而处理器类型设置为Object,其他的框架不用修改源码就能和SpringMVC整合。完整的接口介绍如下:
传入处理器对象,SpringMVC遍历所有的HandlerAdapter类,如果某个处理适配器支持该处理器类型,则返回该处理器:
supports(handler)接口判断某个具体的处理适配器是否支持传入的处理器对象,定义如下:
在RequestMappingHandlerAdapter类中,支持的处理器类型为HandlerMethod:
因此此处返回的HandlerAdapter实现类为:RequestMappingHandlerAdapter。
3.调用拦截器的preHandle方法
调用处理执行链对象的applyPreHandle()方法,代码如下:
可以看出框架会遍历在第1步:获取处理链对象中获取的拦截器对象,依次调用preHandle()方法,如果某次调用返回false,则会调用triggerAfterCompletion()方法,并返回false。triggerAfterCompletion()实现如下:
即依次调用拦截器对象的afterCompletion()方法。
4.调用具体的接口方法
调用第2步:获取的处理适配器对象的handle()方法处理请求。而RequestMappingHandlerAdapter类中处理请求的是handleInternal()方法。方法执行时序图如下:
从上图中可以看出,框架先创建ServletInvocableHandlerMethod对象,然后调用该对象的invokeAndHandle(webRequest,mavContainer)方法,最后获取ModelAndView对象。具体实现如下:
而invokeAndHandle()方法中,主要操作为:
根据request解析参数,并调用具体的方法,获取返回值(即调用invokeForRequest()方法);
调用返回值处理器处理返回值。
4.1调用具体的方法
该部分操作步骤为:
解析参数;
根据参数调用具体的方法,并获取返回值;
返回返回值。
解析参数时,会先获取方法定义的所有参数列表,然后根据每个具体的MethodParameter类型,调用具体的参数解析器类HandlerMethodArgumentResolver的解析参数方法resolveArgument()。
源码如下:
此处的argumentResolvers对象为HandlerMethodArgumentResolverComposite类,该类封装了一系列的参数解析器,属性如下:
解析时,根据HandlerMethodArgumentResolver.supportsParameter(MethodParametermethodParameter)判断该参数解析器是否支持该参数。
前面参数由于使用的是@RequestBody注解,因此调用RequestResponseBodyMethodProcessor类,典型的还有@RequestParam,@PathVariable,@RequestHeader等:
而RequestResponseBodyMethodProcessor类处理参数时,使用了HttpMessageConverter消息转换机制。
4.1.1HttpMessageConverter消息转换机制
该机制的流程为:
由于请求的content-type为application/json类型,所以此处调用的消息转换类为MappingJackson2HttpMessageConverter,默认的字符集为UTF-8:
而read()的实现为调用Jackson的类ObjectMapper.readValue()方法:
注:在调用read()方法之前,如果无匹配的HttpMessageConverter类,则会抛出HttpMediaTypeNotSupportedException异常;
在调用write()方法之前,如果无匹配的HttpMessageConverter类,则会抛出HttpMediaTypeNotAcceptableException异常。
解析完参数后,则通过反射机制调用具体的方法并获取返回值:
4.2处理返回值
该部分的逻辑和前面解析参数逻辑类似。主要为调用具体的HandlerMethodReturnValueHandler(返回值处理器)处理返回值。
框架中默认的返回值处理器有:
此处由于使用@ResponseBody注解,因此调用RequestResponseBodyMethodProcessor处理器。该处理器中,使用HttpMessageConverter消息转换机制(见4.1.1),调用write()方法处理返回值:
而在调用write()方法之前,会调用getProducibleMediaTypes()方法获取可生产的媒体类型,如果用户自定义RequestMapping的produces属性,则此处会返回该值;
如果用户未定义,则根据返回值Class类型,遍历系统中的消息转换器列表,获取支持的媒体类型列表:
5.调用拦截器的postHandle方法
调用拦截器对象的applyPostHandle()方法,代码如下:
从上图可以看出框架会遍历拦截器对象列表,以处理链对象中拦截器对象列表相反的顺序调用拦截器对象的postHandle()方法。
6.处理结果
调用processDispatchResult()方法,实现代码如下:
这部分处理主要分为如下几部分:
处理异常;
渲染ModelAndView对象;
调用处理执行链的triggerAfterCompletion()方法。
6.1处理异常
如果在前面的处理中抛出了异常,则会获取相应的模型视图对象。
有两种处理方式:如果异常对象为ModelAndViewDefiningException类型,则直接获取模型视图对象;否则的话调用当前系统内的处理异常解析器(HandlerExceptionResolver)处理:
如果某个异常解析器返回了有效的模型视图对象,则跳出循环。
此处的ExceptionHandlerExceptionResolver类通过用户自定义的@ExceptionHandler方法解析异常,如果用户未定义,则跳出该解析器。此处示例代码为:
@ExceptionHandler(Exception.class)publicStringhandlerException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionex){\nSystem.out.println(“handlerException…”);return”redirect:/error.do”;\n}
在该类的doResolveHandlerMethodException()方法中,会创建一个ServletInvocableHandlerMethod对象,然后调用该对象的invokeAndHandle()方法:
与前面处理正常HandlerMethod流程类似,就不深入探讨了。
6.2渲染ModelAndView对象
在渲染方法render()中,如果传入的mv对象是View引用类型,即为String字符串时,则调用当前的视图解析器ViewResolver解析该字符串,如当前配置了视图解析器为:
<!–viewpath–><beanid=”viewResolver”class=”org.springframework.web.servlet.view.InternalResourceViewResolver”>\n<propertyname=”prefix”value=”/”/>\n<propertyname=”suffix”value=”.html”/></bean>
则该实现会在视图解析器列表viewResolvers中:
在解析时,将会添加上对应的prefix,suffix,处理后的View对象为:
接下来则调用View对象的render()方法,根据提供的Model对象渲染该视图对象。
6.3调用处理执行链的triggerAfterCompletion()方法
该方法只调用在preHandle()方法中成功调用且返回为true的拦截器,且从列表后往前调用:
三、解决方案
根据前面的原理介绍可知,文章开头的两种场景错误都是由于在第4步:调用具体的接口方法出错:场景1为解析参数时出错,场景2为返回值处理时出错。
1.场景1出错原因
见4.1.1消息转换机制。场景1中,ajax请求如果未设置Content-Type则会使用默认的类型:application/x-www-form-urlencoded;charset=UTF-8。
而由于接收参数定义为@RequestBody,对应的参数解析器为RequestResponseBodyMethodProcessor类,调用HttpMessagConverter消息转换机制。
而SpringMVC中并无支持该类型的HttpMessageConverter类,因此抛出异常。
2.场景1解决方案
指定具体的Content-Type,如:
$.ajax({\nurl:url,\nmethod:’post’,\ncontentType:’application/json’,//解决415错误\ndata:JSON.stringify(obj),\nsuccess:function(data){console.log(data);\n},\nerror:function(data){console.log(“error…”);console.log(data);\n}\n});
3.场景2出错原因
见4.2处理返回值。场景2中,定义了@ResponseBody注解,调用的返回值处理器为RequestResponseBodyMethodProcessor,调用HttpMessagConverter消息转换机制。
返回的Class类型是String,而在SpringMVC的消息转换器列表中支持该返回值类型的消息转换器有StringHttpMessageConverter,MappingJackson2HttpMessageConverter支持的媒体列表有:
取得媒体列表后,会选取其中的一个:
而String返回值类型取得的媒体类型为text/plain;charset=ISO-8859-1,将该媒体类型设置为response的contentType,因此返回中文乱码:
而Category类支持的媒体类型只有两种,因此返回值正常:
4.场景2解决方案
在接口上方定义produces,如:
@RequestMapping(value=”/save-by-map”,method=RequestMethod.POST,javaproduces=”text/plain;charset=utf-8″)@ResponseBodypublicStringsaveByMap(@RequestBodyMap<String,Object>valMap){\ncategoryService.save(valMap);returnvalMap.toString();\n}
或者在springmvc的配置文件中配置消息转换器的媒体类型,如:
<mvc:annotation-driven>\n<!–消息转换器–>\n<mvc:message-convertersregister-defaults=”true”>\n<beanclass=”org.springframework.http.converter.StringHttpMessageConverter”>\n<propertyname=”supportedMediaTypes”value=”text/plain;charset=UTF-8″/>\n</bean>\n</mvc:message-converters></mvc:annotation-driven>
四、设计模式
本章内容不包括对具体的设计模式原理介绍,只介绍前面框架源码中出现的设计模式的应用场景,如读者对相关的设计模式原理有兴趣,可查看每小节的链接或者自行学习。
前面研究的原理中,涉及的设计模式如下:
1.模板方法模式
模板方法模式原理:http://blog.csdn.net/u012099869/article/details/78689257。
该模式在框架中使用的地方很多,主要作用为:定义算法的骨架。
如获取处理执行链方法中,getHandler()方法定义了算法的骨架如下:
而getHandlerInternal()则由具体的子类来实现:
2.适配器模式
该模式本质为:(接口不兼容时,通过)转换匹配,(从而)复用功能。
如HandlerAdapter的实现,可适配所有的处理器类型。是常见的适配器模式实现方式:
可以看出handle()方法传入handler对象,在实现类中操作传入的handler对象。
有新的处理器类型时,实现该接口即可。如@EnableMvc方式使用的RequestMappingHandlerAdapter,以及已经弃用的AnnotationMethodHandlerAdapter。
还有种缺省适配方式,由子类选择覆盖需要的方法。如WebMvcConfigurerAdapter类:
3.责任链模式
该模式本质为:分离对象的职责,动态的组合在一起。
常见的有两种实现方式:
责任链:请求在链上传递,有一个对象处理过则跳出;
功能链:请求在链上传递,链上的每个责任对象负责处理某一方面的功能,处理过不跳出,向下传递。
如HandlerExecutionChain的实现,为功能链的实现:
定义的拦截器列表interceptorList,循环调用preHandle()方法,处理用户请求之前,均会经过该拦截器列表中所有的对象处理。
4.策略模式
该模式本质为:分离算法,选择实现。即对应同一操作,提供不同的策略实现。
如参数解析器HandlerMethodArgumentResolver:
返回值处理器HandlerMethodReturnValueHandler:
5.工厂方法模式
该模式形式为:提供一个创建对象实例的接口,让子类决定实例化哪一个类。
如在RequestMappingHandlerAdapter.invokeHandleMethod()方法中,创建ServletInvocableHandlerMethod时,会传入WebDataBinderFactory对象。
而该类即为典型的工厂方法模式的实现:
在其默认的实现里,通过createBinderInstance()方法创建WebDataBinder对象,该实现可由子类自行扩展:
6.组合模式
该模式定义为:将对象组合成树型结构,以统一叶子对象和组合对象。
一般有以下两种场景:
统一整体和部分的操作;
统一地使用组合结构中的所有对象。
SpringMVC中参数解析时调用的HandlerMethodArgumentResolverComposite类,组合一系列的参数解析器,即为第2种用法:
还有HandlerMethodReturnValueHandlerComposite,WebMvcConfigurerComposite等。
7.单例模式
模式定义为:保证一个类仅有一个实例,并提供一个全局访问点。该模式在日常的开发中应用也非常广泛。
两种典型的实现方式:
懒汉式:一般为延迟加载方式,在使用的对象实例不存在时,再创建对象实例;
饿汉式:一般在初始化时,创建对象实例。
SpringMVC中,获取WebAsyncManager类时,即通过缓存的方式实现的懒汉式单例模式。将创建的WebAsyncManager对象缓存在ServletRequest中:
注:WebAsyncManager类为管理异步请求处理的主要类。
SpringMVC框架中还有很多使用的设计模式未被列出,以上列举的只是前面深入源码过程中比较典型的几个。
五、写在最后
以上就是我所理解的SpringMVC处理HTTP请求的过程。而前面所列出的也只是整个SpringMVC框架的冰山一角,还有很多等待着我们去探索。
在深入学习的过程中,感触最深的也是自己的无知,此处引用一句名言:越学习,越发现自己的无知。
本文篇幅较长,文中涉及的流程图画法也比较粗糙,希望这种技术交流的方式能给你一些启发。
好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!
