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

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

Spring 版本:5.2.4.RELEASEjava

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

HandlerAdapter 組件

HandlerAdapter 組件,處理器的適配器。由於處理器 handler 的類型是 Object 類型,須要有一個調用者來實現 handler 是怎麼被執行。Spring 中的處理器的實現多變,好比用戶的處理器能夠實現 Controller 接口或者 HttpRequestHandler 接口,也能夠用 @RequestMapping 註解將方法做爲一個處理器等,這就致使 Spring MVC 沒法直接執行這個處理器。因此這裏須要一個處理器適配器,由它去執行處理器github

因爲 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,因此將這部份內容拆分紅了五個模塊,依次進行分析:web

HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver

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

回顧

先來回顧一下 ServletInvocableHandlerMethod 在哪裏調用參數解析器的,能夠回到 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》InvocableHandlerMethod 小節下面的 getMethodArgumentValues 方法,以下:express

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,方法參數解析器mvc

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;
}
  • 狀況1、二,是處理參數類型爲文件 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 源碼分析》

相關文章
相關標籤/搜索