詳解SpringMVC中Controller的方法中參數的工作原理


Spring MVC中Controller的處理方法的參數可以是Integer,String,自定義對象,ServletRequest,ServletResponse,ModelAndView等等,非常靈活。本文將分析SpringMVC是如何對這些參數進行處理的,使讀者能夠處理自定義的一些參數。

先來看幾個示例:

@Controller public class UserController { @RequestMapping(value = "/user/testRequestBody") @ResponseBody public User testRequestBody(@RequestBody User user) { return user; } @RequestMapping(value = "/user/testCustomObj") @ResponseBody public User testCustomObj(User user) { return user; } @RequestMapping(value = "/user/testRequestParam") @ResponseBody public User testRequestParam(@RequestParam User user) { return user; } @RequestMapping("/user/testDate") @ResponseBody public Date testDate(Date date) { return date; } }

首先這是一個Controller,有4個方法。他們對應的參數分別是帶有@RequestBody的自定義對象、自定義對象、帶有@RequestParam的自定義對象、日期對象。

接下來我們一個一個方法進行訪問看對應的現象是如何的。

首先第一個testRequestBody:

第二個testCustomObj:

 

第三個testRequestParam:

 

第四個testDate:

 

為何User參數會被解析,帶有@RequestParam的User參數不會被解析,甚至報錯?

為何日期類型不能被解析?

SpringMVC到底是如何處理這些方法的參數的?

@RequestBody、@RequestParam這兩個注解有什么區別?

帶着這幾個問題。我們開始進行分析。

在分析源碼之前,首先讓我們來看下SpringMVC中兩個重要的接口。

兩個接口分別對應請求方法參數的處理、響應返回值的處理,分別是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler

/** * Strategy interface for resolving method parameters into argument values in * the context of a given request. */
public interface HandlerMethodArgumentResolver { /** * Whether the given {@linkplain MethodParameter method parameter} is * supported by this resolver. */
    boolean supportsParameter(MethodParameter parameter); /** * Resolves a method parameter into an argument value from a given request. */ Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; }
/** * Strategy interface to handle the value returned from the invocation of a * handler method . */
public interface HandlerMethodReturnValueHandler { /** * Whether the given {@linkplain MethodParameter method return type} is * supported by this handler. */
    boolean supportsReturnType(MethodParameter returnType); /** * Handle the given return value by adding attributes to the model and * setting a view or setting the * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true} * to indicate the response has been handled directly. */
    void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }

SpringMVC處理請求大致是這樣的:

首先被DispatcherServlet截獲,DispatcherServlet通過handlerMapping獲得HandlerExecutionChain,然后請求HandlerAdapter。

HandlerAdapter在內部對於每個請求,都會實例化一個ServletInvocableHandlerMethod進行處理,ServletInvocableHandlerMethod在進行處理的時候,會分兩部分別對請求跟響應進行處理

之后HandlerAdapter得到ModelAndView,然后做相應的處理。

本文將重點介紹ServletInvocableHandlerMethod對請求以及響應的處理。

/** * Invokes the method and handles the return value through one of the * configured {@link HandlerMethodReturnValueHandler}s. */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { /** * Invoke the method after resolving its argument values in the context of the given request. * Argument values are commonly resolved through HandlerMethodArgumentResolvers. */ Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.responseReason)) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { /** * Handle the given return value by adding attributes to the model and * setting a view or setting the ModelAndViewContainer#setRequestHandled flag to true * to indicate the response has been handled directly. */
        this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } }

1. 處理請求的時候,會根據ServletInvocableHandlerMethod的屬性argumentResolvers(這個屬性是它的父類InvocableHandlerMethod中定義的)進行處理,其中argumentResolvers屬性是一個HandlerMethodArgumentResolverComposite類(這里使用了組合模式的一種變形),這個類是實現了HandlerMethodArgumentResolver接口的類,里面有各種實現了HandlerMethodArgumentResolver的List集合。

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { protected final Log logger = LogFactory.getLog(getClass()); private final List<HandlerMethodArgumentResolver> argumentResolvers =
            new LinkedList<HandlerMethodArgumentResolver>(); private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
            new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256); }

2. 處理響應的時候,會根據ServletInvocableHandlerMethod的屬性returnValueHandlers(自身屬性)進行處理,returnValueHandlers屬性是一個HandlerMethodReturnValueHandlerComposite類(這里使用了組合模式的一種變形),這個類是實現了HandlerMethodReturnValueHandler接口的類,里面有各種實現了HandlerMethodReturnValueHandler的List集合。

public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler { protected final Log logger = LogFactory.getLog(getClass()); private final List<HandlerMethodReturnValueHandler> returnValueHandlers =
        new ArrayList<HandlerMethodReturnValueHandler>(); }

ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers這兩個屬性都是在ServletInvocableHandlerMethod進行實例化的時候被賦值的(使用RequestMappingHandlerAdapter的屬性進行賦值)。

private ServletInvocableHandlerMethod createRequestMappingMethod( HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { ServletInvocableHandlerMethod requestMethod; requestMethod = new ServletInvocableHandlerMethod(handlerMethod); requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); requestMethod.setDataBinderFactory(binderFactory); requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); return requestMethod; }

RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers這兩個屬性是在RequestMappingHandlerAdapter進行實例化的時候被Spring容器注入的。

@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans
 initControllerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }

其中默認的ArgumentResolvers:

/** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments
    if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }

默認的returnValueHandlers:

/** * Return the list of return value handlers to use including built-in and * custom handlers provided via {@link #setReturnValueHandlers}. */
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(); // Single-purpose return value types
    handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); handlers.add(new ListenableFutureReturnValueHandler()); // Annotation-based return value types
    handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); // Multi-purpose return value types
    handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types
    if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all
    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers; }

使用@ResponseBody注解的話最終返回值會被RequestResponseBodyMethodProcessor這個HandlerMethodReturnValueHandler實現類處理。

我們通過源碼發現,RequestResponseBodyMethodProcessor這個類其實同時實現了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver這兩個接口。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); } @Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null || returnType.getMethodAnnotation(ResponseBody.class) != null); } }

RequestResponseBodyMethodProcessor支持的請求類型是Controller方法參數中帶有@RequestBody注解,支持的響應類型是Controller方法帶有@ResponseBody注解。 

@Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException { mavContainer.setRequestHandled(true); // Try even with null return value. ResponseBodyAdvice could get involved.
 writeWithMessageConverters(returnValue, returnType, webRequest); }

RequestResponseBodyMethodProcessor響應的具體處理是使用消息轉換器。

@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name); if (argument != null) { validate(binder, parameter); } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return argument; }

處理請求的時候使用內部的readWithMessageConverters方法。

@Override protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException { final HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest); InputStream inputStream = inputMessage.getBody(); if (inputStream == null) { return handleEmptyBody(methodParam); } else if (inputStream.markSupported()) { inputStream.mark(1); if (inputStream.read() == -1) { return handleEmptyBody(methodParam); } inputStream.reset(); } else { final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream); int b = pushbackInputStream.read(); if (b == -1) { return handleEmptyBody(methodParam); } else { pushbackInputStream.unread(b); } inputMessage = new ServletServerHttpRequest(servletRequest) { @Override public InputStream getBody() throws IOException { // Form POST should not get here
                return pushbackInputStream; } }; } return super.readWithMessageConverters(inputMessage, methodParam, paramType); } private Object handleEmptyBody(MethodParameter param) { if (param.getParameterAnnotation(RequestBody.class).required()) { throw new HttpMessageNotReadableException("Required request body content is missing: " + param); } return null; }

然后會執行父類(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。

/** * Create the method argument value of the expected parameter type by reading * from the given HttpInputMessage.*/ @SuppressWarnings("unchecked") protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException { MediaType contentType; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } if (contentType == null) { contentType = MediaType.APPLICATION_OCTET_STREAM; } Class<?> contextClass = methodParam.getContainingClass(); for (HttpMessageConverter<?> converter : this.messageConverters) { if (converter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter; if (genericConverter.canRead(targetType, contextClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + targetType + "] as \"" + contentType + "\" using [" + converter + "]"); } return genericConverter.read(targetType, contextClass, inputMessage); } } Class<T> targetClass = (Class<T>) ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class); if (converter.canRead(targetClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + targetClass.getName() + "] as \"" + contentType + "\" using [" + converter + "]"); } return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage); } } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); }

下面來我們來看看常用的HandlerMethodArgumentResolver實現類(本文粗略講下,有興趣的讀者可自行研究)。

1. RequestParamMethodArgumentResolver

 支持帶有@RequestParam注解的參數或帶有MultipartFile類型的參數

2. RequestParamMapMethodArgumentResolver

  支持帶有@RequestParam注解的參數 && @RequestParam注解的屬性value存在 && 參數類型是實現Map接口的屬性

3. PathVariableMethodArgumentResolver

支持帶有@PathVariable注解的參數 且如果參數實現了Map接口,@PathVariable注解需帶有value屬性

4. MatrixVariableMethodArgumentResolver

支持帶有@MatrixVariable注解的參數 且如果參數實現了Map接口,@MatrixVariable注解需帶有value屬性 

5. RequestResponseBodyMethodProcessor

 本文已分析過

6. ServletRequestMethodArgumentResolver

 參數類型是實現或繼承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod這些類。

(這就是為何我們在Controller中的方法里添加一個HttpServletRequest參數,Spring會為我們自動獲得HttpServletRequest對象的原因)

7. ServletResponseMethodArgumentResolver

 參數類型是實現或繼承或是ServletResponse、OutputStream、Writer這些類

8. RedirectAttributesMethodArgumentResolver

 參數是實現了RedirectAttributes接口的類

9. HttpEntityMethodProcessor

 參數類型是HttpEntity

從名字我們也看的出來, 以Resolver結尾的是實現了HandlerMethodArgumentResolver接口的類,以Processor結尾的是實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的類。

 

下面來我們來看看常用的HandlerMethodReturnValueHandler實現類。

1. ModelAndViewMethodReturnValueHandler

返回值類型是ModelAndView或其子類

2. ModelMethodProcessor

返回值類型是Model或其子類

3. ViewMethodReturnValueHandler

返回值類型是View或其子類 

4. HttpHeadersReturnValueHandler

返回值類型是HttpHeaders或其子類  

5. ModelAttributeMethodProcessor

返回值有@ModelAttribute注解

6. ViewNameMethodReturnValueHandler

返回值是void或String

其余沒講過的讀者可自行查看源碼。

下面開始解釋為何本文開頭出現那些現象的原因:

1. 第一個方法testRequestBody以及地址 http://localhost:8080/user/testRequestBody?name=zhangsan&age=10

這個方法的參數使用了@RequestBody,被RequestResponseBodyMethodProcessor進行處理。之后根據http請求頭部的contentType然后選擇合適的消息轉換器進行讀取。

很明顯,我們的消息轉換器只有默認的那些跟部分json以及xml轉換器,且傳遞的參數name=1&age=3,傳遞的頭部中沒有content-type,默認使用了application/octet-stream,因此觸發了HttpMediaTypeNotSupportedException異常

解放方案: 我們將傳遞數據改成json,同時http請求的Content-Type改成application/json即可(post請求?)。

2. testCustomObj方法以及地址 http://localhost:8080/user/testCustomObj?name=zhangsan&age=10

這個請求會找到ServletModelAttributeMethodProcessor這個resolver。默認的resolver中有兩個ServletModelAttributeMethodProcessor,只不過實例化的時候屬性annotationNotRequired一個為true,1個為false。這個ServletModelAttributeMethodProcessor處理參數支持@ModelAttribute注解,annotationNotRequired屬性為true的話,參數不是簡單類型就通過,因此選擇了ServletModelAttributeMethodProcessor,最終通過DataBinder實例化User對象,並寫入對應的屬性。

3 testRequestParam方法以及地址 http://localhost:8080/user/testRequestParam?name=zhangsan&age=10

這個請求會找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在處理參數的時候使用request.getParameter(參數名)即request.getParameter("user")得到,很明顯我們的參數傳的是name=zhangsan&age=10。因此得到null,RequestParamMethodArgumentResolver處理missing value會觸發MissingServletRequestParameterException異常。 

解決方案:去掉@RequestParam注解,讓ServletModelAttributeMethodProcessor來處理。

4. testDate方法以及地址 http://localhost:8080/user/testDate?date=2014-05-15

這個請求會找到RequestParamMethodArgumentResolver。因為這個方法與第二個方法一樣,有兩個RequestParamMethodArgumentResolver,屬性useDefaultResolution不同。RequestParamMethodArgumentResolver支持簡單類型,ServletModelAttributeMethodProcessor是支持非簡單類型。最終步驟跟第三個方法一樣,我們的參數名是date,於是通過request.getParameter("date")找到date字符串(這里參數名如果不是date,那么最終頁面是空白的,因為沒有@RequestParam注解,參數不是必須的,RequestParamMethodArgumentResolver處理null值返回null)。最后通過DataBinder找到合適的屬性編輯器進行類型轉換。最終找到java.util.Date對象的構造函數 public Date(String s),由於我們傳遞的格式不是標准的UTC時間格式,因此最終觸發了IllegalArgumentException異常。

 Ref:

https://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html


免責聲明!

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



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