SpringBoot 参数处理器处理Optional和Pageable参数

一. 介绍

在实际应用中,处理器方法的参数各式各样,遇到 Optional< Boolean > 和 Pageable 参数,SpringBoot 是如何进行解析的呢?

@GetMaping("/test01/{id}")
public RestOpResult index(
    @PathVariable("endpointId") Integer endpointId,
    @RequestParam(name = "uri", required = false) String uri,
    @RequestParam(name = "authorized", required = false) String authorized,
    @RequestHeader(name = "x-auth-token") String authToken,
    @RequestHeader(name = "x-auth-groups") String authGroup,
    @Parameter(hidden = true)
    @RequestParam(name = "includeToken", required = false) Optional<Boolean> includeTokenParam,
    Pageable pageable,
    HttpServletRequest request) {
    
}

参数 5 和参数 6 对应的参数是 Optional< Boolean > 和 Pageable 类型,下面我们进到 SpringBoot 参数解析部分看看这俩特殊参数是如何进行解析的;

二. 参数解析

先进到 DispatcherServlet 类,直接在 invokeForRequest() 上打断点;

// -------------------------------- InvocableHandlerMethod ---------------------------------
public Object invokeForRequest(NativeWebRequest request,
                               @Nullable ModelAndViewContainer mavContainer,
                               Object... providedArgs) throws Exception {
	// 进行参数解析获取参数
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
}


// -------------------------------- InvocableHandlerMethod ---------------------------------
protected Object[] getMethodArgumentValues(NativeWebRequest request,
                                           @Nullable ModelAndViewContainer mavContainer,
                                           Object... providedArgs) throws Exception {
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    
    // 1. 轮询解析参数 parameters
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        
        // 2. 看参数解析器们是否支持解析参数 parameter
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            
            // 3. 参数解析器支持解析的情况下,进行参数解析
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            throw ex;
        }
    }
    
    // 返回解析出来的参数
    return args;
}

1. Optional < Boolean > 参数类型

对该参数类型,使用的参数解析器是 RequestParamMethodArgumentResolver,我们看下参数解析过程;

// ---------------------------- RequestParamMethodArgumentResolver -----------------------------
public final Object resolveArgument(MethodParameter parameter,
                                    @Nullable ModelAndViewContainer mavContainer,
                                    NativeWebRequest webRequest,
                                    @Nullable WebDataBinderFactory binderFactory) throws Exception {
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();

    Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }

    // 1. 获取参数值,从 request 中通过 request.getParameter() 获取参数值
    // 如果没有带值,arg == null
    // 如果带了值,值为 "true",arg == "true"
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    if (arg == null) {
        // 1.1 参数自带了默认值,即 @RequetParam 注解中带了 defaultValue 属性
        if (namedValueInfo.defaultValue != null) {
            arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
        }
        // 1.2 参数为必须项并且参数不是 Optional 类型
        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 = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
    }

    if (binderFactory != null) {
        WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
        try {
            // 2. 根据参数绑定器和converter去转换参数并绑定到参数上
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
        }
        catch (ConversionNotSupportedException ex) {
            throw new MethodArgumentConversionNotSupportedException();
        }
        catch (TypeMismatchException ex) {
            throw new MethodArgumentTypeMismatchException();
        }
        // Check for null value after conversion of incoming argument value
        if (arg == null && namedValueInfo.defaultValue == null &&
            namedValueInfo.required && !nestedParameter.isOptional()) {
            handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
        }
    }

    // 3. 参数解析的后置操作,一般不进行啥操作
    // 只有 PathVariableMethodArgumentResolver 会进行操作,会把解析出来的参数放到 request 的属性中
    // 感兴趣可以看看
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

    // 4. 返回解析并绑定后的参数
    return arg;
}

第 2 步中,这个 binder 对象如下,它最重要的属性是内部的 conversionService 对象,coversionService 内部的 converters 包含 128 个参数转换器 converter;

当我们不传参数时,解析出来的 arg 是 Optional.empty();

当我们传 true 时,解析出来的 arg 是 Optional.of(true);

后续了解即可!!!!假设传值为 true,解析过程如下,我们直接看 GenericConversionService 类;(WebConversionService 是 GenericConversionService 的实现类)

// -------------------------------- GenericConversionService ------------------------------
public Object convert(@Nullable Object source,
                      @Nullable TypeDescriptor sourceType,
                      TypeDescriptor targetType) {
    // source 值为 "true"
    // sourceType 值为 java.lang.String
    // targetType 值为 java.util.Optional<java.lang.Boolean>
    if (sourceType == null) {
        Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
        return handleResult(null, targetType, convertNullSource(null, targetType));
    }
    if (source != null && !sourceType.getObjectType().isInstance(source)) {
        throw new IllegalArgumentException();
    }
    
    // 1. 获取合适的 converter
    // 这里获取到的 converter 为 ObjectToOptionalConverter
    GenericConverter converter = getConverter(sourceType, targetType);
    if (converter != null) {
        
        // 2. 根据获取到的 converter 去转换 source 值
        Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
        return handleResult(sourceType, targetType, result);
    }
    return handleConverterNotFound(source, sourceType, targetType);
}

看 ObjectToOptionalConverter 是如何 converter source 值的;

// --------------------------- ObjectToOptionalConverter ------------------------------
public Object convert(@Nullable Object source,
                      TypeDescriptor sourceType,
                      TypeDescriptor targetType) {
    // 1. 如果为 null 值,返回 Optional.empty()
    if (source == null) {
        return Optional.empty();
    }
    
    // 2. 如果值就是 Optional 类型,直接返回 source 对象 
    else if (source instanceof Optional) {
        return source;
    }
    
    // 3. 如果 Optional 具有泛型,需要进一步解析泛型
    else if (targetType.getResolvableType().hasGenerics()) {
        // 这里我们 target 是 Optional<Boolean> 类型
        // new GenericTypeDescriptor(targetType) 得到的是 java.lang.Boolean 类型的 TypeDescriptor
        // 也就是再次调用 GenericConversionService.convert() 解析出泛型对应的 target 值
        // 最后返回 Optional.of(target)
        // 有点递归调用的思想
        Object target = this.conversionService.convert(source, sourceType,
                                                       new GenericTypeDescriptor(targetType));
        if (target == null || (target.getClass().isArray() && Array.getLength(target) == 0) ||
            (target instanceof Collection<?> collection && collection.isEmpty())) {
            return Optional.empty();
        }
        return Optional.of(target);
    }
    else {
        return Optional.of(source);
    }
}

至此,Optional< Boolean > 类型的参数解析完毕;

2. Pageable 参数类型

Pageable 是 SpringData 里的对象,对于该参数类型,使用的参数解析器不是普通的 RequestParamMethodArgumentResolver,而是 PageableHandlerMethodArgumentResolver;

我们也是直接看 PageableHandlerMethodArgumentResolver 的 resolveArgument();

// ------------------------- PageableHandlerMethodArgumentResolver ---------------------------
public Pageable resolveArgument(MethodParameter methodParameter,
                                @Nullable ModelAndViewContainer mavContainer,
                                NativeWebRequest webRequest,
                                @Nullable WebDataBinderFactory binderFactory) {
    // 1. 从 request 中获取参数 page 和 pageSize 属性
    // 感兴趣可以看看
    // 这里的 getPageParameterName() 和 getSizeParameterName() 默认是取 page 和 pageSize 属性
    // 但是这俩属性是可以进行配置的,比如我配置成取 pageNumber 和 pageSize 的属性
    String page = webRequest.getParameter(getParameterNameToUse(getPageParameterName(), methodParameter));
    String pageSize = webRequest.getParameter(getParameterNameToUse(getSizeParameterName(), methodParameter));

    // 2. 通过参数解析器 SortArgumentResolver#resolveArgument() 解析出 sort 对象
    Sort sort = sortResolver.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
    
    // 3. 根据参数,参数值等构造出 pageable 对象
    Pageable pageable = getPageable(methodParameter, page, pageSize);

    if (sort.isSorted()) {
        return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
    }

    // 4. 返回该 pageable 对象
    return pageable;
}


// ------------------------- PageableHandlerMethodArgumentResolver ---------------------------
protected Pageable getPageable(MethodParameter methodParameter,
                               @Nullable String pageString,
                               @Nullable String pageSizeString) {
    assertPageableUniqueness(methodParameter);

    // 1. 根据 methodParameter 获取默认的分页对象
    Optional<Pageable> defaultOrFallback = getDefaultFromAnnotationOrFallback(methodParameter).toOptional();

    Optional<Integer> page = parseAndApplyBoundaries(pageString, Integer.MAX_VALUE, true);
    Optional<Integer> pageSize = parseAndApplyBoundaries(pageSizeString, maxPageSize, false);

    if (!(page.isPresent() && pageSize.isPresent()) && !defaultOrFallback.isPresent()) {
        return Pageable.unpaged();
    }

    // 2. page 参数取值,如果不带 page 参数时取默认分页参数对象的 pageNumber 值
    int p = page.orElseGet(() -> defaultOrFallback.map(Pageable::getPageNumber)
                   .orElseThrow(IllegalStateException::new));
    // pageSize 参数取值,如果不带 pageSize 参数时取默认分页对象的 pageSize 值
    int ps = pageSize.orElseGet(() -> defaultOrFallback.map(Pageable::getPageSize)
               .orElseThrow(IllegalStateException::new));

    // 参数边界
    ps = ps < 1 ? defaultOrFallback.map(Pageable::getPageSize)
        .orElseThrow(IllegalStateException::new) : ps;
    ps = ps > maxPageSize ? maxPageSize : ps;

    // 3. 返回 Page 对象
    return PageRequest.of(p, ps, defaultOrFallback.map(Pageable::getSort).orElseGet(Sort::unsorted));
}

可以看到,如果 page 和 pageSize 传了值的情况下,取传的值;

如果没有传值,取的是默认分页对象,我们看下第 1 步的默认分页对象是怎么生成的;

// ------------------------- PageableHandlerMethodArgumentResolver ---------------------------
private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) {
    // 1. 获取参数 Pageable 前的 @PageableDefault 注解
    MergedAnnotation<PageableDefault> defaults = MergedAnnotations.from(
        methodParameter.getParameterAnnotations()).get(PageableDefault.class);

    // 如果有 @PageableDefault 注解的话,根据注解的 page 和 pageSize 生成 Pageable 对象
    if (defaults.isPresent()) {
        return getDefaultPageRequestFrom(methodParameter, defaults);
    }

    // 如果没有 @PageableDefault 注解的话,使用默认值为 PageRequest.of(0, 20)
    // 可以看到,如果在 request 中不带参数,Pageable 会使用默认值 PageRequest.of(0, 20)
    return fallbackPageable;
}

至此,Pageable 参数解析完成;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值