SpringMVC常见组件之HandlerMethodArgumentResolver解析-1https://developer.aliyun.com/article/1382039
【5】ServletModelAttributeMethodProcessor
如下图所示其继承自ModelAttributeMethodProcessor,实现了HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler两个接口,也就是说其提供了参数解析、返回结果处理两种功能。
① supportsParameter
如下所示,如果参数标注了@ModelAttribute
注解或者参数非简单类型,则返回true。
@Override public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); }
② ServletModelAttributeMethodProcessor的几个方法
在正式分析参数解析前,我们先看一下ServletModelAttributeMethodProcessor的几个方法。其提供了createAttribute、getRequestValueForAttribute、getUriTemplateVariables、createAttributeFromRequestValue、bindRequestParameters以及resolveConstructorArgument六个方法。
① createAttribute
方法如下所示,首先从从URI Template Variables解析attributeName,如果得不到value则从request中解析。如果得到的value不为null,则尝试获取attribute(需要进行类型转换)。如果得到的value为null,则委托给基类处理。
@Override protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { String value = getRequestValueForAttribute(attributeName, request); if (value != null) { Object attribute = createAttributeFromRequestValue( value, attributeName, parameter, binderFactory, request); if (attribute != null) { return attribute; } } return super.createAttribute(attributeName, parameter, binderFactory, request); }
该方法是一个模板方法,分别调用了getRequestValueForAttribute
、createAttributeFromRequestValue
以及父类的createAttribute
方法。
② getRequestValueForAttribute
@Nullable protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) { Map<String, String> variables = getUriTemplateVariables(request); String variableValue = variables.get(attributeName); if (StringUtils.hasText(variableValue)) { return variableValue; } String parameterValue = request.getParameter(attributeName); if (StringUtils.hasText(parameterValue)) { return parameterValue; } return null; } protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) { @SuppressWarnings("unchecked") Map<String, String> variables = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (variables != null ? variables : Collections.emptyMap()); }
方法如上所示首先从请求域中获取HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE属性值Map variables,然后尝试从variables解析attributeName,如果得到值则返回。如果得不到值则采用基本的request.getParameter(attributeName);解析值,然后返回值或者null。
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + “.uriTemplateVariables”
③ createAttributeFromRequestValue
当获取到属性值后,将会创建一个Model Attribute。直白点就是获取一个DataBinder 得到conversionService 然后尝试进行类型转换,将转换后的值(是一个复杂类型哦,比如SysUser)返回或者返回null。
@Nullable protected Object createAttributeFromRequestValue(String sourceValue, String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { DataBinder binder = binderFactory.createBinder(request, null, attributeName); ConversionService conversionService = binder.getConversionService(); if (conversionService != null) { TypeDescriptor source = TypeDescriptor.valueOf(String.class); TypeDescriptor target = new TypeDescriptor(parameter); if (conversionService.canConvert(source, target)) { return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter); } } return null; }
④ resolveConstructorArgument
解析构造参数对象,首先调用基类的resolveConstructorArgument方法,如果得到value不为null,则返回。然后尝试从servletRequest获取属性HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;的值Map uriVars,然后尝试从uriVars中解析paramName获取值返回。
@Override @Nullable public Object resolveConstructorArgument(String paramName, Class<?> paramType, NativeWebRequest request) throws Exception { Object value = super.resolveConstructorArgument(paramName, paramType, request); if (value != null) { return value; } ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); if (servletRequest != null) { String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; @SuppressWarnings("unchecked") Map<String, String> uriVars = (Map<String, String>) servletRequest.getAttribute(attr); return uriVars.get(paramName); } return null; }
⑤ bindRequestParameters
绑定请求参数,这时从请求参数到解析后的目标参数非常重要的一步。如下所示其转换为ServletRequestDataBinder 然后调用servletBinder.bind(servletRequest);
方法进行处理。
@Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); Assert.state(servletRequest != null, "No ServletRequest"); ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; servletBinder.bind(servletRequest); }
这里我们可以先回顾一下ServletRequestDataBinder
的继承树示意图
关于.ServletRequestDataBinder#bind
更多详细过程可以参考博文SpringMVC常见组件之DataBinder数据绑定器分析。
③ 父类核心方法resolveArgument
@Override @Nullable public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 首先校验 mavContainer binderFactory 不能为null Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); //根据目标方法参数获取参数名字 String name = ModelFactory.getNameForParameter(parameter); // 获取@ModelAttribute注解信息 ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } // 目标方法参数对应的值对象 Object attribute = null; // 数据绑定结果,通常保存Erros BindingResult bindingResult = null; // 直接从model中获取 if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { // Create attribute instance--很重要的一步哦 try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } else { attribute = ex.getTarget(); } bindingResult = ex.getBindingResult(); } } //尝试进行数据绑定、类型转换、数据校验 if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
方法解释如下
① 校验mavContainer、binderFactory是否存在;
② 获取参数名字,如果标注了@ModelAttribute且其value不为null ,那么参数名字为value值;否则按照通用规则获取参数名;
③ 如果ModelAttribute注解不为null,则告诉mavContainer当前对象绑定;
④ 如果mavContainer有当前name(前面确定的参数名字,也就是key),那么从model中获取值对象attribute;
⑤ !④否则createAttribute(name, parameter, binderFactory, webRequest);获取一个值对象attribute;
⑥ 如果bindingResult为null(也就是前面没有抛出异常),那么执行如下步骤:
⑦ 创建WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
⑧ 如果binder.getTarget() != null,且未进行过绑定,那么调用bindRequestParameters(binder, webRequest);为target对象属性赋值;
⑨ 调用validateIfApplicable(binder, parameter);判断是否需要校验,需要校验则调用校验器进行处理;
⑩ 如果绑定结果有error,且方法参数有Errors类型但是没有紧跟此处的目标参数(也就是非SysUser User ,Errors errors格式),将会抛出异常BindException
(11) 如果参数类型与值对象attribute类型不合适,那么尝试进行转换;
(12) bindingResultModel 数据放入mavContainer的model中。bindingResultModel
数据实例如下所示:
- 下面我们看下这个过程中createAttribute方法。
① createAttribute
我们先回顾一下其子类ServletModelAttributeMethodProcessor的createAttribute方法:
① 尝试从URI Template Variables中获取参数name对应的值value;
② 如果①获取的value不为空则返回,否则尝试从request中获取参数name对应的value;
③ 如果前面获取的value不为null,则尝试创建一个值对象attribute不为null则返回;
④ 调用父类的createAttribute方法
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { MethodParameter nestedParameter = parameter.nestedIfOptional(); Class<?> clazz = nestedParameter.getNestedParameterType(); Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz); Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest); if (parameter != nestedParameter) { attribute = Optional.of(attribute); } return attribute; }
父类的createAttribute方法如上所示,首先获取原始的具体的MethodParameter ,然后获取其Class对象,拿到其构造器后反射实例化一个空对象attribute 。
② validateValueIfApplicable
如果可以,那么进行校验。方法会获取参数的注解,检测是否能够进行校验。如果可以校验,那么会调用当前数据绑定器拥有的校验器进行校验。如果校验结果有异常信息将会放到BindingResult
中。
protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter parameter, Class<?> targetType, String fieldName, @Nullable Object value) { for (Annotation ann : parameter.getParameterAnnotations()) { Object[] validationHints = determineValidationHints(ann); if (validationHints != null) { for (Validator validator : binder.getValidators()) { if (validator instanceof SmartValidator) { try { ((SmartValidator) validator).validateValue(targetType, fieldName, value, binder.getBindingResult(), validationHints); } catch (IllegalArgumentException ex) { // No corresponding field on the target class... } } } break; } } }
那么如何检测当前是否能够进行校验呢?如下所示如果注解是Validated
类型或者注解名字以Valid
开头,那么就可以进行校验。
@Nullable private Object[] determineValidationHints(Annotation ann) { Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); if (hints == null) { return new Object[0]; } return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); } return null; }
【6】PathVariableMethodArgumentResolver
路径变量解析器,也就是解析URI中的参数值赋予方法参数。如下图所示当url为/testUser/1
时,这里方法参数id为1。
① supportsParameter
方法如下所示,其支持解析两种类型:
@PathVariable !Map
也就是标注了@PathVariable
注解并且类型不是Map;@PathVariable("指定名字") Map map
也就是说标注了@PathVariable
注解并且类型是Map且@PathVariable
注解value值不为空
@Override public boolean supportsParameter(MethodParameter parameter) { if (!parameter.hasParameterAnnotation(PathVariable.class)) { return false; } if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class); return (pathVariable != null && StringUtils.hasText(pathVariable.value())); } return true; }
② resolveName
方法如下所示,从request获取key为HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE属性得到一个Map类型的uriTemplateVars 。然后尝试从uriTemplateVars 解析属性name得到value返回,如果uriTemplateVars 为null则直接返回null。
@Override @SuppressWarnings("unchecked") @Nullable protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (uriTemplateVars != null ? uriTemplateVars.get(name) : null); }
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE是什么呢?
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";。如下图所示:
什么时候将URL上的路径变量值放到请求中的呢?有两个地方(其实就是针对两大类HandlerMapping):
第一个地方
AbstractUrlHandlerMapping.lookupHandler() -->AbstractUrlHandlerMapping.buildPathExposingHandler -->chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); -->preHandle()方法 -->exposeUriTemplateVariables(this.uriTemplateVariables, request); -->request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
第二个地方是
AbstractHandlerMethodMapping.lookupHandlerMethod() ->RequestMappingInfoHandlerMapping.handleMatch() –>RequestMappingInfoHandlerMapping.extractMatchDetails() –>request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern); request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
③ PathVariableMapMethodArgumentResolver
我们顺带看一下其姊妹类PathVariableMapMethodArgumentResolver。如下所示其解析@PathVariable Map也就是说标注了@PathVariable注解,注解value为空,参数类型为Map。
@Override public boolean supportsParameter(MethodParameter parameter) { PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(ann.value())); }
其解析值就更简单了,拿到uriTemplateVars后直接根据是否为空选择返回new LinkedHashMap<>(uriTemplateVars)
或者Collections.emptyMap();
@Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { @SuppressWarnings("unchecked") Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (!CollectionUtils.isEmpty(uriTemplateVars)) { return new LinkedHashMap<>(uriTemplateVars); } else { return Collections.emptyMap(); } }