天天看點

精盡Spring MVC源碼分析 - HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver

HandlerAdapter 元件

HandlerAdapter 元件,處理器的擴充卡。因為處理器 handler 的類型是 Object 類型,需要有一個調用者來實作 handler 是怎麼被執行。Spring 中的處理器的實作多變,比如使用者的處理器可以實作 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作為一個處理器等,這就導緻 Spring MVC 無法直接執行這個處理器。是以這裡需要一個處理器擴充卡,由它去執行處理器

由于 HandlerMapping 元件涉及到的内容較多,考慮到内容的排版,是以将這部分内容拆分成了五個子產品,依次進行分析:

  • 《HandlerAdapter 元件(一)之 HandlerAdapter》
  • 《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》
  • 《HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver》
  • 《HandlerAdapter 元件(四)之 HandlerMethodReturnValueHandler》
  • 《HandlerAdapter 元件(五)之 HttpMessageConverter》

HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver

本文是接着《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》一文來分享 HandlerMethodArgumentResolver 元件。在 HandlerAdapter 執行處理器的過程中,具體的執行過程交由 ServletInvocableHandlerMethod 對象來完成,其中需要先通過 HandlerMethodArgumentResolver 參數解析器從請求中解析出方法的入參,然後再通過反射機制調用對應的方法。

回顧

先來回顧一下 ServletInvocableHandlerMethod 在哪裡調用參數解析器的,可以回到 《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》 中 InvocableHandlerMethod 小節下面的 getMethodArgumentValues 方法,如下:

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];
    for (int i = 0; i < parameters.length; i++) {
        // 獲得目前周遊的 MethodParameter 對象,并設定 parameterNameDiscoverer 到其中
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        //先從 providedArgs 中獲得參數。如果獲得到,則進入下一個參數的解析,預設情況 providedArgs 不會傳參
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
         //判斷 resolvers 是否支援目前的參數解析
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 執行解析,解析成功後,則進入下一個參數的解析
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}      
  • 處,在擷取到 Method 方法的所有參數對象,依次處理,根據 resolvers 判斷是否支援該參數的處理,如果支援則進行參數轉換
  • resolvers 為 HandlerMethodArgumentResolverComposite 組合對象,包含了許多的參數解析器

HandlerMethodArgumentResolver 接口

org.springframework.web.method.support.HandlerMethodArgumentResolver,方法參數解析器

public interface HandlerMethodArgumentResolver {
	/**
	 * 是否支援解析該參數
	 */
	boolean supportsParameter(MethodParameter parameter);
	/**
	 * 解析該參數
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}      

類圖

精盡Spring MVC源碼分析 - HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver

因為請求入參的場景非常多,是以 HandlerMethodArgumentResolver 的實作類也非常多,上面僅列出了部分實作類,本文也僅分析上面圖中右側常見的幾種參數場景

HandlerMethodArgumentResolverComposite

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite,實作 HandlerMethodArgumentResolver 接口,複合的 HandlerMethodArgumentResolver 實作類

構造方法

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
	/**
	 * HandlerMethodArgumentResolver 數組
	 */
	private final ListargumentResolvers = new LinkedList<>();
	/**
	 * MethodParameter 與 HandlerMethodArgumentResolver 的映射,作為緩存
	 */
	private final MapargumentResolverCache = new ConcurrentHashMap<>(256);
}      
  • argumentResolvers:HandlerMethodArgumentResolver 數組。這就是 Composite 複合~
  • argumentResolverCache:MethodParameter 與 HandlerMethodArgumentResolver 的映射,作為緩存。因為,MethodParameter 是需要從 argumentResolvers 周遊到适合其的解析器,通過緩存後,無需再次重複周遊

在《HandlerAdapter 元件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter小節的 getDefaultArgumentResolvers 方法中可以看到,預設的 argumentResolvers 有哪些 HandlerMethodArgumentResolver 實作類,注意這裡是有順序的添加哦

getArgumentResolver

getArgumentResolver(MethodParameter parameter) 方法,獲得方法參數對應的 HandlerMethodArgumentResolver 對象,方法如下:

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 優先從 argumentResolverCache 緩存中,獲得 parameter 對應的 HandlerMethodArgumentResolver 對象
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        // 獲得不到,則周遊 argumentResolvers 數組,逐個判斷是否支援。
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            // 如果支援,則添加到 argumentResolverCache 緩存中,并傳回
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}      

很簡單,先從argumentResolverCache緩存中擷取,沒有擷取到則周遊 argumentResolvers,如果支援該參數則該 HandlerMethodArgumentResolver 對象并緩存起來

注意,往 argumentResolvers 添加的順序靠前,則優先判斷是否支援該參數哦~

supportsParameter

實作 supportsParameter(MethodParameter parameter) 方法,如果能獲得到對應的 HandlerMethodArgumentResolver 參數處理器,則說明支援處理該參數,方法如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
}      

resolveArgument

實作 resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 方法,解析出指定參數的值,方法如下:

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 擷取參數解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
                parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    /**
     * 進行解析
     *
     * 基于 @RequestParam 注解
     * {@link org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveArgument}
     * 基于 @PathVariable 注解
     * {@link org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver#resolveArgument}
     */
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}      

很簡單,擷取到該方法參數對應的 HandlerMethodArgumentResolver 參數處理器,然後調用其 resolveArgument 執行解析

AbstractNamedValueMethodArgumentResolver

org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver,實作 ValueMethodArgumentResolver 接口,基于名字擷取值的HandlerMethodArgumentResolver 抽象基類。例如說,@RequestParam(value = "username") 注解的參數,就是從請求中獲得 username 對應的參數值。???? 明白了麼?

AbstractNamedValueMethodArgumentResolver 的子類也有挺多了,我們僅分析它的兩個子類,如上面類圖的下面兩個:

  • RequestParamMethodArgumentResolver:基于 @RequestParam 注解( 也可不加該注解的請求參數 )的方法參數,詳情見下文
  • PathVariableMethodArgumentResolver ,基于 @PathVariable 注解的方法參數,詳情見下文

public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Nullable
	private final ConfigurableBeanFactory configurableBeanFactory;

	@Nullable
	private final BeanExpressionContext expressionContext;
	/**
	 * MethodParameter 和 NamedValueInfo 的映射,作為緩存
	 */
	private final MapnamedValueInfoCache = new ConcurrentHashMap<>(256);
}      

NamedValueInfo 内部類

AbstractNamedValueMethodArgumentResolver 的靜态内部類,代碼如下:

protected static class NamedValueInfo {
    /**
     * 名字
     */
    private final String name;

    /**
     * 是否必填
     */
    private final boolean required;

    /**
     * 預設值
     */
    @Nullable
    private final String defaultValue;

    public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
        this.name = name;
        this.required = required;
        this.defaultValue = defaultValue;
    }
}      

getNamedValueInfo

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
    //從 namedValueInfoCache 緩存中,獲得 NamedValueInfo 對象
    NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
    if (namedValueInfo == null) {
        //獲得不到,則建立 namedValueInfo 對象。這是一個抽象方法,子類來實作
        namedValueInfo = createNamedValueInfo(parameter);
         //更新 namedValueInfo 對象
        namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
        //添加到 namedValueInfoCache 緩存中
        this.namedValueInfoCache.put(parameter, namedValueInfo);
    }
    return namedValueInfo;
}      
  1. 從 namedValueInfoCache 緩存中,獲得 NamedValueInfo 對象,擷取到則直接傳回
  2. 獲得不到,則調用 createNamedValueInfo(MethodParameter parameter) 方法,建立 NamedValueInfo 對象。這是一個抽象方法,交由子類來實作
  3. 調用 updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) 方法,更新 NamedValueInfo 對象,方法如下:
    private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
        String name = info.name;
        if (info.name.isEmpty()) {
            // 【注意!!!】如果 name 為空,則使用參數名
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException(
                        "Name for argument type [" + parameter.getNestedParameterType().getName() +
                        "] not available, and parameter name information not found in class file either.");
            }
        }
        // 獲得預設值
        String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
        // 建立 NamedValueInfo 對象
        return new NamedValueInfo(name, info.required, defaultValue);
    }      
    如果名稱為空,則取參數名,擷取預設值,建立一個新的 NamedValueInfo 對象傳回
  4. 添加到 namedValueInfoCache 緩存中
  5. 傳回該 NamedValueInfo  對象

resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 方法,從請求中解析出指定參數的值

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    //獲得方法參數對應的 NamedValueInfo 對象。
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    //如果 parameter 是内嵌類型(Optional 類型)的,則擷取内嵌的參數。否則,還是使用 parameter 自身
    MethodParameter nestedParameter = parameter.nestedIfOptional();

    //如果 name 是占位符,則進行解析成對應的值
    Object resolvedName = resolveStringValue(namedValueInfo.name);
    if (resolvedName == null) {
        // 如果解析不到,則抛出 IllegalArgumentException 異常
        throw new IllegalArgumentException(
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }

    //解析 name 對應的值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    //如果 arg 不存在,則使用預設值
    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());
    }
    //如果 arg 為空串,則使用預設值
    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());

        }
    }

    //處了解析的值
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

    return arg;
}      
  1. 調用 getNamedValueInfo(MethodParameter parameter) 方法,獲得方法參數對應的 NamedValueInfo 對象
  2. 如果 parameter 是内嵌類型(Optional 類型)的,則擷取内嵌的參數。否則,還是使用 parameter 自身。一般情況下,parameter 參數,我們不太會使用 Optional 類型。可以暫時忽略
  3. 調用 resolveStringValue(String value) 方法,如果 name 是占位符,則進行解析成對應的值,方法如下:
    @Nullable
    private Object resolveStringValue(String value) {
        // 如果 configurableBeanFactory 為空,則不進行解析
        if (this.configurableBeanFactory == null) {
            return value;
        }
        // 獲得占位符對應的值
        String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
        // 擷取表達式處理器對象
        BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
        if (exprResolver == null || this.expressionContext == null) {
            return value;
        }
        // 計算表達式
        return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
    }      
    這種用法非常小衆,從來沒用過。示例如下:
    // Controller.java
    
    @RequestMapping("/hello3")
    public String hello3(@RequestParam(value = "${server.port}") String name) {
        return "666";
    }
    
    // application.properties
    server.port=8012      
    此時,就可以發送 GET /hello3?8012=xxx 請求
  4. 【重點】調用 resolveName(String name, MethodParameter parameter, NativeWebRequest request) 抽象方法,解析參數名 name 對應的值,交由子類去實作
  5. 如果上面解析出來的參數值 arg 為 null ,則使用預設值
    1. 如果預設值非空,則調用 resolveStringValue(defaultValue) 方法,解析預設值
    2. 如果是必填,則調用 handleMissingValue(handleMissingValue) 方法,處理參數缺失的情況調用,也就是抛出指定的異常
    3. 調用 handleNullValue(String name, Object value, Class 方法,處理 null 值的情況,方法如下:
      @Nullable
      private Object handleNullValue(String name, @Nullable Object value, Class paramType) {
          if (value == null) {
              if (Boolean.TYPE.equals(paramType)) {
                  return Boolean.FALSE;
              } else if (paramType.isPrimitive()) { // 如果是基本類型則不能為 null
                  throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name +
                          "' is present but cannot be translated into a null value due to being declared as a " +
                          "primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
              }
          }
          return value;
      }      
  6. 否則,如果 arg為空字元串,并且存在預設值,則和上面的 5.1 相同處理方式
  7. 資料綁定相關,暫時忽略
  8. 調用 handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 方法,解析參數值的後置處理,空方法,子類可以覆寫,子類 PathVariableMethodArgumentResolver 會重寫該方法
代碼有點長,不過邏輯不難了解

RequestParamMethodArgumentResolver

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver,實作 UriComponentsContributor 接口,繼承 AbstractNamedValueMethodArgumentResolver 抽象類,參數解析器 HandlerMethodArgumentResolver 的實作類,處理普通的請求參數

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {
	private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
	/**
	 * 是否使用預設解決
	 *
	 * 這個變量有點繞,見 {@link #supportsParameter(MethodParameter)} 方法
	 */
	private final boolean useDefaultResolution;

	public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
		this.useDefaultResolution = useDefaultResolution;
	}

	public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
			boolean useDefaultResolution) {
		super(beanFactory);
		this.useDefaultResolution = useDefaultResolution;
	}
}      

實作 supportsParameter(MethodParameter parameter) 方法,判斷是否支援處理該方法入參,方法如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    //有 @RequestParam 注解的情況
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        //如果是 Map 類型,則 @RequestParam 注解必須要有 name 屬性
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        }
        else {
            //否則傳回 true
            return true;
        }
    }
    else {
        // 如果有 @RequestPart 注解,傳回 false 。即 @RequestPart 的優先級 > @RequestParam
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        // 獲得參數,如果存在内嵌的情況
        parameter = parameter.nestedIfOptional();
        //如果 Multipart 參數。則傳回 true ,表示支援
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        }
        //如果開啟 useDefaultResolution 功能,則判斷是否為普通類型
        else if (this.useDefaultResolution) {
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        }
        // 其它,不支援
        else {
            return false;
        }
    }
}      
  1. 如果 Multipart 參數。則傳回 true ,表示支援調用 MultipartResolutionDelegate#isMultipartArgument(parameter) 方法,如果 Multipart 參數。則傳回 true ,表示支援。代碼如下:
    public static boolean isMultipartArgument(MethodParameter parameter) {
        Class paramType = parameter.getNestedParameterType();
        return (MultipartFile.class == paramType ||
                isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
                (Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter)));
    }      
    上傳檔案相關類型
  2. 如果開啟 useDefaultResolution 功能,則調用 BeanUtils#isSimpleProperty(Class 方法,判斷是否為普通類型,代碼如下:
    public static boolean isSimpleProperty(Class type) {
        Assert.notNull(type, "'type' must not be null");
        return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
    }
    public static boolean isSimpleValueType(Class type) {
        return (type != void.class && type != Void.class &&
                (ClassUtils.isPrimitiveOrWrapper(type) ||
                Enum.class.isAssignableFrom(type) ||
                CharSequence.class.isAssignableFrom(type) ||
                Number.class.isAssignableFrom(type) ||
                Date.class.isAssignableFrom(type) ||
                URI.class == type ||
                URL.class == type ||
                Locale.class == type ||
                Class.class == type));
    }      
    那麼 useDefaultResolution 到底是怎麼被指派的呢?回到 RequestMappingHandlerAdapter 的 getDefaultArgumentResolvers() 的方法,精簡代碼如下:
    private ListgetDefaultArgumentResolvers() {
        Listresolvers = new ArrayList<>();
    
        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        
        // ... 省略許多 HandlerMethodArgumentResolver 的添加
        
        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
    
        return resolvers;
    }      
    我們可以看到有兩個 RequestParamMethodArgumentResolver 對象,前者 useDefaultResolution 為 false ,後者為 useDefaultResolution 為 true 。什麼意思呢?優先将待有 @RequestParam 注解的請求參數給第一個 RequestParamMethodArgumentResolver 對象;其次,給中間省略的一大片參數解析器試試能不能解析;最後,使用第二個 RequestParamMethodArgumentResolver 兜底,處理剩餘的情況。
  3. 如果該方法參數有 @RequestParam 注解的情況
    1. 如果是 Map 類型,則 @RequestParam 注解必須要有 name 屬性,是不是感覺有幾分靈異?答案在下面的 RequestParamMapMethodArgumentResolver 中揭曉
    2. 否則,傳回 true

createNamedValueInfo

實作父類的 createNamedValueInfo(MethodParameter parameter) 方法,建立 NamedValueInfo 對象,方法如下:

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
    return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}

private static class RequestParamNamedValueInfo extends NamedValueInfo {

    public RequestParamNamedValueInfo() {
        super("", false, ValueConstants.DEFAULT_NONE);
    }

    public RequestParamNamedValueInfo(RequestParam annotation) {
        super(annotation.name(), annotation.required(), annotation.defaultValue());
    }
}      
  1. 如果方法參數有 @RequestParam 注解,則根據注解建立一個 RequestParamNamedValueInfo 對象,擷取注解中的 name、required 和 defaultValue配置
  2. 否則,就建立一個空的 RequestParamNamedValueInfo 對象,三個屬性分别為,空字元串、false 和 ValueConstants.DEFAULT_NONE

    上面的 getNamedValueInfo 方法中講述到,name 為 空字元串 沒有關系,會擷取方法的參數名

    說明:通過反射擷取方法的參數名,我們隻能擷取到 arg0,arg1 的名稱,因為jdk8之後這些變量名稱沒有被編譯到class檔案中,編譯時需要指定-parameters選項,方法的參數名才會記錄到class檔案中,運作時我們就可以通過反射機制擷取到,是以我們最好還是用 @RequestParam 注解來标注
    ValueConstants.DEFAULT_NONE 則會設定為 null

resolveName

實作 #resolveName(String name, MethodParameter parameter, NativeWebRequest request) 方法,獲得參數的值,方法如下:

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // 情況一,HttpServletRequest 情況下的 MultipartFile 和 Part 的情況
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    if (servletRequest != null) {
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }
    }

    // 情況二,MultipartHttpServletRequest 情況下的 MultipartFile 的情況
    Object arg = null;
    MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        Listfiles = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    // 情況三,普通參數的擷取
    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}      
  • 情況一、二,是處理參數類型為檔案 org.springframework.web.multipart.MultipartFile 和 javax.servlet.http.Part 的參數的擷取,例如我們常用到 MultipartFile 作為參數就是在這裡處理的
  • 情況三,是處理普通參數的擷取。就是我們常見的 String、Integer 之類的請求參數,直接從請求中擷取參數值就好了

因為在《MultipartResolver 元件》中講過了會對請求進行處理,包括解析出參數,解析成對應的 HttpServletRequest 對象

獲得到參數值後,就可以準備開始通過反射調用對應的方法了

RequestParamMapMethodArgumentResolver

org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver,實作 HandlerMethodArgumentResolver 接口,用于處理帶有 @RequestParam 注解,但是注解上沒有 name 屬性的 Map 類型的參數, HandlerMethodArgumentResolver 的實作類,代碼如下:

public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
		return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(requestParam.name()));
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);

		// MultiValueMap 類型的處理
		if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
			Class valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
			if (valueType == MultipartFile.class) { // MultipartFile 類型
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
			}
			else if (valueType == Part.class) { // Part 類型
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collectionparts = servletRequest.getParts();
					LinkedMultiValueMapresult = new LinkedMultiValueMap<>(parts.size());
					for (Part part : parts) {
						result.add(part.getName(), part);
					}
					return result;
				}
				return new LinkedMultiValueMap<>(0);
			}
			else {
				MapparameterMap = webRequest.getParameterMap();
				MultiValueMapresult = new LinkedMultiValueMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					for (String value : values) {
						result.add(key, value);
					}
				});
				return result;
			}
		}
		// 普通 Map 類型的處理
		else {
			Class valueType = resolvableType.asMap().getGeneric(1).resolve();
			if (valueType == MultipartFile.class) { // MultipartFile 類型
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
			}
			else if (valueType == Part.class) { // Part 類型
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collectionparts = servletRequest.getParts();
					LinkedHashMapresult = new LinkedHashMap<>(parts.size());
					for (Part part : parts) {
						if (!result.containsKey(part.getName())) {
							result.put(part.getName(), part);
						}
					}
					return result;
				}
				return new LinkedHashMap<>(0);
			}
			else {
				MapparameterMap = webRequest.getParameterMap();
				Mapresult = new LinkedHashMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					if (values.length > 0) {
						result.put(key, values[0]);
					}
				});
				return result;
			}
		}
	}
}      

上面沒有仔細看,實際上是有點看不懂,不知道處理場景????就舉兩個例子吧

  1. 對于 RequestParamMapMethodArgumentResolver 類,它的效果是,将所有參數添加到 Map 集合中,示例如下:
    // Controller.java
    
    @RequestMapping("/hello")
    public String hello4(@RequestParam Mapmap) {
        return "666";
    }      
    發送請求 GET /hello?name=yyy&age=20,name 和 age 參數,就會都添加到 map 中
  2. 對于 RequestParamMethodArgumentResolver 類,它的效果是,将指定名字的參數添加到 Map 集合中,示例如下:
    // Controller.java
    
    @RequestMapping("/hello")
    public String hello5(@RequestParam(name = "map") Mapmap) {
        return "666";
    }      
    發送請求 GET /hello4?map={"name": "yyyy", age: 20}, map 參數的元素則都會添加到方法參數 map 中。當然,要注意下,實際請求要 UrlEncode 編碼下參數,是以實際請求是 GET /hello?map=%7b%22name%22%3a+%22yyyy%22%2c+age%3a+20%7d

PathVariableMethodArgumentResolver

org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver,實作 UriComponentsContributor 接口,繼承 AbstractNamedValueMethodArgumentResolver 抽象類,處理路徑參數

實作 supportsParameter(MethodParameter parameter) 方法,判斷是否支援處理該方法參數,代碼如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    //如果無 @PathVariable 注解
    if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
    }
    //Map 類型,有 @PathVariable 注解,但是有 name 屬性
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
        PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
        return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
    }
    return true;
}      
  1. 如果沒有 @PathVariable 注解則直接傳回 fasle,也就是說必須配置 @PathVariable 注解
  2. 如果還是 Map 類型,則需要 @PathVariable 注解有 name 屬性,才傳回 true,檢視 org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver 就了解了,和上述的邏輯差不多
  3. 否則,直接傳回 true

實作 createNamedValueInfo(MethodParameter parameter) 方法,方法如下:

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    // 獲得 @PathVariable 注解
    PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
    Assert.state(ann != null, "No PathVariable annotation");
    // 建立 PathVariableNamedValueInfo 對象
    return new PathVariableNamedValueInfo(ann);
}

private static class PathVariableNamedValueInfo extends NamedValueInfo {

    public PathVariableNamedValueInfo(PathVariable annotation) {
        super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
    }
}      

必須要有 @PathVariable 注解,沒有的話抛出異常,然後根據注解建立 PathVariableNamedValueInfo 對象

實作 resolveName(String name, MethodParameter parameter, NativeWebRequest request) 方法,從請求路徑中擷取方法參數的值,方法如下:

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // 獲得路徑參數
    MapuriTemplateVars = (Map) request.
        getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    // 獲得參數值
    return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}      

handleResolvedValue

重寫 handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request) 方法,添加獲得的屬性值到請求的 View.PATH_VARIABLES 屬性種,方法如下:

@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
        @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
    // 獲得 pathVars
    String key = View.PATH_VARIABLES;
    int scope = RequestAttributes.SCOPE_REQUEST;
    MappathVars = (Map) request.getAttribute(key, scope);
    // 如果不存在 pathVars,則進行建立
    if (pathVars == null) {
        pathVars = new HashMap<>();
        request.setAttribute(key, pathVars, scope);
    }
     // 添加 name + arg 到 pathVars 中
    pathVars.put(name, arg);
}      

具體用途還不清楚????

總結

在 HandlerAdapter 執行 HandlerMethod 處理器的過程中,會将該處理器封裝成 ServletInvocableHandlerMethod 對象,通過該對象來執行處理器。該對象通過反射機制調用對應的方法,在調用方法之前,借助 HandlerMethodArgumentResolver 參數解析器從請求中擷取到對應的方法參數值,因為你無法确認哪個參數值對應哪個參數,是以需要先通過它從請求中解析出參數值,一一對應,然後才能調用該方法。

HandlerMethodArgumentResolver 參數解析器的實作類非常多,采用了組合模式來進行處理,如果有某一個參數解析器支援解析該方法參數,則使用它從請求體中擷取到該方法參數的值,注意這裡有一定的先後順序,因為是通過 LinkedList 儲存所有的實作類,排在前面的實作類則優先處理。

本文分析了我們常用的 @RequestParam 和 @PathVariable 注解所對應的 HandlerMethodArgumentResolver 實作類,如下:

  • RequestParamMethodArgumentResolver:解析 @RequestParam 注解配置參數(名稱、是否必須、預設值),根據注解配置從請求擷取參數值
  • PathVariableMethodArgumentResolver:解析 @PathVariable 注解配置的(名稱、是否必須),根據注解配置從請求路徑中擷取參數值

繼續閱讀