zoukankan      html  css  js  c++  java
  • 说说SpringMVC从http流到Controller接口参数的转换过程

    一,前言

    谈起springMVC框架接口请求过程大部分人可能会这样回答:负责将请求分发给对应的handler,然后handler会去调用实际的接口。核心功能是这样的,但是这样的回答未免有些草率。面试过很多人,大家彷佛约定好了的一般,给的都是这样"泛泛"的标准答案。最近开发遇到了这样的两个场景:

    • 1>,上游的回调接口要求接受类型为application/x-www-form-urlencode,请求方式post,接受消息为xml文本。
    • 2>,对接系统动态生成文件(文件实时变更,采用chunk编码),导致业务系统无法预览文件(浏览器会直接下载),采用中转接口对文件流进行转发。

    针对上述需求,如何开发rest风格的接口解决呢?

    二、request的生命周期

    我们知道,当一个请求到达后端web应用(mvc架构的应用)监听的端口, 率先被拦截器拦截到,然后转交到对应的接口。我们知道底层的数据必定是数据流形式的,那么他是怎么把流转成接口需要的参数,从而发起调用的呢?此时我们便需要去研究DispathServlet的处理逻辑了。

    2.1 DispatchServlet具备的职能

    • handler 容器
    • handler 前、后置处理器
    • 请求转发(交由HandlerApdater.handler()执行)
    • 响应结果转发

    具体入口代码如下(DipatchServlet.doDispatch):

    
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    
    			try {
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// 找到与请求匹配的handler
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == null) {
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    
    				// 找到与请求匹配的HandlerAdpater
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    				// ... 省略部分代码
    
                                    // handler 前置处理器
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
    				// handler 调用: 会实际调用到我们的controller接口
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    
    				applyDefaultViewName(processedRequest, mv);
                                    // handler 后置处理
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
                            
                            // 返回结果分发
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			// 省略部分代码
    		}
    	}
    
    

    这个接口就是我们寻常所说的handler的转发逻辑。但是我们也知道了实际上去调用我们controller接口的是HandlerAdapter

    2.2 HandlerAdapter具备的职能

    从上述我们知道了请求的转发过程,现在我们要弄清楚handler怎么调用到我们的controller接口的(以RequestMappingHandlerAdapter为例)。

    • argumentResolvers 参数解析器,提供了supportsParameter()、resolveArgument()两个方法来告诉容器是否能解析该参数以及怎么解析
    • returnValueHandlers 返回值解析器,
    • modelAndViewResolvers 模型视图解析器
    • messageConverters 消息转换器,

    跟踪源码发现(RequestMappingHandlerAdapter.invokeHandlerMethod()),他调用Controller接口发生再ServletInvocableHandlerMethod.invokeAndHandle()方法。看一下主体逻辑:

    
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    			Object... providedArgs) throws Exception {
    
                    // 调用controller接口 
    		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    		
                    // ... 省略部分代码
    
    		try {
                            // 处理返回结果
    			this.returnValueHandlers.handleReturnValue(
    					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    		}
    		catch (Exception ex) {
    			if (logger.isTraceEnabled()) {
    				logger.trace(formatErrorForReturnValue(returnValue), ex);
    			}
    			throw ex;
    		}
    	}
    
    

    调用controller接口的方法跟踪源码会发现,主要是通过request寻找到正确的参数解析器,然后去解析参数,这里我们以@RequestBody标注的参数为例,看其是如何解析的:
    (RequestResponseBodyMethodProcessor.readWithMessageConverters())

    
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
    			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
    
    		MediaType contentType;
    		boolean noContentType = false;
    		
                    //... 省略部分代码
    
    		EmptyBodyCheckingHttpInputMessage message;
    		try {
    			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
    
    			for (HttpMessageConverter<?> converter : this.messageConverters) {
    				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
    				GenericHttpMessageConverter<?> genericConverter =
    						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
    				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
    						(targetClass != null && converter.canRead(targetClass, contentType))) {
    					if (message.hasBody()) {
    						HttpInputMessage msgToUse =
    								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
    						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
    								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
    						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
    					}
    					else {
    						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
    					}
    					break;
    				}
    			}
    		}
    		catch (IOException ex) {
    			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
    		}
    
                    // ... 省略部分代码        
            
    		return body;
    	}
    
    
    

    可以看到其实就是简单的找到适配的MessageConvert,调用其read方法即可。把参数解析出来之后,发起对controller接口调用。至此从发起请求到落到controller接口的过程就是这样子的。

    2.3 总结从容器接受到请求到交付到controller接口的过程。

    上图较为完整的描述了从http报文字节流到controller接口java对象的过程,返回的处理是类型的流程不在赘述。

    三、总结

    有章节二知道了生命周期,我们知道严格意义上,对于问题一,我们只需要定义一个HandlerMethodArgumentResolver去专门解析类似参数(实际上我们用@RequestBody修饰的参数,那么只需要定义一个MessageConvert即可),然后注入到容器即可。针对问题二,其实只要不要覆盖原生的MessageConverts对于文件流的输出本身SpringMVC就是支持的,但是因为我们通常注入MessageConvert是通过WebMvcConfigurerAdapter实现会导致默认的转换器丢失需要特别注意。

  • 相关阅读:
    学员操作——隔行变色
    jQuery基础及选择器(1)
    学员操作——组合继承
    JavaScript面向对象
    学员操作——创建继承person的student子类
    学员操作——flower函数
    JavaScript操作DOM(2)
    JavaScript操作DOM(1)
    学员操作——制作秒表定时器
    学员操作——制作5s关闭广告
  • 原文地址:https://www.cnblogs.com/enjoyall/p/14524610.html
Copyright © 2011-2022 走看看