Springmvc ModelAndView踩过的坑之HttpServletResponse response
先抛出问题。以下两个方法声明有毛区别:
@RequestMapping(value = "/rg")
public void rg(@PathVariable Long pageId, @PathVariable Long moduleId) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("what", "haha");
sendJsonpResultJson(result);
}
@RequestMapping(value = "/rg")
public void rg(HttpServletResponse response,@PathVariable Long pageId, @PathVariable Long moduleId) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("what", "haha");
sendJsonpResultJson(result);
}
这是在一个Controller里面的接口方法声明,这两个方法,一个声明了
HttpServletResponse response,
另一个没有,他们看似没有区别,但是spring mvc的套路里面,他们在特殊场景下的区别大的你想哭。
先描述问题的源头:
某天刮风,飘来了一个接口需要处理
http://localhost:8088/1/2/rg.html
这个接口没有什么特殊,GET请求,返回JSON数据,由于习惯使用g.html而不是g.json,再为了兼容jsonp。然而,依赖@ResponseBody注解的方式,对jsonp支持不够完美。
因此,方法g里面,直接操作response,具体处理的地方是另一个地方,利用Filter+ThreadLocal实现的,因此,在g方法中无需声明HttpServletResponse就能达到目的
HttpServletResponse response = this.getResponse();
if (StringUtils.isNotBlank(contentType)) {
response.setContentType(contentType);
} else {
response.setContentType("application/json");
}
response.setCharacterEncoding(SystemConstant.ENCODING_UTF_8);
response.getWriter().print(obj);
这样处理一下,接口也的确返回了数据,但是reponse.status一直是500。问题描述完毕。
怎么了,你累了,说好的200呢?
首先找到500的原因.
spring mvc里面,我配置了根据客户端的不同的请求决定不同的view进行响应的视图解析器
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="0"/>
<property name="defaultViews">
<list>
<bean id="mappingJackson2JsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> <!-- for application/json -->
<!--<ref bean="mappingJackson2JsonView"/>-->
</list>
</property>
</bean>
实验证明,如果我不配置这个解析器,页面会直接404,其实这是同一个问题,因此我去掉这个解析器,页面立即显示了闪花眼镜的tomcat404

看到错误信息,加上404,联想到/1/2/1/2/rg这个资源没有找到,然而,我们本也没打算给它配资源,get请求回去的,是json数据。
在DispaterSerlvet的方法doDispatch中,ModelAndView被赋值,利用这个入口,可以找到原因。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
循序打入断点,到了解析方法参数的地方。
如果mv(即ModuleAndView)为null,则不会去寻找资源,因此开始寻找这个void请求,为毛还返回不为null的mv。
往下走,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod中,调用getModelAndView
mavContainer.isRequestHandled()
为真,那么我们就达到目的了。(我为什么会这么推测了,因此Controller参数含response和不含response的情况下,我分别跟踪了代码。为什么想到需要response参数呢,因为别人家的接口都正确返回了200状态,我家的为什么不按套路,只有对比了。)
return getModelAndView(mavContainer, modelFactory, webRequest);
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
....
往下走
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle中的判断条件
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest); if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
....
这是请求Controller层接口的实现方法,由于我们的rg方法是void返回类型,因此,这里的returnValue是null,如果满足
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled())
就好,而带response参数的请求
mavContainer.isRequestHandled()
为true。
往下走
最终
HandlerMethodArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
隐藏了我们想要的真相。
对于参数response,他对应的方法参数解析器是
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (mavContainer != null) {
mavContainer.setRequestHandled(true);
}
mavContainer是ModelAndView的容器,里面存放了很多信息,这里不深究了。
这里
mavContainer.setRequestHandled(true);
设置了请求已经被处理的标识,这样
mavContainer.isRequestHandled()
就为真了。
最后在方法上加上了response参数后,最终返回的ModelAndView为null,也就不会去找资源
/1/2/1/2/rg
也就不会出现404了。
总结
HttpServletResponse httpServletResponse参数神奇的原因是
ServletResponseMethodArgumentResolver
它做了特殊处理,带上这个参数的接口,都会被认为请求已经被处理了。
在springmvc里面,一个参数声明与否,并不是等价的,即使你没有用到,他也有存在的意义
Springmvc ModelAndView踩过的坑之HttpServletResponse response的更多相关文章
- web开发实战--弹出式富文本编辑器的实现思路和踩过的坑
前言: 和弟弟合作, 一起整了个智慧屋的小web站点, 里面包含了很多经典的智力和推理题. 其实该站点从技术层面来分析的话, 也算一个信息发布站点. 因此在该网站的后台运营中, 富文本的编辑器显得尤为 ...
- Spring 4.2.2以上版本和swagger集成方案和踩过的坑
因为公司使用的spring版本太高,在集成swagger的时候会存在一些问题,而网上的很多实例大多都是版本比较低的,为了是朋友们少才坑,我这边将集成的过程记录一下: 1. 引入spring.swagg ...
- 项目中踩过的坑之-sessionStorage
总想写点什么,却不知道从何写起,那就从项目中踩过的坑开始吧,希望能给可能碰到相同问题的小伙伴一点帮助. 项目情景: 有一个id,要求通过当前网页打开一个新页面(不是当前页面),并把id传给打开的新页面 ...
- "开发路上踩过的坑要一个个填起来————持续更新······(7月30日)"
欢迎转载,请注明出处! https://gii16.github.io/learnmore/2016/07/29/problem.html 踩过的坑及解决方案记录在此篇博文中! 个人理解,如有偏颇,欢 ...
- 【转载】Fragment 全解析(1):那些年踩过的坑
http://www.jianshu.com/p/d9143a92ad94 Fragment系列文章:1.Fragment全解析系列(一):那些年踩过的坑2.Fragment全解析系列(二):正确的使 ...
- Redis Cluster踩过的坑
Redis Cluster踩过的坑请参考如下链接:http://www.iteye.com/blogs/subjects/Redis_Cluster_Devops
- 第八篇:web之前端踩的一些坑
前端踩的一些坑 前端踩的一些坑 本节内容 事件代理 清除标签的所有事件 bootstrap的模态框自定义方法 ajax在django里面实现post提交 ajax提交数据嵌套 1.事件代理 之前写 ...
- 使用ffmpeg视频编码过程中踩的一个坑
今天说说使用ffmpeg在写视频编码程序中踩的一个坑,这个坑让我花了好多时间,回头想想,非常多时候一旦思维定势真的挺难突破的.以下是不对的编码结果: ...
- 那些年踩过的坑之:first-child伪类选择器
原文:那些年踩过的坑之:first-child伪类选择器 :first-child 选择器用于选取属于其父元素的首个子元素的指定选择器.——w3school 嗯,乍一看好像说的不是很明白,因此这个选择 ...
随机推荐
- 数据权限设计——基于EntityFramework的数据权限设计方案:一种设计思路
前言:“我们有一个订单列表,希望能够根据当前登陆的不同用户看到不同类型的订单数据”.“我们希望不同的用户能看到不同时间段的扫描报表数据”.“我们系统需要不同用户查看不同的生产报表列”.诸如此类,最近经 ...
- 最长上升子序列(logN算法)
例如:1 7 3 5 9 4 8 一个序列,比如说a[]={1,7,3,5,9,4,8},找出它的最长上升子序列的个数,很明显是4个,可以是{1,3,5,9},{1,3,5,8}或者{1,3,4,8} ...
- html5客户端本地存储之sessionStorage及storage事件
首先您可以看一下<JavaScript本地存储实践(html5的localStorage和ie的userData)>sessionStorage和上文中提到的localStorage非常相 ...
- PTA 数据结构 银行业务队列简单模拟
仅供参考,请勿粘贴 设某银行有A.B两个业务窗口,且处理业务的速度不一样,其中A窗口处理速度是B窗口的2倍 -- 即当A窗口每处理完2个顾客时,B窗口处理完1个顾客.给定到达银行的顾客序列,请按业务完 ...
- PHPMailer 发送邮件(二)
发现PHPMailer又做了较大的更新,以前发送邮件的方法已不太适用,刚好要做一个实验,需要搭建个环境,这里使用Gmail进行测试,现记录下来. 传送地址Github: PHPMailer 基本要求的 ...
- 第一数学归纳法 vs 第二数学归纳法 vs 良序定理
相关: 第一数学归纳法 vs 第二数学归纳法 vs 良序定 第二数学归纳法:硬币问题和堆垛游戏 第一数学归纳法:施塔特中心的地板砖 良序原理:算术基本定理的证明 From : Mathematics ...
- 一起写框架-Ioc内核容器的实现-基础功能-ComponentScan支持多包扫描(六)
实现功能 1.我们看到@ComponentScan注解一个开始定义就是需要支持,扫描多个包,将多个包的类名获取到.现在就实现这个功能. 实现思路 根据传入的字符串数组,获得多个包下的类全限制名. 实现 ...
- HTML知识速递
1.html的定义 超文本标记语言(Hypertext Markup Language,HTML)通过标签语言来标记要显示的网页中的各个部分.一套规则,浏览器认识的规则 浏览器按顺序渲染网页文件,然后 ...
- 从零开始搭建Vue组件库 VV-UI
前言: 前端组件化是当今热议的话题之一,也是我们在开发单页应用经常会碰到的一个问题,现在我们有了功能非常完善的Element-UI.各个大厂也相继宣布开源XXX-UI.但是也会存在一些问题,比如每个公 ...
- android wear开发:为可穿戴设备创建一个通知 - Creating a Notification for Wearables
注:本文内容来自:https://developer.android.com/training/wearables/notifications/creating.html 翻译水平有限,如有疏漏,欢迎 ...