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

wKiom1eF8HGgb1A5AABx0oL7rKE246.png-wh_50

看到錯誤信息,加上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里面,一個參數聲明與否,並不是等價的,即使你沒有用到,他也有存在的意義


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM