Spring MVC 源碼分析 - HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

該系列文檔是本人在學習 Spring MVC 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀

Spring 版本:5.1.14.RELEASE

該系列其他文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》

HandlerAdapter 組件

HandlerAdapter 組件,處理器的適配器。因為處理器 handler 的類型是 Object 類型,需要有一個調用者來實現 handler 是怎么被執行。Spring 中的處理器的實現多變,比如用戶的處理器可以實現 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器。所以這里需要一個處理器適配器,由它去執行處理器

由於 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模塊,依次進行分析:

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);
        // <1> 先從 providedArgs 中獲得參數。如果獲得到,則進入下一個參數的解析,默認情況 providedArgs 不會傳參
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
         // <2> 判斷 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;
}
  • <2> 處,在獲取到 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;
}

類圖

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

HandlerMethodArgumentResolverComposite

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite,實現 HandlerMethodArgumentResolver 接口,復合的 HandlerMethodArgumentResolver 實現類

構造方法

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
	/**
	 * HandlerMethodArgumentResolver 數組
	 */
	private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
	/**
	 * MethodParameter 與 HandlerMethodArgumentResolver 的映射,作為緩存
	 */
	private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = 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 Map<MethodParameter, NamedValueInfo> namedValueInfoCache = 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) {
    // <1> 從 namedValueInfoCache 緩存中,獲得 NamedValueInfo 對象
    NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
    if (namedValueInfo == null) {
        // <2> 獲得不到,則創建 namedValueInfo 對象。這是一個抽象方法,子類來實現
        namedValueInfo = createNamedValueInfo(parameter);
         // <3> 更新 namedValueInfo 對象
        namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
        // <4> 添加到 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

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 {

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

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

    // <4> 解析 name 對應的值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    // <5> 如果 arg 不存在,則使用默認值
    if (arg == null) {
        // <5.1> 使用默認值
        if (namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }
        // <5.2> 如果是必填,則處理參數缺失的情況
        else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
        }
        // <5.3> 處理空值的情況
        arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
    }
    // <6> 如果 arg 為空串,則使用默認值
    else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
        arg = resolveStringValue(namedValueInfo.defaultValue);
    }

    // <7> 數據綁定相關
    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());

        }
    }

    // <8> 處理解析的值
    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. 如果上面解析出來的參數值 argnull ,則使用默認值

    1. 如果默認值非空,則調用 resolveStringValue(defaultValue) 方法,解析默認值

    2. 如果是必填,則調用 handleMissingValue(handleMissingValue) 方法,處理參數缺失的情況調用,也就是拋出指定的異常

    3. 調用 handleNullValue(String name, Object value, Class<?> paramType) 方法,處理 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

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

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // <3> 有 @RequestParam 注解的情況
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        // <3.1> 如果是 Map 類型,則 @RequestParam 注解必須要有 name 屬性
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        }
        else {
            // <3.2> 否則返回 true
            return true;
        }
    }
    else {
        // 如果有 @RequestPart 注解,返回 false 。即 @RequestPart 的優先級 > @RequestParam
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        // 獲得參數,如果存在內嵌的情況
        parameter = parameter.nestedIfOptional();
        // <1> 如果 Multipart 參數。則返回 true ,表示支持
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        }
        // <2> 如果開啟 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<?> clazz) 方法,判斷是否為普通類型,代碼如下:

    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 List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = 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 對象,前者 useDefaultResolutionfalse ,后者為 useDefaultResolutiontrue 。什么意思呢?優先將待有 @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 對象,獲取注解中的 namerequired defaultValue配置

  2. 否則,就創建一個空的 RequestParamNamedValueInfo 對象,三個屬性分別為,空字符串falseValueConstants.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) {
        List<MultipartFile> files = 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.MultipartFilejavax.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)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
					for (Part part : parts) {
						result.add(part.getName(), part);
					}
					return result;
				}
				return new LinkedMultiValueMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				MultiValueMap<String, String> result = 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)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedHashMap<String, Part> result = 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 {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				Map<String, String> result = 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 Map<String, Object> map) {
        return "666";
    }
    

    發送請求 GET /hello?name=yyy&age=20nameage 參數,就會都添加到 map

  2. 對於 RequestParamMethodArgumentResolver 類,它的效果是,將指定名字的參數添加到 Map 集合中,示例如下:

    // Controller.java
    
    @RequestMapping("/hello")
    public String hello5(@RequestParam(name = "map") Map<String, Object> map) {
        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

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

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // <1> 如果無 @PathVariable 注解
    if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
    }
    // <2> 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

實現 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

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

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

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;
    Map<String, Object> pathVars = (Map<String, Object>) 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 注解配置的(名稱、是否必須),根據注解配置從請求路徑中獲取參數值

注意,關於方法參數為 Map 類型,應該如何配置,可以參考上面的 RequestParamMapMethodArgumentResolver 小節中的兩個示例

關於其他的 HandlerMethodArgumentResolver 實現類,感興趣的可以去看看

在接下來的《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》中講到 RequestResponseBodyMethodProcessor 既是 HandlerMethodReturnValueHandler 實現類,也是 HandlerMethodArgumentResolver 實現類,用於處理器 @RequestBody 和 @ResponseBody 兩個注解

參考文章:芋道源碼《精盡 Spring MVC 源碼分析》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM