基于SpringBoot 2.2.0.RELEASE
先看下Spring對
HandlerMethodArgumentResolver
的接口定義
/**
*用于将請求上下文中的方法參數解析為參數值的政策接口
*/
public interface HandlerMethodArgumentResolver {
/**是否MethodParamter是否能被該resolver解析器支援
* @param parameter 待檢查的方法參數
* @return {@code true} 如果解析器支援提供出來的參數 傳回true
* 否則傳回false
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 将給定請求的方法參數解析為參數值
* @param parameter 要解析的方法參數。
* 此參數必須先前已傳遞給{supportsParameter},該必須已傳回{@code true}。
* 傳回已解析的參數值,如果無法解析,則傳回{@code null}
* @throws Exception 如果出錯則抛出異常
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
因為
SpringMVC/SpringBoot
中controller中方法入參對應多中情況,如@RequestParam類型的參數、@PathVariable類型的參數、@RequestBody類型的參數等等吧。相信
HandlerMethodArgumentResolver
提供了不同的實作類來根據參數類型解析對應的參數。
下面是常見
HandlerMethodArgumentResolver
實作類以及能處理的參數類型
- RequestParamMethodArgumentResolver
帶有@RequestParam注解的參數;(與MultipartResolver結合使用的)參數類型是MultipartFile;(與Servlet 3.0 multipart requests結合使用的)參數類型是javax.servlet.http.Part的參數以及一些沒有被@RequestParam修飾的基本資料類型,如int/long等。
- RequestParamMapMethodArgumentResolver
帶有@RequestParam注解的類型是Map的參數
- PathVariableMethodArgumentResolver
帶有@PathVaribale注解類型的參數
- RequestResponseBodyMethodProcessor
借助HttpMessageConverter,這個類是來解析注解為@RequestBody的請求參數和注解為@ResponseBody的響應内容
下面以源碼分析其中的一些類
- RequestParamMethodArgumentResolver
HandlerMethodArgumentResolver源碼分析
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
//如果參數有被@RequestParam注解辨別
if (parameter.hasParameterAnnotation(RequestParam.class)) {
//如果方法參數的類型是Map
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
//因為指定在@RequestParam注解中的name 是用來解析請求參數的value值的,
//是以下面是判斷name是否存在,如果存在 則可以使用該解析器
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
//如果被注解标注的不是Map類型
return true;
}
}
else {
//如果參數有被@RequestPart注解辨別
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
//如果請求參數類型是MultipartFile
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
//如果可以使用預設解析方案
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
//将給定的參數類型和值名稱解析為參數值。
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
//如果方法中的參數是MultipartFile類型,則解析它的資料值
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
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;
}
}
- PathVariableMethodArgumentResolver
HandlerMethodArgumentResolver源碼分析
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
//如果參數沒有被@PathVariable标注
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
//如果方法的參數類型是Map
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
//因為指定在@PathVariable注解中的value 是用來解析請求參數的value值的,
//是以下面是判斷value是否存在,如果存在 則可以使用該解析器
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
//AbstractNamedValueMethodArgumentResolver#resolveArgument會調用到
@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);
if (pathVars == null) {
pathVars = new HashMap<>();
request.setAttribute(key, pathVars, scope);
}
pathVars.put(name, arg);
}
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
//在url中的屬性已經被spring容器解析 并放入org.springframework.web.servlet.HandlerMapping.uriTemplateVariables的key裡
//如果我的url為a/{id},且我請求的時候給id指派為1
//那麼這裡擷取到的 map形如{"id",1}
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
//如果map不為空 根據key(name)取出value值
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
}
經過發現,
PathVariableMethodArgumentResolver
和
RequestParamMethodArgumentResolver
的繼承體系是一樣的,而且
resolveArgument
是寫在父類的。下面來看下
AbstractNamedValueMethodArgumentResolver
是如何解析參數的
//參數解析是一個參數一個參數的解析,
//如我controller中@RequestParam String a,@RequestParam String b
//則會執行兩次參數解析
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//擷取參數的NameValueInfo,這個NameValueInfo其實就是name/required/defaultValue,
//相信使用過@RequestParam/@PathVariable注解的,應該比較熟悉,因為這是注解内的屬性
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
//解析注解的名稱,因為注解的name屬性中可能會含有占位符和表達式
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//解析獲得參數的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
//如果參數為空,但是配置了預設值 則以預設值作為參數值
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
//如果參數是必需的
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
//如果解析的參數值是空的,而且這個字段也是必須的,就會執行到這裡。
//AbstractNamedValueMethodArgumentResolver這裡是抛出異常處理
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
//如果某個參數不是必須的,并且解析出來的參數值是空
//這裡方法的内部實作是:如果arg不為空 則傳回arg;
//如果arg為空,如果方法類型為Boolean,則傳回FALSE,
//如果是其他的基本資料類型 則抛出異常
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());
}
}
//PathVariable注解時 會執行到這個方法
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
下面跟着
RequestResponseBodyMethodProcessor
類來看下Spring如何對
@RequestBody
和
@ResponseBody
做處理的。
從類的繼承關系可以看出,
RequestResponseBodyMethodProcessor
既實作了
HandlerMethodArgumentResolver
又實作了
HandlerMethodReturnValueHandler
,是以具有了解析入參和響應結果的能力。
先從解析入參開始,老套路了,既然實作了
HandlerMethodArgumentResolver
,那麼肯定會先根據
supportParameter
來判斷目前參數解析器是否能夠解析該注解的參數,然後再決定是否解析。
RequestResponseBodyMethodProcessor類
@Override
public boolean supportsParameter(MethodParameter parameter) {
//判斷目前參數中是否有@RequestBody的注解,如果有則可以使用該參數解析器
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//根據MediaType選擇合适的HTTPMessageConverter 來讀取請求資訊
//這個是解析參數的重要步驟,具體實作在其父類AbstractMessageConverterMethodArgumentResolver中做的操作
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
//調用父類AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
AbstractMessageConverterMethodArgumentResolver
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
//擷取請求頭的content-type
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
//如果contentType為空 為其設定預設值
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
//解析方法中的參數類型
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
//擷取Http請求方法
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
//messageConverters 是在SpringMVC上下文啟動的加載進來的
//有架構提供的預設值,有根據classpath來決定加載某messageConverter的
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
//根據具體messageConverter能處理的content-type與實際請求的content-type來判斷能否被
//messageConverter處理。
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
//如果能被具體某httpmessageconverter處理,則進行處理
//比如 能被MappingJackson2HttpMessageConverter處理,能被其方法read處理
//将請求參數 轉為json
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
//如果目前上下文中沒有能夠處理目前content-type類型參數的HTTPMessageConverter存在
if (body == NO_VALUE) {
//如果請求方法不存在或者不被支援或者請求參數沒内容 那麼傳回null
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
//否則這裡抛出異常
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
}
this.messageConverters如何被加載進上下文,可參考HttpMessageConverter
以上是常用注解的參數解析代碼分析,但是由于SpringMVC架構 代碼常用到很多設計模式,會出現 這一刻代碼在這個類,下一刻就跑到了其某個實作類或父類等這樣的情況。加之本人水準有限,是以會導緻代碼分析的時候,有種亂糟糟的感覺,望見諒。