Spring 註解面面通 之 @RequestParam參數綁定源碼解析

  Spring MVC中使用HandlerMethodArgumentResolver策略接口來定義處理器方法參數解析器,@RequestParam使用的是RequestParamMapMethodArgumentResolverRequestParamMethodArgumentResolver,接下來一起來深入瞭解一下其源碼實現。

  類結構

在這裏插入圖片描述

在這裏插入圖片描述

  類解析

  HandlerMethodArgumentResolverAbstractNamedValueMethodArgumentResolver是解析策略的上層定義和抽象,關於這兩個類可以參照《Spring 註解面面通 之 @CookieValue參數綁定源碼解析》中的解析。

  RequestParamMapMethodArgumentResolverRequestParamMethodArgumentResolver則是用來針對不用類型的方法參數的解析。

  1) RequestParamMapMethodArgumentResolver實現了HandlerMethodArgumentResolversupportsParameter(...)resolveArgument(...)方法。

  RequestParamMapMethodArgumentResolver相對比較簡單,但在某些條件成立的情況下才會使用此類進行解析:

  ① 方法參數由@RequestParam註解註釋。

  ② 方法參數類型必須是Map類型。

  ③ @RequestParam註解的name不能有值。

  resolveArgument(...)在解析參數時,從NativeWebRequestHttpServletRequest的包裝)中獲取所有參數,針對MultiValueMap和普通Map兩種參數類型進行處理:

  ① 參數類型爲MultiValueMap時,返回LinkedMultiValueMap實例,包含所有請求參數。

  ② 參數類型爲Map時,返回LinkedHashMap實例,包含所有請求參數。

package org.springframework.web.method.annotation;

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
 * 解析用@RequestParam註釋的Map類型方法參數,其中未指定請求參數名稱.
 *
 * 創建的Map包含所有請求參數名稱/值對.
 * 如果方法參數類型是MultiValueMap,那麼對於請求參數具有多個值的情況,
 * 	創建的映射包含所有請求參數和值.
 */
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
   
   

	/**
	 * 方法參數檢查.
	 * 方法參數由@RequestParam註釋,且@RequestParam的name屬性爲空.
	 * 方法參數類型必須爲Map類型.
	 */
	@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 {
   
   

		Class<?> paramType = parameter.getParameterType();
		// 獲取請求的所有參數.
		Map<String, String[]> parameterMap = webRequest.getParameterMap();
		// 方法參數類型爲MultiValueMap.
		if (MultiValueMap.class.isAssignableFrom(paramType)) {
   
   
			MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
			parameterMap.forEach((key, values) -> {
   
   
				for (String value : values) {
   
   
					result.add(key, value);
				}
			});
			return result;
		}
		// 方法參數類型爲非MultiValueMap的Map類型.
		else {
   
   
			Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
			parameterMap.forEach((key, values) -> {
   
   
				if (values.length > 0) {
   
   
					result.put(key, values[0]);
				}
			});
			return result;
		}
	}
}

  2) RequestParamMethodArgumentResolver繼承自抽象AbstractNamedValueMethodArgumentResolver(可以參照《Spring 註解面面通 之 @CookieValue參數綁定源碼解析》)。

  RequestParamMethodArgumentResolver除了能處理@RequestParam註解外,還可以處理@RequestPart註解:

  當處理@RequestParam註解時,需在某些條件成立的情況下才會使用此類進行解析:

  ① 方法參數由@RequestParam註解註釋。

  ② 方法參數若是Map類型時,@RequestParamname屬性不能爲空。

  ③ 方法參數若不是Map類型時,都可以處理。

  當處理@RequestPart註解時,需在某些條件成立的情況下才會使用此類進行解析:

  ① 方法參數不可由@RequestPart註解註釋。

  ② 方法參數類型爲org.springframework.web.multipart.MultipartFileorg.springframework.web.multipart.MultipartFile集合、org.springframework.web.multipart.MultipartFile數組、javax.servlet.http.Partjavax.servlet.http.Part集合或javax.servlet.http.Part數組。

​  ③ 一個簡單類型的方法參數,包括:booleanbytecharshortintlongfloatdoubleEnum.classCharSequence.classNumber.class、Date.class、URI.class、URL.classLocale.classClass.class

package org.springframework.web.method.annotation;

import java.beans.PropertyEditor;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.UriComponentsContributor;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.multipart.support.MultipartResolutionDelegate;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 解析用@RequestParam註釋的方法參數,MultipartFile類型的參數與
 * 	Spring的{@link MultipartResolver}抽象結合使用,以及AAA類型的參數與Servlet 3.0多部分請求一起使用. 
 * 這個解析器也可以在默認的解析模式下創建,在這種模式下,沒有用RequestParam註釋的簡單類型(int、long等)也被視爲請求參數,參數名從參數名派生.
 *
 * 如果方法參數類型是Map,則使用註釋中指定的名稱來解析請求參數字符串值. 
 * 然後通過類型轉換將該值轉換爲Map,假設已經註冊了合適的Converter或PropertyEditor.
 * 或者,如果沒有指定請求參數名,則使用RequestParamMapMethodArgumentResolver以映射的形式提供對所有請求參數的訪問.
 *
 * 調用@WebDataBinder將類型轉換應用於尚未與方法參數類型匹配的已解析請求頭值.
 */
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {
   
   

	/**
	 * String 類型描述符.
	 */
	private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);

	private final boolean useDefaultResolution;


	/**
	 * @param useDefaultResolution 在默認解析模式下,一個簡單類型的方法參數,
	 * 	如BeanUtils.isSimpleProperty中定義的那樣,被視爲一個請求參數,
	 * 	即使它沒有被註釋,請求參數名是從方法參數名派生的.
	 */
	public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
   
   
		this.useDefaultResolution = useDefaultResolution;
	}

	/**
	 * @param beanFactory 一個Bean工廠,用於解析默認值中的${…}佔位符和#{…}SpEL表達式,如果默認值不包含表達式,則爲null.
	 * @param useDefaultResolution 在默認解析模式下,一個簡單類型的方法參數,
	 * 	如BeanUtils.isSimpleProperty中定義的那樣,被視爲一個請求參數,
	 * 	即使它沒有被註釋,請求參數名是從方法參數名派生的.
	 */
	public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
			boolean useDefaultResolution) {
   
   

		super(beanFactory);
		this.useDefaultResolution = useDefaultResolution;
	}


	/**
	 * 方法參數檢查:
	 * 方法參數由@RequestParam註釋,且@RequestParam的name屬性爲空.
	 * 方法參數類型爲Map類型.
	 * 方法參數不可由@RequestPart註釋.
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
   
   
		// 處理@RequestParam註解.
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
   
   
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
   
   
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
   
   
				return true;
			}
		}
		// 處理@RequestPart註解.
		else {
   
   
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
   
   
				return false;
			}
			parameter = parameter.nestedIfOptional();
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
   
   
				return true;
			}
			else if (this.useDefaultResolution) {
   
   
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
   
   
				return false;
			}
		}
	}

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

	/**
	 * 解析方法參數值.
	 */
	@Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   
   
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
		// 解析多部分請求值.
		if (servletRequest != null) {
   
   
			Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
			if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
   
   
				return mpArg;
			}
		}

		Object arg = null;
		MultipartHttpServletRequest multipartRequest = request.getNativeRequest(MultipartHttpServletRequest.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;
	}

	/**
	 * 處理參數缺失異常.
	 */
	@Override
	protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
			throws Exception {
   
   

		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
		if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
   
   
			if (servletRequest == null || !MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
   
   
				throw new MultipartException("Current request is not a multipart request");
			}
			else {
   
   
				throw new MissingServletRequestPartException(name);
			}
		}
		else {
   
   
			throw new MissingServletRequestParameterException(name,
					parameter.getNestedParameterType().getSimpleName());
		}
	}

	@Override
	public void contributeMethodArgument(MethodParameter parameter, @Nullable Object value,
			UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
   
   

		Class<?> paramType = parameter.getNestedParameterType();
		if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType || Part.class == paramType) {
   
   
			return;
		}

		RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
		String name = (requestParam == null || StringUtils.isEmpty(requestParam.name()) ?
				parameter.getParameterName() : requestParam.name());
		Assert.state(name != null, "Unresolvable parameter name");

		if (value == null) {
   
   
			if (requestParam != null &&
					(!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE))) {
   
   
				return;
			}
			builder.queryParam(name);
		}
		else if (value instanceof Collection) {
   
   
			for (Object element : (Collection<?>) value) {
   
   
				element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element);
				builder.queryParam(name, element);
			}
		}
		else {
   
   
			builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
		}
	}

	@Nullable
	protected String formatUriValue(
			@Nullable ConversionService cs, @Nullable TypeDescriptor sourceType, @Nullable Object value) {
   
   

		if (value == null) {
   
   
			return null;
		}
		else if (value instanceof String) {
   
   
			return (String) value;
		}
		else if (cs != null) {
   
   
			return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
		}
		else {
   
   
			return value.toString();
		}
	}

	/**
	 * 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());
		}
	}

}

  總結

  @RequestParam是用來處理Web請求頭中的信息,隨着網站的多樣和多元化,@RequestParam使用頻率會越來越廣泛。

  若文中存在錯誤和不足,歡迎指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章