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爲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裏面,一個參數聲明與否,並不是等價的,即使你沒有用到,他也有存在的意義


新博客地址:http://www.cnblogs.com/windliu 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章