在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Controller的handler方法参数能够自动完成参数封装(有时即使没有@PathVariable、@RequestParam、@RequestBody等注解都可),甚至在方法参数任意位置写HttpServletRequest、HttpSession、Writer…等类型的参数,它自动就有值了便可直接使用。对此你是否想问一句:Spring MVC它是怎么将他们进行赋值的?那么本文就揭开它的神秘面纱,还你一片"清白"。
SpringMVC参数解析器使用了策略模式和职责链模式
HandlerMethodArgumentResolver策略接口:用于在给定请求的上下文中将方法参数解析为参数值。简单的理解为:它负责处理你Handler方法里的所有入参:包括自动封装、自动赋值、校验(底层是利用DataBinder)等等。有了它才能会让Spring MVC处理入参显得那么高级、那么自动化。
public interface HandlerMethodArgumentResolver {
/**
* 给定的方法参数parameter是否受此解析程序支持。
* @param parameter:要检查的方法参数
**/
boolean supportsParameter(MethodParameter parameter);
/**
* 将方法参数从给定请求解析为参数值。
* @param parameter: 请求参数
* @param mavContainer: 容器
* @param webRequest: 请求
* @param binderFactory: 用于创建一个WebDataBinder用于数据绑定、校验
**/
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
为了避免请求的发送者和接收者之间的耦合关系,使多个接受对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
在springMVC的参数处理中,参数处理对处理器的顺序是敏感的,因此我们需要关注Spring MVC最终的执行顺序,这时候我们的聚合容器HandlerMethodArgumentResolverComposite
就出场了:
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
// 具有缓存
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
...
// @since 4.3 木有任何地方调用
public void clear() {
this.argumentResolvers.clear();
}
// getArgumentResolver()方法是本文的核心
public boolean supportsParameter(MethodParameter parameter) {
//是否存在能解析parameter的解析器
return getArgumentResolver(parameter) != null;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 这里是关键:每个参数最多只会被一个处理器处理
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException(" ");
}
//利用获取到的参数解析器解析参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
...
// 判断当前参数解析器是否能解析parameter,这块逻辑保证了每个parameter参数最多只会被一个处理器处理
// 这个从缓存的数据结构中也能够看出来的
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
}
HandlerMethodArgumentResolverComposite中有一个参数解析器池,每当要解析参数时,他就会遍历参数解析器池。如果当前参数解析器支持当前入参的解析(supportsParameter方法为true),就立即停止遍历,使用这个参数解析器进行解析
SpringMVC参数解析器有很多,所以我分类进行说明。我把它分为:基于键值对,数据类型是Map,固定参数类型,基于ContentType的消息转换器这四类进行分析
从URI(路径变量)、HttpServletRequest、HttpSession、Header、Cookie…等中根据名称key来获取值
这类处理器所有的都是基于抽象类AbstractNamedValueMethodArgumentResolver来实现,它是最为重要的分支(分类)
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 获取给定方法参数的命名值。
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
// namedValueInfo.name 是参数的名称字符串,不过该字符串可能是个表达式,需要进一步解析为
// 最终的参数名称,下面的 resolveStringValue 语句就是对该名字进行表达式求值,从而得到解析后的
// 控制器方法参数名称,此名称是从请求上下文中获取相应参数值的关键信息
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()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
//子类实现,参为null时但处理方法
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
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());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception;
从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:
根据MethodParameter创建NameValueInfo(子类的实现可继承自NameValueInfo,就是对应注解的属性们)
根据方法参数名称name从HttpServletRequest, Http Headers, URI template variables等等中获取属性值
对arg == null这种情况的处理(非必须)
下面开始介绍他的子类
PathVariableMethodArgumentResolver是用来将请求URL中的模板变量映射到接口方法的参数解析器
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}
@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);
}
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new MissingPathVariableException(name, parameter);
}
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
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
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
return;
}
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
String name = (ann != null && StringUtils.hasLength(ann.value()) ? ann.value() : parameter.getParameterName());
String formatted = formatUriValue(conversionService, new TypeDescriptor(parameter.nestedIfOptional()), value);
uriVariables.put(name, formatted);
}
@Nullable
protected String formatUriValue(@Nullable ConversionService cs, @Nullable TypeDescriptor sourceType, Object value) {
if (value instanceof String) {
return (String) value;
}
else if (cs != null) {
return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
}
else {
return value.toString();
}
}
private static class PathVariableNamedValueInfo extends NamedValueInfo {
public PathVariableNamedValueInfo(PathVariable annotation) {
super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
}
}
}
*处理器处理情况说明:**参数上必须要有@PathVariable注解
@PathVariable
源码如下
public @interface PathVariable {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
它帮助Spring MVC实现restful风格的URL。它用于处理标注有@PathVariable
注解的方法参数,用于从URL中获取值(并不是?后面的参数哦)
注意:如果不是使用
@PathVariable
进行显示定义属性名,那么属性名就是方法参数名
案例
key是name,如果不在@PathVariable
中显示写,那么key就是string1 ,其value是PathVariableMethodArgumentResolver
使用resolveName方法从URL中获取
@RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
public String string( @PathVariable("name") String string1){
System.out.println(string1);
return string1;
}
搜索路径:clyu/hello/test ,控制台输出如下:tset
如果我要接收的是一个list,那怎么办呢,情况如下:
@RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
public String string( @PathVariable("name") List<String> string){
System.out.println(string);
return string.toString();
}
搜索路径:clyu/hello/test
,控制台输出如下:[test]
搜索路径:clyu/hello/test,age
,控制台输出如下:[test, age]
其底层使用了StringToCollectionConverter,其默认支持根据逗号分割字符串,在往里的说是使用了是使用StringUtils的commaDelimitedListToStringArray方法进行分割的
public static String[] commaDelimitedListToStringArray(@Nullable String str) {
return delimitedListToStringArray(str, ",");
}
关于@PathVariable的required=false
使用注意事项
@PathVariable的required=false使用较少,一般用于在用URL传多个值时,但有些值是非必传的时候使用。
比如这样的URL:/user/{id}/{name}
,/user/{id}
,/user
@ResponseBody
@GetMapping("/test/{id}")
public Person test(@PathVariable(required = false) Integer id) { ... }
以为这样写通过/test这个url就能访问到了,其实这样是不行的,会404。
正确姿势如下,这样/test和/test/1这两个url就都能正常work了~
@ResponseBody
@GetMapping({"/test/{id}", "/test"})
public Person test(@PathVariable(required = false) Integer id) { ... }
其底层原理如下
作用:将请求参数绑定到你控制器的方法参数上
处理器处理情况说明
所有标注有@RequestParam注解的类型(非Map)
下面都表示没有标注@RequestParam注解的情况
1、不能标注有@RequestPart注解,否则直接不处理了
2、MultipartFile类型或者对应的集合/数组类型或者javax.servlet.http.Part 对应结合/数组类型
3、useDefaultResolution=true情况下(useDefaultResolution在Spring中,普遍为true),"基本类型"也会处理
注意如果是map,不是这个解析器,而RequestParamMapMethodArgumentResolver
这里的普通类型不是java的概念,而是Spring的。其底层判断方法是BeanUtils.isSimpleProperty,如果参数类型是CharSequence,Number,Date,URI,URL,Locale,Class,或者是他们子类,或者是他们的数组类型,就可以认为是基本类型
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private final boolean useDefaultResolution;
public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
this.useDefaultResolution = useDefaultResolution;
}
public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
boolean useDefaultResolution) {
super(beanFactory);
this.useDefaultResolution = useDefaultResolution;
}
/**
* Supports the following:
*
* - @RequestParam-annotated method arguments.
* This excludes {@link Map} params where the annotation does not specify a name.
* See {@link RequestParamMapMethodArgumentResolver} instead for such params.
*
- Arguments of type {@link MultipartFile} unless annotated with @{@link RequestPart}.
*
- Arguments of type {@code Part} unless annotated with @{@link RequestPart}.
*
- In default resolution mode, simple type arguments even if not with @{@link RequestParam}.
*
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
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;
}
}
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;
}
}
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
// 核心方法:根据Name 获取值(普通/文件上传)
// 并且还有集合、数组等情况
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
// 这块解析出来的是个MultipartFile或者其集合/数组
if (servletRequest != null) {
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);
}
}
// 若解析出来值仍旧为null,那处理完文件上传里木有,那就去参数里取吧
// 由此可见:文件上传的优先级是高于请求参数的
if (arg == null) {
//小知识点:getParameter()其实本质是getParameterNames()[0]的效果
// 强调一遍:?ids=1,2,3 结果是["1,2,3"](兼容方式,不建议使用。注意:只能是逗号分隔)
// ?ids=1&ids=2&ids=3 结果是[1,2,3](标准的传值方式,建议使用)
// 但是Spring MVC这两种都能用List接收 请务必注意他们的区别~~~
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.hasLength(requestParam.name()) ?
requestParam.name() : parameter.getParameterName());
Assert.state(name != null, "Unresolvable parameter name");
parameter = parameter.nestedIfOptional();
if (value instanceof Optional) {
value = ((Optional<?>) value).orElse(null);
}
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();
}
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}
public RequestParamNamedValueInfo(RequestParam annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}
}
2、参数解析过程
先处理文件信息,如果没有文件信息,在根据request.getParameterValues(name)获取属性值的集合
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
@RequestParam源码如下
将请求参数绑定到你控制器的方法参数,控制其行为方式
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
案例
因为可以根据名字获取其值的集合,测试如下:
@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( String string){
System.out.println(string);
return string;
}
搜索路径:/clyu/hello?string=1,控制台输出 :1
搜索路径:/clyu/hello?string=1&string=3,控制台输出 :1,3
如果接收对象是数组呢
@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( @RequestParam List<String> string){
System.out.println(string);
return string.toString();
}
搜索路径:/clyu/hello?string=1,控制台输出 :[1]
搜索路径:/clyu/hello?string=1&string=3,控制台输出 :[1,3]
搜索路径:/clyu/hello?string=1,3,控制台输出 :[1,3]
两个请求的URL不一样,但都能正确的达到效果。(@RequestParam Object[] objects这么写两种URL也能正常封装)
对此有如下这个细节你必须得注意:对于集合List而言@RequestParam注解是必须存在的,否则报错如下(因为交给ServletModelAttributeMethodProcessor处理了):但如果你这么写String[] objects,即使不写注解,也能够正常完成正确封装。因为它在spring中属于基本类型
作用:解析请求头参数绑定到接口参数上
处理器处理情况说明
必须标注@RequestHeader
注解,并且不能是Map类型
有的小伙伴会说:
@RequestHeader
Map headers这样可以接收到所有的请求头啊,其实这不是本类的功劳,
而是RequestHeaderMapMethodArgumentResolver
的作用
public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public RequestHeaderMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestHeader ann = parameter.getParameterAnnotation(RequestHeader.class);
Assert.state(ann != null, "No RequestHeader annotation");
return new RequestHeaderNamedValueInfo(ann);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
String[] headerValues = request.getHeaderValues(name);
if (headerValues != null) {
return (headerValues.length == 1 ? headerValues[0] : headerValues);
}
else {
return null;
}
}
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new MissingRequestHeaderException(name, parameter);
}
private static final class RequestHeaderNamedValueInfo extends NamedValueInfo {
private RequestHeaderNamedValueInfo(RequestHeader annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}
}
@RequestHeader源码如下
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
案例
@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( @RequestHeader String string){
System.out.println(string);
return string.toString();
}
在请求体中添加键值对string :123456,发送请求,控制台输出:123456
Tip:注解指定的value值(key值)是不区分大小写的,逗号分隔是可以被封装成集合/数组的
如果这个信息不是必须写的话,请标注@RequestHeader(required = false)
作用:请求中获取属性值绑定到参数上
1、处理器处理情况说明
必须标注有@RequestAttribute
注解
public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestAttribute.class);
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
Assert.state(ann != null, "No RequestAttribute annotation");
return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
}
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new ServletRequestBindingException("Missing request attribute '" + name +
"' of type " + parameter.getNestedParameterType().getSimpleName());
}
}
作用:从session中获取属性值绑定到接口参数上
处理器处理情况说明
必须标注有@SessionAttribute
注解
public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(SessionAttribute.class);
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
SessionAttribute ann = parameter.getParameterAnnotation(SessionAttribute.class);
Assert.state(ann != null, "No SessionAttribute annotation");
return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
}
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new ServletRequestBindingException("Missing session attribute '" + name +
"' of type " + parameter.getNestedParameterType().getSimpleName());
}
}
作用:从请求Cookie中获取值绑定到接口参数上
处理器处理情况说明
必须标注有@CookieValue
注解
public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValueMethodArgumentResolver {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public ServletCookieValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
@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");
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
return cookieValue;
}
else if (cookieValue != null) {
return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
}
else {
return null;
}
}
}
这类解析器我认为是对基于键值对接口参数解析的一种补充,它依赖基于键值对接口参数解析的相关注解。曾几何时你是否想过通过@RequestParam一次性全给封装进一个Map里,然后再自己分析?同样的本类处理器给@RequestHeader、@PathVariable、@MatrixVariable都赋予了这种能力~
public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(ann.value()));
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
@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();
}
}
1、处理器处理情况说明
必须标注有@PathVariable注解,且其参数类型是map
2、参数解析过程
把所有的路径参数使用Map装着返回即可
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);
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
// MultiValueMap
Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
}
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 {
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
Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
}
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 {
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、处理器处理情况说明
使用@RequestParam标注,且参数类型是map
2、参数解析过程
它不能传一key多值情况若出现相同的key,以在最前面的key的值为准。Map实例是一个LinkedHashMap
3、案例
@ResponseBody
@GetMapping("/test")
public Object test(@RequestParam Map<String,Object> params) {
System.out.println(params);
return params;
}
搜索路径:/test?name=fsx&age=18&age=28,控制台输出如下:{name=fsx, age=18}
age只能取其中一个值
一次性把请求头信息都拿到:数据类型支出写MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map
public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
Map.class.isAssignableFrom(parameter.getParameterType()));
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (MultiValueMap.class.isAssignableFrom(paramType)) {
MultiValueMap<String, String> result;
if (HttpHeaders.class.isAssignableFrom(paramType)) {
result = new HttpHeaders();
}
else {
result = new LinkedMultiValueMap<>();
}
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<String, String> result = new LinkedHashMap<>();
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;
}
}
1、处理器处理情况说明
必须标注@RequestHeader,且参数是map类型
2、案例
@ResponseBody
@GetMapping("/test")
public Object test(@RequestHeader Map<String, Object> headers) {
headers.forEach((k, v) -> System.out.println(k + "-->" + v));
return headers;
}
控制台输出:
host-->localhost:8080
connection-->keep-alive
cache-control-->max-age=0
upgrade-insecure-requests-->1
。。。。
不过强烈不建议直接使用Map,而是使用HttpHeaders类型。这么写@RequestHeader HttpHeaders headers,获取的时候更为便捷。
它处理Map类型,但没有标注任何注解的情况,它的执行顺序是很靠后的,所以有点兜底的意思。
这个处理器同时也解释了:为何你方法入参上写个Map、HashMap、ModelMap等等就可以非常便捷的获取到模型的值的原因~
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType());
}
@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");
return mavContainer.getModel();
}
1、处理器处理情况说明
此处理器,在spring的参数处理链中很靠后,是处理没有任何注解标注的map类型
2、参数解析过程
把Model直接返回
在实际项目中,我们经常会在接口中获取如:HttpServletRequest, HttpServletResponse,我们会发现,我们不要做任何处理,就能获取到对象,这就是固定类型解析器的魅力 除了上面2个,参数比如是SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream等源生的API对象也可以获取
处理器处理情况说明
如果形参类型是WebRequest,ServletRequest,MultipartRequest,HttpSession,pushBuilder,Principal,InputStream,Reader,HttpMethod,Locale,TimeZone,ZoneId
就会使用此处理器,看到这你应该明白,以后你需要使用这些参数的话,直接在方法上申明即可,不需要自己再去get了
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
@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);
}
@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));
}
// @since 3.1
public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver {
// 它相对来说很比较简单
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (ServletResponse.class.isAssignableFrom(paramType) || // webRequest.getNativeResponse(requiredType)
OutputStream.class.isAssignableFrom(paramType) || //response.getOutputStream()
Writer.class.isAssignableFrom(paramType)); //response.getWriter()
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 这个判断放在这。。。
if (mavContainer != null) {
mavContainer.setRequestHandled(true);
}
...
}
}
处理器处理情况说明
如果形参类型是ServletResponse,OutputStream,Writer就会使用此处理器
public class SessionStatusMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return SessionStatus.class == parameter.getParameterType();
}
@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();
}
}
处理器处理情况说明
如果形参类型是SessionStatus就会使用此处理器
public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> type = parameter.getParameterType();
return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
}
@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);
}
}
处理器处理情况说明
如果形参类型是UriComponentsBuilder,ServletUriComponentsBuilder就会使用此处理器
通过UriComponentsBuilder来得到URL的各个部分,以及构建URL都是非常的方便的。
public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}
@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);
redirectAttributes = new RedirectAttributesModelMap(dataBinder);
}
else {
redirectAttributes = new RedirectAttributesModelMap();
}
mavContainer.setRedirectModel(redirectAttributes);
return redirectAttributes;
}
}
处理器处理情况说明
如果形参类型是RedirectAttributes就会使用此处理器
RedirectAttributes是Spring mvc 3.1版本之后出来的一个功能,专门用于重定向之后还能带参数跳转的
如果涉及到重定向:多个视图见传值,使用它还是比较方便的。
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
@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");
return mavContainer.getModel();
处理器处理情况说明
如果形参类型是Model就会使用此处理器
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
private final boolean annotationNotRequired;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
@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 ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
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 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;
}
处理器处理情况说明
如果形参类型使用@ModelAttribute 或者不是普通类型(通过 !BeanUtils.isSimpleProperty来判断)的参数就会使用此处理器
它用于在方法参数可以写Errors类型,来拿到数据校验结果。
public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return Errors.class.isAssignableFrom(paramType);
}
@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());
//接口入参中有BindingResult,那么其必须放在最后的位置,否则报错
if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return model.get(lastKey);
}
throw new IllegalStateException("");
}
}
处理器处理情况说明
如果是Errors类型就会被此处理器解析,准确的说是BindingResult。
注意如接口入参中有BindingResult,那么其必须放在最后的位置
什么是HTTP Content-type
Content-Type(内容类型),一般是指网页中存在的 Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些 PHP 网页点击的结果却是下载一个文件或一张图片的原因。
Content-Type 标头告诉客户端实际返回的内容的内容类型。
语法格式:
Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something
常见的媒体格式类型如下:
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
application/xhtml+xml :XHTML格式
application/xml: XML数据格式
application/atom+xml :Atom XML聚合格式
application/json: JSON数据格式
application/pdf:pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded :表单默认的提交数据的格式
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
所有基于ContentType的参数解析器都继承于AbstractMessageConverterMethodArgumentResolver
AbstractMessageConverterMethodArgumentResolver是基于ContentType消息转换器类型,其底层是利用HttpMessageConverter将输入流转换成对应的参数。
// @since 3.1
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
protected final List<HttpMessageConverter<?>> messageConverters;
protected final List<MediaType> allSupportedMediaTypes;
// 和RequestBodyAdvice和ResponseBodyAdvice有关的
private final RequestResponseBodyAdviceChain advice;
// 构造函数里指定HttpMessageConverter
// 此一个参数的构造函数木人调用
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
this(converters, null);
}
// @since 4.2
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
Assert.notEmpty(converters, "'messageConverters' must not be empty");
this.messageConverters = converters;
// 它会把所有的消息转换器里支持的MediaType都全部拿出来汇聚起来~
this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
}
// 提供一个defualt方法访问
RequestResponseBodyAdviceChain getAdvice() {
return this.advice;
}
// 统一了子类从请求中获取值的方式
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
//获取请求体的contentType
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
//如果获取不到请求体的contentType
//那么contentType默认是application/octet-stream
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
//获取当前参数,所在的类的class
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();
}
//获取当前方法
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
//NO_VALUE = new Object()
Object body = NO_VALUE;
//请求的包装类
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
//在这里进行请求体数据转换
//循环遍历消息解析器
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
//如果消息解析器不是GenericHttpMessageConverter类型,就置null
//目的是:如果参数是泛型,调用GenericHttpMessageConverter的方法
GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
//获取所有RequestBodyAdvice进行前置处理
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
//利用解析器解析请求体
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse)
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
//获取所有RequestBodyAdvice进行请求后置处理
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
//如果没有请求体
//获取所有RequestBodyAdvice进行请求体,空值处理
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
//忽略代码
}
...
}
说明:此抽象类并没有实现resolveArgument()这个接口方法。但是其子类的resolveArgument方法底层都是readWithMessageConverters方法。统一了子类从请求中获取值的方式;readWithMessageConverters过程如下
1、获取请求体的contentType。如果获取不到默认是application/octet-stream
2、循环遍历消息解析器,寻找可以解析当前请求体的消息解析器
3、如果请求体不为空。
a、获取所有RequestBodyAdvice进行请求前置处理
b、利用解析器解析请求体
c、获取所有RequestBodyAdvice进行请求后置处理
4、如果请求体为空,获取所有RequestBodyAdvice进行请求体空值处理
处理器处理情况说明
处理参数被@RequestPart修饰,或者参数类型是MultipartFile | Servlet 3.0提供的javax.servlet.http.Part类型(并且没有被@RequestParam修饰)
此处理器用于解析@RequestPart参数类型,它和多部分文件上传有关
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
List<Object> requestResponseBodyAdvice) {
super(messageConverters, requestResponseBodyAdvice);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return true;
}
else {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
@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 = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
// 如果注解没有指定,就取形参名
String name = getPartName(parameter, requestPart);
parameter = parameter.nestedIfOptional();
Object arg = null;
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
arg = mpArg;
}
else {
try {
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
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);
}
}
作用:处理请求体数据绑定当接口参数上
处理器处理情况说明
处理参数被@RequestBody修饰
注意:这个解析器也是一个返回值解析器。由于本文是关于参数解析器的所以相关方法忽略了
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager) {
super(converters, manager);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
super(converters, null, requestResponseBodyAdvice);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
super(converters, manager, requestResponseBodyAdvice);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 所以核心逻辑:读取流、消息换换等都在父类里已经完成。子类直接调用就可以拿到转换后的值arg
// arg 一般都是个类对象。比如Person实例
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
// 若是POJO,就是类名首字母小写(并不是形参名)
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());
}
}
// 把校验结果放进Model里,方便页面里获取
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
// 适配:支持到Optional类型的参数
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);
// 所以核心逻辑:读取流、消息换换等都在父类里已经完成。子类直接调用就可以拿到转换后的值arg
// arg 一般都是个类对象。比如Person实例
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;
}
protected boolean checkRequired(MethodParameter parameter) {
RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
return (requestBody != null && requestBody.required() && !parameter.isOptional());
}
}
参数解析过程
1、先利用HttpMessageConverter消息转换器解析数据解析,方法如下:
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
2、在利用WebDataBinder解析数据数据校验,注意这里只要数据校验,没有进行数据转化,和其他3解析器不一样
RequestResponseBodyMethodProcessor利用WebDataBinder参数解析的代码
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
AbstractNamedValueMethodArgumentResolver 利用WebDataBinder参数解析的代码
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
这也是@DateTimeFormat 对于标注@RequestBody 的入参无效的原因,人家都不用你的东西,当然没有效果啊
@RequestBody 源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
作用:用于处理HttpEntity和RequestEntity类型的入参的
处理器处理情况说明
接口参数是HttpEntity类型或者RequestEntity类型
注意:这个解析器也是一个返回值解析器。由于本文是关于参数解析器的所以相关方法忽略了
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (HttpEntity.class == parameter.getParameterType() || RequestEntity.class == parameter.getParameterType());
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws IOException, HttpMediaTypeNotSupportedException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
// 拿到HttpEntity的泛型类型
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);
// 注意步操作:new了一个RequestEntity进去,持有实例即可
if (RequestEntity.class == parameter.getParameterType()) {
return new RequestEntity<>(body, inputMessage.getHeaders(), inputMessage.getMethod(), inputMessage.getURI());
} else { // 用的父类HttpEntity,那就会丢失掉Method等信息(因此建议入参用RequestEntity类型,更加强大些)
return new HttpEntity<>(body, inputMessage.getHeaders());
}
}
}
其实