SpringMVC請求處理之對方法引數的處理
前言
講完了DispatchServlet(也可以說是SpringMVC框架)的初始化之後,我們再接著看DispatchServlet處理請求的原理,也可以說是SpringMVC處理請求的原理。今天就先來看看SpringMVC對方法引數的處理。
我們先給出一個測試的類
package com.wangcc.controller;
import java.util.Date;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wangcc.entity.Player;
@Controller
// 不是以/開頭的,springmvc會自動幫你新增/
@RequestMapping("/test")
public class TestController {
@RequestMapping("testRb")
@ResponseBody
public Player testRb(@RequestBody Player player) {
return player;
}
@RequestMapping("testEntity")
@ResponseBody
public Player testEntity(Player player) {
return player;
}
@RequestMapping("testEntityWithRp" )
@ResponseBody
public Player testEntityWithRp(@RequestParam Player player) {
return player;
}
@RequestMapping("/testDate")
@ResponseBody
public Date testDate(Date date) {
return date;
}
}
我們之前已經講過,對帶有@Controller註解的Bean以及其方法上有@RequestMapping註解的對應的url請求的處理,呼叫的是RequestMappingHandlerAdapter的invokeHandleMethod方法。
private ModelAndView invokeHandleMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
}
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
就是通過這個方法得到了ModelAndView例項,所以當完整的走完這個方法之後,也就對請求的處理的主幹部分走完了。今天我們就來看這個方法的一小部分。
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
SpringMVC處理方法引數
- 我們先看下上面ServletInvocableHandlerMethod例項的獲取
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
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;
}
1.使用我們在初始化RequestMappingHandlerMapping時註冊到AbstractHandlerMapping的urlMap時封裝的HandlerMethod例項handlerMethod為引數構建一個ServletInvocableHandlerMethod例項。
2.分別以RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers屬性注入到requestMethod的argumentResolvers屬性和returnValueHandlers屬性中。
這裡需要講解下RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers怎麼得到的以及內容是什麼。
我們在講解RequestMappingHandlerMapping的時候提到了InitializingBean介面,而我們發現RequestMappingHandlerAdapter也實現了這個介面,那麼我們就知道了在初始化這個類的時候是需要執行他的afterPropertiesSet方法,而這兩個屬性的注入就是在這個方法裡完成的。
afterPropertiesSet
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);
}
}
我們先通過getDefaultArgumentResolvers得到一個HandlerMethodArgumentResolver集合
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;
}
然後將這個集合放入到HandlerMethodArgumentResolverComposite例項中。然後把這個例項賦給argumentResolvers屬性。
那麼returnValueHandlers屬性同理。
接著分析requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
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; } } else if (StringUtils.hasText(this.responseReason)) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { 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; } }
我們看下第一行
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
這一行就已經得到了這個方法的返回值了。我們進入這個方法看。
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Invoking ["); sb.append(getBeanType().getSimpleName()).append("."); sb.append(getMethod().getName()).append("] method with arguments "); sb.append(Arrays.asList(args)); logger.trace(sb.toString()); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]"); } return returnValue; }
我們發現第一行就是對方法引數的處理,嗯,終於找到我們今天要重點講解的地方了。
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); } throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; }
1.先通過this.argumentResolvers.supportsParameter(parameter)來找到能處理該方法引數的HandlerMethodArgumentResolver例項。
2.然後通過this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);來呼叫HandlerMethodArgumentResolver例項的resolveArgument方法來處理引數。
supportsParameter
HandlerMethodArgumentResolverComposite
@Override public boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null; } private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) { if (logger.isTraceEnabled()) { logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + parameter.getGenericParameterType() + "]"); } if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
這段程式碼不難理解,就是在我們開始講解的注入的HandlerMethodArgumentResolver集合裡面篩選出能夠處理引數的例項。
我們以這個方法為例:
@RequestMapping("testRb") @ResponseBody public Player testRb(@RequestBody Player player) { return player; }
相應的例項就是RequestResponseBodyMethodProcessor,我們瞅一眼他的supportsParameter方法就一目瞭然了。
RequestResponseBodyMethodProcessor
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); }
所以引數上有@RequestBody註解的都會使用這個例項來處理引數。
接著看看是如何呼叫resolveArgument方法的
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
所以呼叫的就是RequestResponseBodyMethodProcessor的resolveArgument方法了。
resolveArgument
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return arg;
}
我們先使用readWithMessageConverters來處理引數
readWithMessageConverters
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
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() {
// Form POST should not get here
return pushbackInputStream;
}
};
}
return super.readWithMessageConverters(inputMessage, methodParam, paramType);
}
在對資料做一些封裝處理後,最後會呼叫父類AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法
@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();
Class<T> targetClass = (Class<T>)
ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
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);
}
}
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);
}
這裡會在messageConverters集合中選擇一個合適的HttpMessageConverter來處理資料,這裡的messageConverters就是RequestMappingHandlerAdapter中的屬性,該屬性的注入具體在SpringMVC配置檔案解析(六)中有說明。
而RequestResponseBodyMethodProcessor是在初始化的時候注入messageConverters屬性的,回頭看getDefaultArgumentResolvers,有一句
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
但是遺憾的是,我們在所有的messageConverters集合中都找不到能夠處理這個資料的HttpMessageConverter,其中ByteArrayHttpMessageConverter能處理這種MediaType(使用get方式的http://localhost:8080/SpringMVC/test/testRb?name=kobe&age=39的MediaType是application/octet-stream),但是他只支援byte型別,而我們這裡的引數是Player這種自定義型別。
沒有任何的HttpMessageConverter可以處理,所以就導致了報錯,本來按照程式應該是報如下錯誤。
public HttpMediaTypeNotSupportedException(MediaType contentType, List<MediaType> supportedMediaTypes) {
this(contentType, supportedMediaTypes, "Content type '" + contentType + "' not supported");
}
但是實際結果卻是http 400 bad request。這個我們需要再看看到底是什麼原因。
到這裡,就把第一個方法的引數處理過程分析完了。
那麼需要如何更改才能使得不報錯了,我們可以不使用application/octet-stream這中MediaType來傳輸資料了,我們只要把他改成application/json,使用json格式來傳遞輸出就可以使用處理Json格式的Convert來處理資料了,而處理完引數之後的操作我們留到以後再分析。
ServletModelAttributeMethodProcessor
@RequestMapping("testEntity")
@ResponseBody
public Player testEntity(Player player) {
return player;
}
我們接著看第二個方法,先還是在HandlerMethodArgumentResolverComposite的supportsParameter方法中篩選出適合的HandlerMethodArgumentResolver來處理。
得到的答案是ServletModelAttributeMethodProcessor
他的註冊方式如下:
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
我們看看這個類先
public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
}
//ModelAttributeMethodProcessor
public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
this.annotationNotRequired = annotationNotRequired;
}
初始化ServletModelAttributeMethodProcessor時,會呼叫父類ModelAttributeMethodProcessor的骨構造方法,而且supportsParameter也是在父類中,我們看看這個方法。
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
return true;
}
else if (this.annotationNotRequired) {
return !BeanUtils.isSimpleProperty(parameter.getParameterType());
}
else {
return false;
}
}
如果引數是有@ModelAttribute註解的就支援,如果沒有這個註解,當構造方法的實參是true時,如果Method的引數型別不是簡單型別也支援,因為有一個實參為true的ServletModelAttributeMethodProcessor被註冊,並且Method的引數型別是Player,不是簡單型別,所以符合。
直接看resolveArgument方法,這個方法的實現還是在父類中
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, webRequest);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
這個過程的具體實現先不分析了,以後有空再細說,主要就是
通過DataBinder例項化了Employee物件,並寫入了對應的屬性,最後把這個例項物件返回給我們。
RequestParamMethodArgumentResolver
@RequestMapping("testEntityWithRp")
@ResponseBody
public Player testEntityWithRp(@RequestParam Player player) {
return player;
}
還是一樣,通過篩選,得到了對應的HandlerMethodArgumentResolver是RequestParamMethodArgumentResolver,對應的註冊程式碼
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
看看他的構造方法
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
super(beanFactory);
this.useDefaultResolution = useDefaultResolution;
}
public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null);
}
例項化的時候呼叫了父類AbstractNamedValueMethodArgumentResolver的構造方法。
supportsParameter方法在本類中實現,resolveArgument在父類中實現。
先看supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(paramType)) {
String paramName = parameter.getParameterAnnotation(RequestParam.class).value();
return StringUtils.hasText(paramName);
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
else if (MultipartFile.class.equals(paramType) || "javax.servlet.http.Part".equals(paramType.getName())) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(paramType);
}
else {
return false;
}
}
}
當Method引數有@RequestParam註解時,如果此時引數實現了Map介面的時候,@RequestParam註解需要具有value屬性,否則不支援,如果有@RequestPart註解,不支援,如果引數是簡單型別,支援。如果是MultipartFile型別,且是javax.servlet.http.Part,支援。
顯然我們這個方法符合這個要求。我們接著看resolveArgument
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveDefaultValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
handleMissingValue(namedValueInfo.name, parameter);
}
arg = handleNullValue(namedValueInfo.name, arg, paramType);
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveDefaultValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
arg = binder.convertIfNecessary(arg, paramType, parameter);
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
仔細閱讀原始碼,你會發現在處理引數的時候會使用request.getParameter(引數名)即request.getParameter(“player”)得到,很明顯我們的引數傳的是name=1&age=3。因此得到null,RequestParamMethodArgumentResolver處理missing value會觸發MissingServletRequestParameterException異常。
那需要如何處理呢,很簡單,去掉@RequestParam註解就好了,這樣就給方法2一樣了。
@InitBinder註解
我們繼續看方法四
@RequestMapping("/testDate")
@ResponseBody
public Date testDate(Date date) {
return date;
}
上面我們分析過RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor,他們一個支援簡單型別一個支援非簡單型別,那麼這裡的Date型別到底是簡單型別還是非簡單型別呢,看下BeanUtils.isSimpleProperty(paramType);的原始碼就知道了,他屬於簡單型別,所以使用的是RequestParamMethodArgumentResolver。這時我們使用request.getParameter(“date”)得到了日期字串,到這裡還是一切正常的,但是後面的使用DataBinder找到合適的屬性編輯器進行型別轉換時,最終找到java.util.Date物件的建構函式 public Date(String s),而由於我們傳遞的格式不是標準的UTC時間格式,因此最終觸發了IllegalArgumentException異常。
所以要解決這個問題的方法最簡單就是把日期格式設定為標準的UTC時間格式,但是這樣並不符合我們的日常習慣,我們肯定是想能夠使用請求中的那種日期格式的,那我們能怎麼辦呢。其實要實現這個功能並不難。在TestController中新增如下程式碼
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
是的只要新增這幾行程式碼就可以了。
那到底是為什麼呢?
@InitBinder註解在例項化ServletInvocableHandlerMethod的時候被注入到WebDataBinderFactory中的,而WebDataBinderFactory是ServletInvocableHandlerMethod的一個屬性。在RequestMappingHandlerAdapter的invokeHandleMethod方法中的getDataBinderFactory就是得到的WebDataBinderFactory。
我們來把目光轉向getDataBinderFactory方法
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
// Global methods first
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache .entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
}
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
return createDataBinderFactory(initBinderMethods);
}
這個方法就是篩選出有@InitBinder註解的方法,將其注入到DataBinderFactory中。
我們需要重點關注的就是
methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() {
@Override
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
}
};
篩選出有@InitBinder註解的方法。
之後RequestParamMethodArgumentResolver通過WebDataBinderFactory建立的WebDataBinder裡的自定義屬性編輯器找到合適的屬性編輯器(我們自定義的屬性編輯器是用CustomDateEditor處理Date物件,而testDate的引數剛好是Date),最終CustomDateEditor把這個String物件轉換成Date物件。