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为null,则不会去寻找资源,因此开始寻找这个void请求,为毛还返回不为null的mv。

往下走,

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod中,调用getModelAndView

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;
		}
		....

如果

mavContainer.isRequestHandled()

为真,那么我们就达到目的了。(我为什么会这么推测了,因此Controller参数含response和不含response的情况下,我分别跟踪了代码。为什么想到需要response参数呢,因为别人家的接口都正确返回了200状态,我家的为什么不按套路,只有对比了。)

往下走

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里面,一个参数声明与否,并不是等价的,即使你没有用到,他也有存在的意义

时间: 2024-12-14 07:31:02

Springmvc ModelAndView踩过的坑之HttpServletResponse response的相关文章

springmvc上传文件 踩过的坑

spring-root.xml中配置 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="9000000"/> <property name="maxInM

Spring 4.2.2以上版本和swagger集成方案和踩过的坑

因为公司使用的spring版本太高,在集成swagger的时候会存在一些问题,而网上的很多实例大多都是版本比较低的,为了是朋友们少才坑,我这边将集成的过程记录一下: 1. 引入spring.swagger的相关jar包(springfox-swagger2.springfox-swagger-ui),在pom.xml中配置: Xml代码 io.springfox springfox-swagger2 2.4.0 org.springframework spring-core org.spring

《C++之那些年踩过的坑(附录一)》

C++之那些年踩过的坑(附录一) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. [版权声明]转载请注明原文来自:http://www.cnblogs.com/GodA/p/6639526.html 本来上个月就开始动笔了,直到现在才发出来,实在太多事情.可能有些小朋友不知道写这一篇随笔的起因,那么你可以看一下我之前写的. 上一篇的最后,我提到了一个问题:代码优化.并留了一个小测试:无符号数与有符号数的性能比

【转载】Fragment 全解析(1):那些年踩过的坑

http://www.jianshu.com/p/d9143a92ad94 Fragment系列文章:1.Fragment全解析系列(一):那些年踩过的坑2.Fragment全解析系列(二):正确的使用姿势3.Fragment之我的解决方案:Fragmentation 本篇主要介绍一些最常见的Fragment的坑以及官方Fragment库的那些自身的BUG,这些BUG在你深度使用时会遇到,比如Fragment嵌套时或者单Activity+多Fragment架构时遇到的坑.如果想看较为实用的技巧,

初学spring boot踩过的坑

一.搭建spring boot环境 maven工程 pom文件内容 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-

用vue-cli搭建项目踩过的坑

1. 需要额外安装sass-loader: 我以为vue-cli会安装所有与css相关的loader,然并非,为这个死了很多脑细胞. npm install sass-loader node-sass webpack --save-dev 2. 修改style标签: 打开src目录下的components目录中的App.vue文件.然后修改 style标签如下: <style lang="sass" rel="stylesheet/sass"> 3. s

sqlalchemy 踩过的坑

记录下Sqlalchemy遇到的问题,不定时更新. 设置主键为非自增 sqlalchemy 在sql server中默认主键是自增的,如果在数据库设置的主键不是自增的,这个时候插入就会出现异常: 提示does not have the identity property 这个时候需要在主键中设置autoincrement=False,显示表示非自增,才能正常写入 sqlalchemy 踩过的坑

多线程和异步编程示例和实践-踩过的坑

上两篇文章,主要介绍了Thread.ThreadPool和TPL 多线程异步编程示例和实践-Thread和ThreadPool 多线程异步编程示例和实践-Task 本文中,分享两则我们在做多线程和异步编程中实际踩过的坑,实际生产环境遇到的问题,以及解决办法. 1. HttpClient 业务场景:使用HttpClient实现第三方业务推送,当第三方的Http服务器不通.或者返回很慢时 线程数暴涨 Asp.Net\Asp.Net MVC场景下,并发多线程导致的线程阻塞:HttpClient.Pos

1.MySQL5.7.19 安装配置踩过的坑

这篇文章主要是分享 安装MySQL时遇到的一些问题,以及解决方法. 第一步:下载MySQL 下载地址:https://dev.mysql.com/downloads/mysql/5.1.html#downloads 我下载的是5.7.19版本,下载地址里面也只有解压版,下载后再进行环境变量的配置就可以. 2.解压并安装Mysql5.7.19 (1)将下载的包解压到指定的路径,自己可以指定路径,我直接解压到了D盘,解压到了                         D:\mysq的目录下.则