Spring以及Springboot 参数绑定原理及使用

前言

Spring-web给我们提供了丰富的参数绑定功能,本文主要说明参数绑定的原理以及使用方式,主要围绕@RequestBody @RequestParam @PathVariable这3种参数解析方式

在介绍三种参数解析方式之前先给出参数解析器的核心接口HandlerMethodArgumentResolver ,主要实现了其中两个方法 supportsParameter和resolveArgument,具体含义见如下码块

public interface HandlerMethodArgumentResolver {

	/**
	 * 根据给定的参数对象是不是支持当前参数解析器
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * 返回controller中所需的参数对像并赋值
	 */
	Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

@PathVariable

  • 原理

PathVariableMethodArgumentResolver是@PathVariable对应的参数解析器,其中resolveArgument()方法是在父类AbstractNamedValueMethodArgumentResolver中实现的,具体的流程如下图代码所示:

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //只支持参数带有PathVariable 注解的方式
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
            return false;
        }
        //如果参数是map类型的子类,注解value()必须有值
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
            return StringUtils.hasText(paramName);
        }
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {

        //从map中取出对应map中的参数值  至于map中的是在UriTemplateVariablesHandlerInterceptor中设置的
        Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
            HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
    }

    /**
     * 父类中的方法@see org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver
     * @return
     * @throws Exception
     */
    @Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        //获取参数名
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        //处理参数名称  有些场景是支持el表达式的
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }

        //获取参对饮的值
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }

        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), 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());

            }
        }

        //设置值到request的attribute的中
        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }
  • 使用说明

@PathVariable的使用方法,如下图代码片段所示,当我们使用rest风格时会大量使用到此注解。

    @RequestMapping("/path/{param0}/{param1}")
    public String pathValueParam(@PathVariable("param0") String param0, @PathVariable String param1) {
        System.out.println("param0 = " + param0 + "=====param1 = " + param1);
        return "hello";
    }

      /**
     * 当PathVariable中未指定值时采用参数名来匹配 
     * 所以这种方式也是可以获取到参数的
     * @param param0 
     * @return
     */
    @RequestMapping("/path/{param0}/{param1}")
    public String pathValueParam(@PathVariable String param0) {
        System.out.println("param0 = " +  "=====param1 = " + param0);
        return "hello";
    }

需要保证url{}中的值和@PathVariable中的值保持一致,当@PathVariable中的值在查找不到时调用该方法的时候会抛出MissingPathVariableException异常。

@RequestParam

  • 原理

RequestParamMethodArgumentResolver是@RequestParam对应的参数解析器,其中resolveArgument()方法是在父类AbstractNamedValueMethodArgumentResolver中实现的,具体的流程如下图代码所示:

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        //支持注解为RequestParam
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            //如果参数是map的子类 则name()必须有值 否则不支持
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
                return StringUtils.hasText(paramName);
            }
            else {
                return true;
            }
        }
        else {
            if (parameter.hasParameterAnnotation(RequestPart.class)) {
                return false;
            }
            //支持文件MultipartFile  类型的参数也会默认走到这个解析器
            parameter = parameter.nestedIfOptional();
            if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
                return true;
            }
            else if (this.useDefaultResolution) {
                return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
            }
            else {
                return false;
            }
        }
    }

    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        MultipartHttpServletRequest multipartRequest =
            WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);

        //处理文件
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }

        Object arg = null;
        if (multipartRequest != null) {
            List<MultipartFile> files = multipartRequest.getFiles(name);
            if (!files.isEmpty()) {
                arg = (files.size() == 1 ? files.get(0) : files);
            }
        }
        //非文件 从parameters中获取
        if (arg == null) {
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }

    /**
     * 父类中的方法@see org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver
     * @return
     * @throws Exception
     */
    @Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        //获取参数名
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        //处理参数名称  有些场景是支持el表达式的
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }

        //获取参对饮的值
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }

        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), 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());

            }
        }

        //设置值到request的attribute的中
        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }
  • 使用说明

因为@RequestParam注解的参数是通过getParameterValues来获取参数,parameters中包含的参数官方给出的说明

For HTTP servlets, parameters are contained in the query string or posted form data.

及form表单(contentType=application/x-www-form-urlencoded)的数据和url中?之后拼接的参数

    /**
     * get请求 url:http://localhost:8080/request/param?param0=a&param1=b
     */
    @RequestMapping("/request/param")
    public String requestParam(@RequestParam(name ="param0") String param0,@RequestParam(name ="param1")  String param1) {
        System.out.println("param0 = " + param0 + "=====param1 = " + param1);
        return "hello";
    }

@RequestParam 默认的required是true也就意味着必填,否则会抛出MissingServletRequestParameterException的异常。需要保证name值和实际的传值名称保持一致(name= "param0" 缺省时要保证和参数名一致也是可以的)。

@RequestBody

下面我们着重来讲解一下其中还涉及了MessageConverter,其对应的参数解析器为RequestResponseBodyMethodProcessor,有且仅支持@RequestBody修饰的参数。在解析参数是采用可messageConverters来解析参数并生成对应的对象。我们最常用到的就是MappingJackson2HttpMessageConverter,它左右就是将我们提交的json数据转换成java对象。

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}
     /**
	 * Throws MethodArgumentNotValidException if validation fails.
	 * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
	 * is {@code true} and there is no body content or if there is no suitable
	 * converter to read the content with.
	 */
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		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 adaptArgumentIfNecessary(arg, parameter);
	}

注意:所有messageconverter遍历玩对象仍然未能处理,此时会抛出HttpMediaTypeNotSupportedException,主要是不支持的内容类型。

下面我们在来说一下messageconverter,当我们不想使用MappingJackson2HttpMessageConverter  想使用Fastjson来代替时我们需要自定义一个FastJson的消息转换器来解读数据。

下面是自定义并配置了一个messageConverter,仅需要实现GenericHttpMessageConverter接口定义的方法就行了

@Component("fastjson2HttpMessageConverter")
public class Fastjson2HttpMessageConverter implements GenericHttpMessageConverter<Object> {

    @Override
    public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
        return getSupportedMediaTypes().contains(mediaType);
    }

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        Object obj = JSON.parseObject(inputMessage.getBody(), type);
        return obj;
    }

    @Override
    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

    }

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Arrays.asList(MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON);
    }

    @Override
    public Object read(Class<?> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

    }
}

然后在xml中配置一下

    <!-- 添加注解驱动 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <ref bean="fastjson2HttpMessageConverter"></ref>
        </mvc:message-converters>
    </mvc:annotation-driven>

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值