Spring MVC 中的参数绑定

参数绑定入口

    @RequestMapping(value = "/saveUser", method = {RequestMethod.POST })
    public ResponseEntity postData(@RequestBody body1, User user, String username, String passwd){

        return new ResponseEntity<>(new ResultData(ResultData.ResultState.SUCCESS, true), HttpStatus.OK);
    }

在上面的方法中,spring MVC框架通过获取到的http请求分别为不同的参数类型进行赋值,即参数绑定。首先确定参数绑定的入口,DispatcherServlet是处理请求的入口,在该类中获取HandlerMapping实例,其中AbstractHandlerMethodMapping加载了所有的Controller的方法,通过反射获取方法上的注解和HandlerMethod,建立url与HandlerMethod直接的关联。这样当DispatcherServlet处理请求时,就会通过url交给对应的HandlerMethod处理。

    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;
    }

以上是InvocableHandlerMethod对请求的处理,HandlerMethod实例中包括Controller类中方法的必要信息,如Method, MethodParameter[],对应的bean等,InvocableHandlerMethod持有下面这三个实例,spring就是通过这三个实例进行的参数绑定。

    private WebDataBinderFactory dataBinderFactory;

    private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();

    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

HandlerMethodArgumentResolver参数解析器

按spring一贯方式,spring通过策略模式给一种功能针对不同情况提供了不同的实现。spring参数解析器对不同的参数类型给出了不同的实现,如@RequestParam、@PathVariable、@RequestBody注解的参数、Bean类型的参数、普通类型的参数等。

public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

HandlerMethodArgumentResolver是参数解析器的顶层接口,supportsParameter用于判断该解析器能够解析的参数类型,resolveArgument用于具体的解析并返回解析结果。
HandlerMethodArgumentResolverComposite保存了spring默认的参数解析器实现,一种改进的组合模式,其实现的supportsParameter方法实际是遍历持有的解析器是否支持相应的参数解析,resolveArgument是调用持有的解析器的resolveArgument方法。

1. 简单参数解析器RequestParamMethodArgumentResolver

RequestParamMethodArgumentResolver能够解析的参数类型是有RequestParam注解的参数或者简单类型的参数,对应get 方式中queryString的值和post方式中 body data的值。在重载的resolveArgument方法中,4-7行确定参数的名称,并根据参数的名称从NativeWebRequest获取参数对应的值,然后将参数值转换为MethodParameter对应的类型。这个转换操作是由DataBinder完成的,DataBinder由WebDataBinderFactory负责创建,WebDataBinderFactory是在InvocableHandlerMethod创建的。

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);
            try {
                arg = binder.convertIfNecessary(arg, paramType, parameter);
            }
            catch (ConversionNotSupportedException ex) {
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
            catch (TypeMismatchException ex) {
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());

            }
        }

        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }

2. @RequestBody和@ResponseBody注解的参数解析器RequestResponseBodyMethodProcessor

这个类还实现了HandlerMethodReturnValueHandler两个接口,用于对处理方法返回值进行处理的策略接口。resolveArgument的参数解析主要是在readWithMessageConverters方法中实现,在这个方法中利用HttpMessageConverter机制将java对象写入到HttpOutputMessage或者从HttpInputMessage读入流到java对象。最后,进行参数验证并返回。

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;
    }

HttpMessageConverter机制
HttpMessageConverter是消息转换器的顶层接口,从接口方法名可以看到主要是成对出现的判断是否可写可读(通过接收的媒体类型判断)以及读写的方法。在servlet标准中,可以用javax.servlet.ServletRequest获取ServletInputStream和ServletOutputStream,spring分别将其转换为HttpInputMessage和HttpOutputMessage接口,可以通过getBody方法获得对应的输入流和输出流。

public interface HttpMessageConverter<T> {
    boolean canRead(Class> clazz, MediaType mediaType);
    boolean canWrite(Class clazz, MediaType mediaType);
    List<MediaType> getSupportedMediaTypes();
    T read(Classextends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;
}

HttpMessageConverter主要实现类如下图,引用自http://www.scienjus.com/custom-http-message-converter/
Spring MVC 中的参数绑定_第1张图片

3. 自定义bean类型的参数解析器ModelAttributeMethodProcessor

ModelAttributeMethodProcessor能够解析ModelAttribute注解的参数,或者非简单类型的参数。通过databinder对参数进行赋值。applyPropertyValues是databinder中的方法,获取属性访问器通过java内省的方式对参数对象中的属性进行赋值。
哈哈

protected void applyPropertyValues(MutablePropertyValues mpvs) {
        try {
            // Bind request parameters onto target object.
            getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
        }
        catch (PropertyBatchUpdateException ex) {
            // Use bind error processor to create FieldErrors.
            for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
                getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
            }
        }
    }

属性访问器(PropertyAccessor)
PropertyAccessor是一个顶层接口,子类实现的setPropertyValue对属性进行赋值,TypeConverter是Spring类型转换体系中最顶层的接口,ConfigurablePropertyAccessor继承了这两个接口,还提供了设置ConversionService的方法,BeanWrapperImpl是这个接口的具体实现类,支持嵌套属性、索引属性(数组|集合|Map)。
BeanWrapperImpl是在applyPropertyValues的getPropertyAccessor中创建的,BeanWrapperImpl构造方法中,设置属性,创建TypeConverterDelegate,并进行目标对象的内省分析,将分析结果保存到cachedIntrospectionResults。cachedIntrospectionResults缓存了由spring封装的ExtendedBeanInfo和GenericTypeAwarePropertyDescriptor。
setPropertyValues迭代所有封装的PropertyValue,调用setPropertyValue(PropertyValue pv),这是一个递归方法,getPropertyAccessorForPropertyPath通过属性名propertyName获取当前属性的子属性,若为空则返回当前属性,接着调用getPropertyNameTokens获取封装的PropertyTokenHolder,propertyName实际是一个属性表达式,PropertyTokenHolder保存了表达式相关的属性:
(1)actualName保存当前级别属性的实际名称,为[前的字符串,
(2)canonicalName为actualName再加上[key1][key2][key3]..这种形式保存当前级别属性的实际名称,为下一个.前的字符串
(3)keys代表当前级别属性中所有位于[与]间的key或索引所组成的数组
最后都调用setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv)方法赋值,此方法中支持array、map、list属性,如果属性值为空且autoGrowNestedPaths为真则创建对应的实例,如果PropertyTokenHolder的keys为空调用类型的默认构造函数。

    public void setPropertyValue(PropertyValue pv) throws BeansException {
        AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = (AbstractNestablePropertyAccessor.PropertyTokenHolder)pv.resolvedTokens;
        if(tokens == null) {
            String propertyName = pv.getName();

            AbstractNestablePropertyAccessor nestedPa;
            try {
                nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);
            } catch (NotReadablePropertyException var6) {
                throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);
            }

            tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));
            if(nestedPa == this) {
                pv.getOriginalPropertyValue().resolvedTokens = tokens;
            }

            nestedPa.setPropertyValue(tokens, pv);
        } else {
            this.setPropertyValue(tokens, pv);
        }

    }

类型转换 Converter
spring在参数赋值前,需要将传入的字符串转换为目标对象的实际类型,databinder通过ConversionService实例进行类型转换(spring3之前使用PropertyEditor来转换)。具体的转换类可实现Converter 接口,它支持从一个 Object 转为另一个 Object 。ConversionService是一个顶层接口,ConverterRegistry接口用于管理具体的Converter转换类,GenericConversionService是这两个接口的实现。

public interface ConversionService {

    boolean canConvert(Class sourceType, Class targetType);

     T convert(Object source, Class targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}

ConverterFactory 接口是一种获取Converter的方式,限制Converter转换的目标类都继承相同的父类,如StringToEnumConverterFactory,从 String 到 Enum 的转换。GenericConverter 接口支持在多个不同的原类型和目标类型之间进行转换。

参考

request参数解析器
databinder
SpringMVC中WebDataBinder的应用及原理
反射获取一个方法中的参数名(不是类型)
详解SpringMVC中Controller的方法中参数的工作原理
属性访问器(PropertyAccessor)
SpringMVC 之类型转换 Converter

你可能感兴趣的:(java,spring)