在RequestMappingHandlerAdapter的各个组件分析中在解析绑定各个参数时用到了HandlerMethodArgumentResolver这个接口,用到哪个具体的实现类时再通过resolveArgument方法来具体实现。这是策略模式的典型实现。
1、HandlerMethodArgumentResolver接口介绍
supportsParameter方法:
该方法用来判断该HandlerMethodArgumentResolver对于HandlerMethod的某个方法参数是否支持。
resolveArgument方法:
该方法用来将request中的参数解析转化成为MethodParameter的类型的对象。
2、HandlerMethodArgumentResolver的具体实现类
在RequestMappingHandlerAdapter中初始化了以下几种HandlerMethodArgumentResolver:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
注意,这里添加的顺序就是解析匹配时的顺序,排在前面的被先判断,最后使用ServletModelAttributeMethodProcessor(true)来捕获所有的场景。
(1)、RequestParamMethodArgumentResolver
在该类中有一个字段:
private final boolean useDefaultResolution;
这个字段被用来在默认解析模式下,如果方法参数是一个简单的类型,如BeanUtils.isSimpleProperty中定义的,被当作请求参数处理,那么即使它没有被注释,请求参数名是从方法参数名派生的。
该类中实现了supportsParameter方法:
/**
* Supports the following:
* <ul>
* <li>@RequestParam-annotated method arguments.
* This excludes {@link Map} params where the annotation does not specify a name.
* See {@link RequestParamMapMethodArgumentResolver} instead for such params.
* <li>Arguments of type {@link MultipartFile} unless annotated with @{@link RequestPart}.
* <li>Arguments of type {@code Part} unless annotated with @{@link RequestPart}.
* <li>In default resolution mode, simple type arguments even if not with @{@link RequestParam}.
* </ul>
*/
@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 {
// 通常情况下只要标记了@RequestParam就会返回true
return true;
}
}
else {
// 如果参数被@RequestPart注解标记了,那么直接返回false。因为会有专门的RequestPart参数解析器去解析。
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
// 如果参数是MultipartFile或者Part类型的数据也会尝试解析,返回true
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
// 另外如果useDefaultResolution为true,也就是允许在没有任何注解的情况下
// 判断参数是否是简单参数类型(比如字符串,数字等等)。如果是的话那么也会尝试解析。
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
resolveArgument这个方法在RequestParamMethodArgumentResolver中并没有被实现,而是在其父类AbstractNamedValueMethodArgumentResolver中进行了实现。
代码如下:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 将@RequestParam注解中的数据解析出来封装成NamedValueInfo
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 + "]");
}
// 尝试先根据给定的parameter的类型和name解析出指定的值。可能会返回null
// 比如mvc?name=root,那么此时name的arg就是root。
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
// 如果@RequestParam注解的defaultValue的值不为空那么此时会使用这个默认值
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 如果required为true,但是却没有解析到值,此时会调用该方法抛出ServletRequestBindingException异常。
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// 获取到值以后,处理空值的情况。这里主要是针对Boolean的值,默认返回false。
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
// 如果值为""并且存在defaultValue,那么此时也会使用默认值。
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 至此一般就会完成从请求中获取了对应的String值。但是还需要转化成对应Type的值。所以就使用了WebDataBinder来实现此功能。
if (binderFactory != null) {
// 首先通过工厂模式来获取WebDataBinder。
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 直接执行转化成对应的Type操作。
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;
}
在调用getNamedValueInfo时,会调用子类(RequestParamMethodArgumentResolver)的createNamedValueInfo方法。
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
// 构造RequestParam注解信息
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
// 构造NameValueInfo实例
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
(2)、RequestParamMapMethodArgumentResolver
这个类的作用是处理@RequestMap注解标记的参数类型是Map时。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 解析出RequestParam对象
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
// 如果解析到RequestParam并且parameter对象的类型是Map,且@RequestMap不存在name属性时,返回true。
return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(requestParam.name()));
}
resolveArgument方法代码如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 通过MethodParameter获取到关于泛型的信息。
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
// MultiValueMap
// 获取MultiValueMap中的第二个泛型的类型。
Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
// 如果泛型的类型是上传文件的类型。
if (valueType == MultipartFile.class) {
// 则将request封装成分区请求,并获取Map后返回。
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
}
// 如果泛型的类型是Part,则通过request获取所有的Part列表后返回。
else if (valueType == Part.class) {
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 {
// 否则按照正常的MultiValueMap进行解析。
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;
}
}
else {
// Regular Map
// 解析常规的Map,首先获取第二个泛型参数。
Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
// 如果参数类型是MutipartFile类型,那么就按照上传文件流程处理。
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
}
// 如果参数类型是Part,同样会从request中获取所有的Part,
else if (valueType == Part.class) {
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 {
// 从request中解析常规的Map
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;
}
}
}
(3)、PathVariableMethodArgumentResolver
这个类的作用是解析方法参数中存在@PathVariable注解的情况。
supportsParameter方法代码如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断是否存在PathVariable注解。
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
// 获取注解信息
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
// 如果存在PathVariable注解并且PathVariable注解的值不为空那么就是支持的。
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
resolveArgument方法也是调用的其父类的AbstractNamedValueMethodArgumentResolver的resolveArgument方法,只不过在该方法中会通过resolveName方法来以不同的方式来获取request中的数据。那么在PathVariableMethodArgumentResolver中的实现方式如下:
@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);
}
这里会直接从request的attribute中获取名称为URI_TEMPLATE_VARIABLES_ATTRIBUTE的属性。这个属性是一个Map用于存储实际的请求与RequestMapping中定义的URL比较之后的数据。
比如RequestMapping中对应的URL为//path。而实际的请求为/root/path,那么此时会将a->root分别做为键值对存放到Map中。具体的注册到attribute的时机如下:
在RequestMappingInfoHandlerMapping中,这部分代码在匹配HandlerMethod时并没有仔细看。
/**
* Expose URI template variables, matrix variables, and producible media types in the request.
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
* @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE
* @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
*/
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
// 首先调用父类的handleMatch
super.handleMatch(info, lookupPath, request);
// 最佳的匹配路径
String bestPattern;
// 用于存放URI属性的匹配结果
Map<String, String> uriVariables;
// 方法@RequestMapping注解中定义的URL值
Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
// 根据实际请求的URL和方法中定义的URL进行比较得出值。
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}
Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
// 将上面解析到的对应关系存入到request的attribute中
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
通过上述方法从request中通过PathVariable的方式得到结果。然后再通过父类的resolveArgument来匹配成对应类型的值。
(4)、PathVariableMapMethodArgumentResolver
用于在PathVariable的基础上对类型是Map的参数进行处理。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
// 主要是判断参数类型是否是Map并且value不为空。
return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(ann.value()));
}
PathVariableMapMethodArgumentResolver自己实现了resolveArgument方法。因为参数类型已经指定为Map了,所以只转成Map就可以了。
代码如下:
/**
* Return a Map with all URI template variables or an empty map.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 首先从request的attribute中获取URI_TEMPLATE_VARIABLES_ATTRIBUTE的值。
@SuppressWarnings("unchecked")
Map<String, String> uriTemplateVars =
(Map<String, String>) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
// 将获取到的全部值直接返回。
if (!CollectionUtils.isEmpty(uriTemplateVars)) {
return new LinkedHashMap<>(uriTemplateVars);
}
else {
return Collections.emptyMap();
}
}
(5)、MatrixVariableMethodArgumentResolver
这个类主要是用于支持@MatrixVariable注解。
目前没有实验成功不是很常用,暂时先跳过。
(6)、MatrixVariableMethodArgumentResolver
暂时跳过。
(7)、ServletModelAttributeMethodProcessor
首先介绍下@ModelAttribute的作用:
使用@ModelAttribute标记参数,可以通过当前处理周期的Model获取属性值绑定到请求参数中。当Model中存在属性值,直接取该属性值;如果当前Model中不存在该属性的值时,会根据请求参数的类型创建该类型的实例,并作为Model中的属性值添加到Model中,同时还可以把请求参数和路径参数的值绑定到属性值实例的内部属性中。
对于@ModelAttribute获取的属性值来源有两大部分,一是通过现有的Model取值;二是根据类型的创建值。
对于通过现有Model取值,需要在执行处理器方法前Model中已有该值,其中Model中属性有几种不同的来源。
(1)、通过RedirectAttributes与重定向视图,在重定向后的请求中自动设置到当前的Model。
(2)、通过在@Controller或@ControllerAdvice中被@ModelAttribute标记的非处理器方法添加。
(3)、通过在@Controller的类上标记的@SessionAttributes注解指定需要从Session中添加到Model中的属性。
如果根据类型创建,又有以下两种不同的策略。
(1)、从请求参数或路径参数中取值,并通过Spring的转换服务Conversion把值转换为目标参数类型。或者根据不同@InitBinder注解标记的方法中注册的策略和WebDataBinder进行定制化转化策略。
(2)、直接创建目标参数的实例。
这个类用于解析参数被@ModelAttribute注解标记的方法参数,也是所有参数解析中最复杂的一个,该类的结构图如下:
可以看到对于HandlerMethodArgumentResolver接口的实现是在ServletModelAttributeMethodProcessor的父类ModelAttributeMethodProcessor中实现的。
有一个比较重要的字段:
// 用来决定一个参数在是否存在注解的情况下还启动该参数解析器。
private final boolean annotationNotRequired;
supportsParameter方法如下:
/**
* Returns {@code true} if the parameter is annotated with
* {@link ModelAttribute} or, if in default resolution mode, for any
* method parameter that is not a simple type.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果方法参数存在@ModelAttribute注解或者当annotationNotRequired为true
// 且参数类型不是简单类型时会启用该参数解析器
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
resolveArgument方法如下:
/**
* Resolve the argument from the model or if not found instantiate it with
* its default if it is available. The model attribute is then populated
* with request values via data binding and optionally validated
* if {@code @java.validation.Valid} is present on the argument.
* @throws BindException if data binding and validation result in an error
* and the next method parameter is not of type {@link Errors}
* @throws Exception if WebDataBinder initialization fails
*/
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
// 获取方法参数名
String name = ModelFactory.getNameForParameter(parameter);
// 获取方法参数上标记的@ModelAttribute注解。
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
// 将参数名和注解设置到容器中。
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
// 首先尝试从ModelAndViewContainer容器中获取参数名称name的对应的对象。
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// 如果获取不到,那么就创建一个方法参数类型的对象。
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
// 如果绑定异常,那么就抛出绑定异常结果
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
// 初始化WebDataBinder,此时如果有用户自定义了参数编辑器,那个就会注册到DataBinder中。
// 同时也将参数对象设置进去,供参数绑定的时候将参数值设置到对象中去。
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 执行参数绑定操作。
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
具体的绑定操作之前的文章中已经将流程和整体的结构讲清楚了,此处不再介绍。
(8)、RequestResponseBodyMethodProcessor
该类用于解析被@ResponseBody注解标记的方法参数。
supportsParameter方法很简单。
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 直接判断方法参数是否存在@RequestBody注解。
return parameter.hasParameterAnnotation(RequestBody.class);
}
resolveArgument方法如下:
/**
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 根据参数类型和request的内容的类型进行转换。
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);
}
在readWithMessageConverters方法中进行了非常多的转换操作,后续单独分析。
(9)、RequestPartMethodArgumentResolver
该类主要用于解析方法参数中带有@RequestPart注解的情况。
结构如下:
supportsParameter方法如下:
/**
* Whether the given {@linkplain MethodParameter method parameter} is a multi-part
* supported. Supports the following:
* <ul>
* <li>annotated with {@code @RequestPart}
* <li>of type {@link MultipartFile} unless annotated with {@code @RequestParam}
* <li>of type {@code javax.servlet.http.Part} unless annotated with
* {@code @RequestParam}
* </ul>
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断方法参数中是否有@RequestPart注解
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return true;
}
else {
// 如果有@RequestParam注解则不支持。
if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
// 判断参数类型是否是MultipartFile或者Part类型。
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
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)));
}
resolveArgument方法如下:
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
// 获取RequestPart注解信息。
RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
// 获取@RequestPart中的name参数。
String name = getPartName(parameter, requestPart);
parameter = parameter.nestedIfOptional();
Object arg = null;
// 解析Multipart参数,主要是用来解析MultipartFile和Part的类型。
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
arg = mpArg;
}
else {
try {
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
// 根据不同的Converter进行转化成不同的对象。
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(request, 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());
}
}
}
catch (MissingServletRequestPartException | MultipartException ex) {
if (isRequired) {
throw ex;
}
}
}
if (arg == null && isRequired) {
if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
throw new MultipartException("Current request is not a multipart request");
}
else {
throw new MissingServletRequestPartException(name);
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
(10)、RequestHeaderMethodArgumentResolver
该类用于解析方法参数中存在@RequestHeader注解的情况。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 直接判断方法参数中是否存在@RequestHeader注解。并且参数类型不是Map。因为有专门负责解析@RequestHeader的Map类型。
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}
RequestHeaderMethodArgumentResolver也是与PathVariable注解等一样,使用其父类AbstractNamedValueMethodArgumentResolver的resolveArgument来进行参数解析。
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
// 直接从request中获取指定name的header。
String[] headerValues = request.getHeaderValues(name);
if (headerValues != null) {
return (headerValues.length == 1 ? headerValues[0] : headerValues);
}
else {
return null;
}
}
(11)、RequestHeaderMapMethodArgumentResolver
该类主要适用于解析方法参数中被@RequestHeader标记且参数类型为Map的情况。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
Map.class.isAssignableFrom(parameter.getParameterType()));
}
resolveArgument方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取参数类型
Class<?> paramType = parameter.getParameterType();
// 如果是MultiValueMap类型
if (MultiValueMap.class.isAssignableFrom(paramType)) {
MultiValueMap<String, String> result;
if (HttpHeaders.class.isAssignableFrom(paramType)) {
result = new HttpHeaders();
}
else {
result = new LinkedMultiValueMap<>();
}
// 遍历request中的header参数,并添加到MultiValueMap中。
for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
String headerName = iterator.next();
String[] headerValues = webRequest.getHeaderValues(headerName);
if (headerValues != null) {
for (String headerValue : headerValues) {
result.add(headerName, headerValue);
}
}
}
return result;
}
else {
// 如果是普通的Map类型,则遍历request中的Header数据
Map<String, String> result = new LinkedHashMap<>();
// 将结果存放到Map中。注意与MultiValueMap的区别。
for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
String headerName = iterator.next();
String headerValue = webRequest.getHeader(headerName);
if (headerValue != null) {
result.put(headerName, headerValue);
}
}
return result;
}
}
(12)、ServletCookieValueMethodArgumentResolver
与@RequestHeader注解的原理其实都是一样的,用于解析request中的Cookie内值,并将值赋值给方法参数。
该类的结构图如下:
supportsParameter方法在其父类AbstractCookieValueMethodArgumentResolver中定义。
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断方法参数中是否存在@CookieValue注解。
return parameter.hasParameterAnnotation(CookieValue.class);
}
resolveArgument方法则还是使用AbstractNamedValueMethodArgumentResolver中的方法,通过resolveName来实现不同的解析逻辑。
resolveName方法则是在ServletCookieValueMethodArgumentResolver中实现。
@Override
@Nullable
protected Object resolveName(String cookieName, MethodParameter parameter,
NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
// 获取request中的Cookie
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
// 如果参数类型是Cookie的话那么直接返回即可。
if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
return cookieValue;
}
else if (cookieValue != null) {
// 如果不是的话,那么直接返回Cookie的value。
return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
}
else {
return null;
}
}
(13)、ExpressionValueMethodArgumentResolver
该类使用类解析方法参数中存在@Value注解的情况。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断方法参数中是否存在Value注解。
return parameter.hasParameterAnnotation(Value.class);
}
resolveArgument方法则还是使用AbstractNamedValueMethodArgumentResolver中的方法。依赖于以下方法进行的解析操作。
/**
* Resolve the given annotation-specified value,
* potentially containing placeholders and expressions.
*/
@Nullable
private Object resolveStringValue(String value) {
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);
}
(14)、SessionAttributeMethodArgumentResolver
用于处理方法参数中存在@SessionAttribute注解的情况,从session中获取属性并将值赋予方法参数。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断方法参数中是否存在@SessionAttribute注解。
return parameter.hasParameterAnnotation(SessionAttribute.class);
}
该类继承于AbstractNamedValueMethodArgumentResolver,并重写了
resolveName方法如下:
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
// 直接从request的attribute中获取session类型的属性。
return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
}
(15)、RequestAttributeMethodArgumentResolver
该类用于解析方法参数存在@RequestAttribute注解的情况。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断方法参数是否存在@RequestAttribute注解。
return parameter.hasParameterAnnotation(RequestAttribute.class);
}
resolveName方法如下:
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
// 从请求中获取属性。
return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
}
接下来的ArgumentResolver都是用于解析参数类型的,根据方法参数的类型直接赋予值,不需要任何注解标记,自动注入。
(16)、ServletRequestMethodArgumentResolver
解析一些常见的类型,直接注入该类型的数据。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
可以看到会支持一些常见的类型。
resolveArgument方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// WebRequest / NativeWebRequest / ServletWebRequest
if (WebRequest.class.isAssignableFrom(paramType)) {
if (!paramType.isInstance(webRequest)) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
}
return webRequest;
}
// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
}
// HttpServletRequest required for all further argument types
return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
注释中已经标明了解析的对象。
@Nullable
private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
if (HttpSession.class.isAssignableFrom(paramType)) {
HttpSession session = request.getSession();
if (session != null && !paramType.isInstance(session)) {
throw new IllegalStateException(
"Current session is not of type [" + paramType.getName() + "]: " + session);
}
return session;
}
else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) {
return PushBuilderDelegate.resolvePushBuilder(request, paramType);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
InputStream inputStream = request.getInputStream();
if (inputStream != null && !paramType.isInstance(inputStream)) {
throw new IllegalStateException(
"Request input stream is not of type [" + paramType.getName() + "]: " + inputStream);
}
return inputStream;
}
else if (Reader.class.isAssignableFrom(paramType)) {
Reader reader = request.getReader();
if (reader != null && !paramType.isInstance(reader)) {
throw new IllegalStateException(
"Request body reader is not of type [" + paramType.getName() + "]: " + reader);
}
return reader;
}
else if (Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
}
else if (HttpMethod.class == paramType) {
return HttpMethod.resolve(request.getMethod());
}
else if (Locale.class == paramType) {
return RequestContextUtils.getLocale(request);
}
else if (TimeZone.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone : TimeZone.getDefault());
}
else if (ZoneId.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
}
// Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName());
}
从请求中解析一些对象实例。
(17)、ServletResponseMethodArgumentResolver
解析response中的类型的数据。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (ServletResponse.class.isAssignableFrom(paramType) ||
OutputStream.class.isAssignableFrom(paramType) ||
Writer.class.isAssignableFrom(paramType));
}
resolveArgument方法如下:
/**
* Set {@link ModelAndViewContainer#setRequestHandled(boolean)} to
* {@code false} to indicate that the method signature provides access
* to the response. If subsequently the underlying method returns
* {@code null}, the request is considered directly handled.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
if (mavContainer != null) {
mavContainer.setRequestHandled(true);
}
Class<?> paramType = parameter.getParameterType();
// ServletResponse, HttpServletResponse
if (ServletResponse.class.isAssignableFrom(paramType)) {
return resolveNativeResponse(webRequest, paramType);
}
// ServletResponse required for all further argument types
return resolveArgument(paramType, resolveNativeResponse(webRequest, ServletResponse.class));
}
private Object resolveArgument(Class<?> paramType, ServletResponse response) throws IOException {
if (OutputStream.class.isAssignableFrom(paramType)) {
return response.getOutputStream();
}
else if (Writer.class.isAssignableFrom(paramType)) {
return response.getWriter();
}
// Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType);
}
这里会解析OutputStream、Writer这些对象并返回。
(18)、HttpEntityMethodProcessor
该类的结构图如下:
supportsParameter方法:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果类型是HttpEntity或者RequestEntity会处理。
return (HttpEntity.class == parameter.getParameterType() ||
RequestEntity.class == parameter.getParameterType());
}
resolveArgument方法如下:
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
throws IOException, HttpMediaTypeNotSupportedException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
// 这里获取的是HttpEntity或RequestEntity的参数化类型也就是泛型参数。
Type paramType = getHttpEntityType(parameter);
if (paramType == null) {
throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() +
"' in method " + parameter.getMethod() + " is not parameterized");
}
// 获取请求的body内容
Object body = readWithMessageConverters(webRequest, parameter, paramType);
// 如果参数类型是RequestEntity。那么就将返回值封装成RequestEntity实例。
if (RequestEntity.class == parameter.getParameterType()) {
return new RequestEntity<>(body, inputMessage.getHeaders(),
inputMessage.getMethod(), inputMessage.getURI());
}
else {
// 否则就将返回值封装成HttpEntity对象。
return new HttpEntity<>(body, inputMessage.getHeaders());
}
}
(19)、RedirectAttributesMethodArgumentResolver
用于解析方法参数类型是RedirectAttributes的子类型的情况。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果方法参数类型是RedirectAttributes的子类,则支持。
return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}
resolveArgument方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");
ModelMap redirectAttributes;
if (binderFactory != null) {
DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
// 直接创建ModelMap对象。
redirectAttributes = new RedirectAttributesModelMap(dataBinder);
}
else {
redirectAttributes = new RedirectAttributesModelMap();
}
// 将创建的对象放到ModelAndViewContainer中存储起来
mavContainer.setRedirectModel(redirectAttributes);
return redirectAttributes;
}
(20)、ModelMethodProcessor
该类用于处理当处理器函数参数中存在Model类型的子类时。
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果方法参数类型是Model的子类时,则处理。
return Model.class.isAssignableFrom(parameter.getParameterType());
}
resolveArgument方法参数如下:
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
// 直接从容器中获取Model。
return mavContainer.getModel();
}
(21)、MapMethodProcessor
该类用于解析方法参数为Map时,比如方法参数为ModelMap或者Map<String, Object>,这种Map一般都是给ModelAndView提供页面数据。
(22)、ErrorsMethodArgumentResolver
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
// 支持参数类型是Errors的子类。
return Errors.class.isAssignableFrom(paramType);
}
resolveArgument方法如下:
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null,
"Errors/BindingResult argument only supported on regular handler methods");
ModelMap model = mavContainer.getModel();
String lastKey = CollectionUtils.lastElement(model.keySet());
if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return model.get(lastKey);
}
throw new IllegalStateException(
"An Errors/BindingResult argument is expected to be declared immediately after " +
"the model attribute, the @RequestBody or the @RequestPart arguments " +
"to which they apply: " + parameter.getMethod());
}
(23)、SessionStatusMethodArgumentResolver
用于处理当方法参数类型为SessionStatus情况。
supportsParameter代码如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果方法参数的类型为SessionStatus时。
return SessionStatus.class == parameter.getParameterType();
}
resolveArgument方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for session status exposure");
return mavContainer.getSessionStatus();
}
(24)、UriComponentsBuilderMethodArgumentResolver
supportsParameter方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> type = parameter.getParameterType();
return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
}
resolveArgument方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(request != null, "No HttpServletRequest");
return ServletUriComponentsBuilder.fromServletMapping(request);
}
3、HandlerMethodArgumentResolver的使用
这里只是简单的识别方法参数当存在FrameParam注解时,将对应的request中的参数简单的加工之后输出。
(1)、自定义MyHandlerMethodArgumentResolver
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(FrameParam.class)) {
return true;
}
return false;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Parameter sourceParameter = parameter.getParameter();
FrameParam frame = sourceParameter.getAnnotation(FrameParam.class);
String name = webRequest.getParameter(parameter.getParameterName());
StringBuilder builder = new StringBuilder(name);
builder.append(":hello:");
builder.append(frame.value());
return builder.toString();
}
}
(2)、将MyHandlerMethodArgumentResolver注册到RequestMappingHandlerAdapter中
@PostConstruct
public void init() {
List<HandlerMethodArgumentResolver> handlerMethodArgumentResolvers = new ArrayList<>();
handlerMethodArgumentResolvers.add(new MyHandlerMethodArgumentResolver());
handlerMethodArgumentResolvers.addAll(requestMappingHandlerAdapter.getArgumentResolvers());
requestMappingHandlerAdapter.setArgumentResolvers(handlerMethodArgumentResolvers);
}
(3)、使用:
@RequestMapping(value = "/frame", method = RequestMethod.GET)
public String frame(@FrameParam(value = "123456")String value, Map<String, Object> map) {
map.put("hello", value);
return "/helloHtml";
}
结果如下: