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

  • 相关阅读:
    Mac 常用快捷键
    Ubuntu 14.04 Flash 安装
    Ubuntu 14.04 Chrome Firefox & Opera 浏览器的安装
    RedHat 7.0 Flash 安装
    UiPath Level 1-Lesson 13. Project Organization Introduction
    UiPath Level 1-Lesson 12. Debugging & Exception
    UiPath Level 1-Lesson 11. Email Automation Introduction
    UiPath Level 1-Lesson 10. PDF
    UiPath Level 1-Lesson 9. Excel & Data Tables
    UiPath Level 1-Lesson 8. Advanced Citrix Automation
  • 原文地址:https://www.cnblogs.com/windliu/p/6377023.html
Copyright © 2020-2023  润新知