SpringMVC-参数解析器
前言 在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Controller的handler方法参数能够自动完成参数封装(有时即使没有@PathVariable、@RequestParam、@RequestBody等注解都可),甚至在方法参数任意位置写HttpServletRequest、HttpSession、Writer…等类型的参数,它自动就有值了便可直接使用。 对此你是否想问一句:Spring MVC它是怎么办到的?那么本文就揭开它的神秘面纱,还你一片”清白”。
Spring MVC作为一个最为流行的web框架,早早已经成为了实际意义上的标准化(框架),特别是随着Struts2的突然崩塌,Spring MVC几乎一骑绝尘,因此深入了解它有着深远的意义
Spring MVC它只需要区区几个注解就能够让一个普通的java方法成为一个Handler处理器,并且还能有自动参数封装、返回值视图处理/渲染等一系列强大功能,让coder的精力更加的聚焦在自己的业务。
HandlerMethodArgumentResolver 策略接口:用于在给定请求的上下文中将方法参数解析为参数值。简单的理解为:它负责处理你Handler方法里的所有入参:包括自动封装、自动赋值、校验等等。有了它才能会让Spring MVC处理入参显得那么高级、那么自动化。 Spring MVC内置了非常非常多的实现,当然若还不能满足你的需求,你依旧可以自定义和自己注册,后面我会给出自定义的示例。
有个形象的公式:HandlerMethodArgumentResolver = HandlerMethod + Argument(参数) + Resolver(解析器)。
解释为:它是HandlerMethod方法的解析器,将HttpServletRequest(header + body 中的内容)解析为HandlerMethod方法的参数(method parameters)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface HandlerMethodArgumentResolver { boolean supportsParameter (MethodParameter parameter) ; @Nullable Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;}
基于这个接口的处理器实现类不可谓不丰富
因为子类众多,所以我分类进行说明。我把它分为四类进行描述:
基于Name
数据类型是Map
的
固定参数类型
基于ContentType
的消息转换器
基于Name
从URI(路径变量)、HttpServletRequest、HttpSession、Header、Cookie…等中根据名称key来获取值
这类处理器所有的都是基于抽象类AbstractNamedValueMethodArgumentResolver
来实现,它是最为重要的分支(分类) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver { @Nullable private final ConfigurableBeanFactory configurableBeanFactory; @Nullable private final BeanExpressionContext expressionContext; private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap <>(256 ); public AbstractNamedValueMethodArgumentResolver () { this .configurableBeanFactory = null ; this .expressionContext = null ; } public AbstractNamedValueMethodArgumentResolver (@Nullable ConfigurableBeanFactory beanFactory) { this .configurableBeanFactory = beanFactory; this .expressionContext = (beanFactory != null ? new BeanExpressionContext (beanFactory, new RequestScope ()) : null ); } @Override @Nullable public final Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); 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); } 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; } private NamedValueInfo getNamedValueInfo (MethodParameter parameter) { NamedValueInfo namedValueInfo = this .namedValueInfoCache.get(parameter); if (namedValueInfo == null ) { namedValueInfo = createNamedValueInfo(parameter); namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo); this .namedValueInfoCache.put(parameter, namedValueInfo); } return namedValueInfo; } protected abstract NamedValueInfo createNamedValueInfo (MethodParameter parameter) ; private NamedValueInfo updateNamedValueInfo (MethodParameter parameter, NamedValueInfo info) { String name = info.name; if (info.name.isEmpty()) { name = parameter.getParameterName(); if (name == null ) { throw new IllegalArgumentException ( "Name for argument type [" + parameter.getNestedParameterType().getName() + "] not available, and parameter name information not found in class file either." ); } } String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue); return new NamedValueInfo (name, info.required, defaultValue); } @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); } @Nullable protected abstract Object resolveName (String name, MethodParameter parameter, NativeWebRequest request) throws Exception; protected void handleMissingValue (String name, MethodParameter parameter, NativeWebRequest request) throws Exception { handleMissingValue(name, parameter); } protected void handleMissingValue (String name, MethodParameter parameter) throws ServletException { throw new ServletRequestBindingException ("Missing argument '" + name + "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName()); } @Nullable private Object handleNullValue (String name, @Nullable Object value, Class<?> paramType) { if (value == null ) { if (Boolean.TYPE.equals(paramType)) { return Boolean.FALSE; } else if (paramType.isPrimitive()) { throw new IllegalStateException ("Optional " + paramType.getSimpleName() + " parameter '" + name + "' is present but cannot be translated into a null value due to being declared as a " + "primitive type. Consider declaring it as object wrapper for the corresponding primitive type." ); } } return value; } protected void handleResolvedValue (@Nullable Object arg, String name, MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) { } protected static class NamedValueInfo { private final String name; private final boolean required; @Nullable private final String defaultValue; public NamedValueInfo (String name, boolean required, @Nullable String defaultValue) { this .name = name; this .required = required; this .defaultValue = defaultValue; } } }
该抽象类中定义了解析参数的主逻辑(模版逻辑),子类只需要实现对应的抽象模版方法即可。
对此部分的处理步骤,我把它简述如下:
基于MethodParameter构建NameValueInfo <– 主要有name, defaultValue, required(其实主要是解析方法参数上标注的注解~)
通过BeanExpressionResolver(${}占位符以及SpEL) 解析name
通过模版方法resolveName从 HttpServletRequest, Http Headers, URI template variables 等等中获取对应的属性值(具体由子类去实现)
对 arg==null这种情况的处理, 要么使用默认值, 若 required = true && arg == null, 则一般报出异常(boolean类型除外~)
通过WebDataBinder将arg转换成Methodparameter.getParameterType()类型(注意:这里仅仅只是用了数据转换而已,并没有用bind()方法)
该抽象类继承树
从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:
根据MethodParameter创建NameValueInfo(子类的实现可继承自NameValueInfo,就是对应注解的属性们)
根据方法参数名称name从HttpServletRequest, Http Headers, URI template variables等等中获取属性值
对arg == null这种情况的处理(非必须)
PathVariableMethodArgumentResolver
它帮助Spring MVC
实现restful风格的URL。它用于处理标注有@PathVariable
注解的方法参数,用于从URL中获取值(并不是?后面的参数哦)。
并且,并且,并且它还可以解析@PathVariable
注解的value值不为空的Map(使用较少,个人不太建议使用)~
UriComponentsContributor
UriComponentsContributor
接口:通过查看方法参数和参数值并决定应更新目标URL的哪个部分,为构建UriComponents
的策略接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface UriComponentsContributor { boolean supportsParameter (MethodParameter parameter) ; void contributeMethodArgument (MethodParameter parameter, Object value, UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) ;}
它的三个实现类:
关于此接口的使用,后面再重点介绍,此处建议自动选择性忽略。
PathVariableMethodArgumentResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 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); } ... }
关于@PathVariable
的使用,不用再给例子了。
说明:因为使用路径参数需要进行复杂的匹配流程以及正则匹配,所有效率相较来说低些,若以若是那种对响应事件强要求的(比如记录点击事件…),建议用请求参数代替(当然你也可以重写RequestMappingHandlerMapping的URL匹配方法来定制化你的需求)。 GET /list/cityId/1 属于RESTful /list/cityId?cityId=1不属于RESTful。通过Apache JMeter测试:非RESTful接口的性能是RESTful接口的两倍,接口相应时间上更是达到10倍左右(是–>300ms左右 非–>20ms左右)
RequestParamMethodArgumentResolver
顾名思义,是解析标注有@RequestParam的方法入参解析器,这个注解比上面的注解强大很多了,它用于从请求参数(?后面的)中获取值完成封装。这是我们的绝大多数使用场景。除此之外,它还支持MultipartFile,也就是说能够从MultipartHttpServletRequest | HttpServletRequest 获取数据,并且并且并且还兜底处理没有标注任何注解的“简单类型”~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 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; } @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 ()); } @Override @Nullable protected Object resolveName (String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (servletRequest != null ) { Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } } Object arg = null ; MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class); if (multipartRequest != null ) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0 ) : files); } } if (arg == null ) { String[] paramValues = request.getParameterValues(name); if (paramValues != null ) { arg = (paramValues.length == 1 ? paramValues[0 ] : paramValues); } } return arg; } private static class RequestParamNamedValueInfo extends NamedValueInfo { public RequestParamNamedValueInfo () { super ("" , false , ValueConstants.DEFAULT_NONE); } public RequestParamNamedValueInfo (RequestParam annotation) { super (annotation.name(), annotation.required(), annotation.defaultValue()); } } ... }
可以看到这个ArgumentResolver
处理器还是很强大的:不仅能处理标注了@RequestParam
的参数,还能接收文件上传参数。甚至那些你平时使用中不标注该注解的 封装也是它来兜底完成 的。至于它如何兜底的,可以参见下面这个骚操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { ... private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers () { List<HandlerMethodArgumentResolver> resolvers = new ArrayList <>(); resolvers.add(new RequestParamMethodArgumentResolver (getBeanFactory(), false )); ... resolvers.add(new RequestParamMethodArgumentResolver (getBeanFactory(), true )); resolvers.add(new ServletModelAttributeMethodProcessor (true )); return resolvers; } ... }
可以看到ServletModelAttributeMethodProcessor
和RequestParamMethodArgumentResolver
一样,也是有兜底的效果的。
get请求如何传值数组、集合
如题的这个case
太常见了有木有,我们经常会遇到使用get请求向后端需要传值的需求(比如根据ids批量查询)。但到底如何传,URL怎么写,应该是有傻傻分不清楚的不确定的情况。
@PathVariable传参 1 2 3 4 5 6 @ResponseBody @GetMapping("/test/{objects}") public Object test (@PathVariable List<Object> objects) {System.out.println(objects); return objects;}
请求URL:/test/fsx,fsx,fsx
。控制台打印:
集合接收成功(使用@PathVariable Object[] objects
也是可以正常接收的)。
使用时应注意如下两点:
多个值只能使用,号分隔才行(否则会被当作一个值,放进数组/集合里,不会报错)
@PathVariable注解是必须的。否则会交给ServletModelAttributeMethodProcessor兜底去处理,它要求有 空构造所以反射创建实例会报错(数组/List)。(注意:如果是这样写ArrayList objects,那是不会报错的,只是值肯定是封装不进来的,一个空对象而已)
说明:为何逗号分隔的String类型默认就能转化为数组,集合。请参考StringToCollectionConverter/StringToArrayConverter
这种内置的GenericConverter
通用转换器~~
@RequestParam传参 1 2 3 4 5 6 7 @ResponseBody @GetMapping("/test") public Object test (@RequestParam List<Object> objects) { System.out.println(objects); return objects; }
请求URL:/test/?objects=1,2,3
。控制台打印:
请求URL改为:/test/?objects=1&objects=2&objects=3
。控制台打印:
两个请求的URL不一样,但都能正确的达到效果 。(@RequestParam Object[] objects
这么写两种URL也能正常封装)
对此有如下这个细节你必须得注意:对于集合List
而言@RequestParam
注解是必须 存在的,否则报错如下(因为交给兜底处理了):
但如果你这么写String[] objects
,即使不写注解 ,也能够正常完成正确封装 。
说明:Object[] objects
这么写的话不写注解是不行的(报错如上)。至于原因,各位小伙伴可以自行思考,没想明白的话可以给我留言(建议小伙伴一定要弄明白缘由)~
注意 需要注意的是,Spring MVC
的这么多HandlerMethodArgumentResolver
它的解析是有顺序的:如果多个HandlerMethodArgumentResolver
都可以解析某一种类型,以顺序在前面的 先解析(后面的就不会再执行解析了)。
由于RequestParamMethodArgumentResolver同样可以对Multipart文件上传进行解析,并且默认顺序在RequestPartMethodArgumentResolver之前,所以如果不添加@RequestPart注解,Multipart类型的参数会被RequestParamMethodArgumentResolver解析。
@RequestHeader
注解,可以把Request请求header部分的值绑定到方法的参数上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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()); } } }
此处理器能处理的是我们这么来使用:
1 2 3 4 5 6 7 8 9 @ResponseBody @GetMapping("/test") public Object test (@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Accept-Encoding") List<String> encodingList) { System.out.println(encoding); System.out.println(encodingList); return encoding; }
请求头截图:
结果打印(集合封装成功了,证明逗号分隔是可以被封装成集合/数组的):
1 2 gzip, deflate, br [gzip, deflate, br]
RequestAttributeMethodArgumentResolver
处理必须标注有@RequestAttribute
注解的参数,原理说这一句话就够了。
1 return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
SessionAttributeMethodArgumentResolver
同上(注解不一样,scope不一样而已)
AbstractCookieValueMethodArgumentResolver
对解析标注有@CookieValue
的做了一层抽象,子类负责从request里拿值(该抽象类不合请求域绑定)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public abstract class AbstractCookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { public AbstractCookieValueMethodArgumentResolver (@Nullable ConfigurableBeanFactory beanFactory) { super (beanFactory); } @Override public boolean supportsParameter (MethodParameter parameter) { return parameter.hasParameterAnnotation(CookieValue.class); } @Override protected NamedValueInfo createNamedValueInfo (MethodParameter parameter) { CookieValue annotation = parameter.getParameterAnnotation(CookieValue.class); Assert.state(annotation != null , "No CookieValue annotation" ); return new CookieValueNamedValueInfo (annotation); } @Override protected void handleMissingValue (String name, MethodParameter parameter) throws ServletRequestBindingException { throw new MissingRequestCookieException (name, parameter); } ... }
ServletCookieValueMethodArgumentResolver
指定了从HttpServletRequest
去拿cookie值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 ; } } }
一般我们这么来用:
1 2 3 4 5 6 7 8 @ResponseBody @GetMapping("/test") public Object test (@CookieValue("JSESSIONID") Cookie cookie, @CookieValue("JSESSIONID") String cookieValue) { System.out.println(cookie); System.out.println(cookieValue); return cookieValue; }
手动设置一个cookie值,然后请求
控制台打印如下:
1 2 javax.servlet.http.Cookie@401ef395 123456
MatrixVariableMethodArgumentResolver
标注有@MatrixVariable注解的参数的处理器。Matrix:矩阵,这个注解是Spring3.2新提出来的,增强Restful的处理能力(配合@PathVariable使用),比如这类URL的解析就得靠它:/owners/42;q=11/pets/21;s=23;q=22。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { public MatrixVariableMethodArgumentResolver () { super (null ); } @Override public boolean supportsParameter (MethodParameter parameter) { if (!parameter.hasParameterAnnotation(MatrixVariable.class)) { return false ; } if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class); return (matrixVariable != null && StringUtils.hasText(matrixVariable.name())); } return true ; } ... }
ExpressionValueMethodArgumentResolver
它用于处理标注有@Value
注解的参数。对于这个注解我们太熟悉不过了,没想到在web层依旧能发挥作用。本文就重点来会会它~
通过@Value
让我们在配置文件里给参数赋值,在某些特殊场合(比如前端不用传,但你想给个默认值,这个时候用它也是一种方案)
说明:这就相当于在Controller层使用了@Value注解,其实我是不太建议的。因为@Value建议还是只使用在业务层~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { public ExpressionValueMethodArgumentResolver (@Nullable ConfigurableBeanFactory beanFactory) { super (beanFactory); } @Override public boolean supportsParameter (MethodParameter parameter) { return parameter.hasParameterAnnotation(Value.class); } @Override protected NamedValueInfo createNamedValueInfo (MethodParameter parameter) { Value ann = parameter.getParameterAnnotation(Value.class); Assert.state(ann != null , "No Value annotation" ); return new ExpressionValueNamedValueInfo (ann); } @Override @Nullable protected Object resolveName (String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception { return null ; } @Override protected void handleMissingValue (String name, MethodParameter parameter) throws ServletException { throw new UnsupportedOperationException ("@Value is never required: " + parameter.getMethod()); } private static final class ExpressionValueNamedValueInfo extends NamedValueInfo { private ExpressionValueNamedValueInfo (Value annotation) { super ("@Value" , false , annotation.value()); } } }
根本原理其实只是利用了defaultValue
支持占位符和SpEL
的特性而已。给个使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @PropertySource("classpath:my.properties") @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { ... }@ResponseBody @GetMapping("/test") public Object test (@Value("#{T(Integer).parseInt('${test.myage:10}') + 10}") Integer myAge) { System.out.println(myAge); return myAge; }
请求:/test,打印:28。 注意:若你写成@Value(“#{‘${test.myage:10}’ + 10},那你得到的答案是:1810(成字符串拼接了)。
另外,我看到网上有不少人说如果把这个@PropertySource(“classpath:my.properties”)放在根容器的config文件里导入,controller层就使用@Value/占位符获取不到值了,其实这是不正确 的。理由如下:
Spring MVC子容器在创建时:initWebApplicationContext()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 if (cwac.getParent() == null ) { cwac.setParent(rootContext); } AbstractApplicationContext:如下代码 @Override public void setParent (@Nullable ApplicationContext parent) { this .parent = parent; if (parent != null ) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } } AbstractEnvironment:merge()方法如下 @Override public void merge (ConfigurableEnvironment parent) { for (PropertySource<?> ps : parent.getPropertySources()) { if (!this .propertySources.contains(ps.getName())) { this .propertySources.addLast(ps); } } ... }
这就是为什么说即使你是在根容器里使用的@PropertySource
导入的外部资源,子容器也可以使用的原因(因为子容器会把父环境给merge
一份过来)。
参数类型是Map的
数据来源同上 ,只是参数类型是Map
这类解析器我认为是对第一类的有些处理器的一种补充,它依赖上面的相关注解。 你是否想过通过@RequestParam一次性全给封装进一个Map里,然后再自己分析?同样的本类处理器给@RequestHeader、@PathVariable、@MatrixVariable都赋予了这种能力~
PathVariableMapMethodArgumentResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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(); } } }
RequestParamMapMethodArgumentResolver
它依赖的方法是:HttpServletRequest#getParameterMap()
、MultipartRequest#getMultiFileMap()
、MultipartRequest#getFileMap()
等,出现于Spring 3.1
。
示例 1 2 3 4 5 6 @ResponseBody @GetMapping("/test") public Object test (@RequestParam Map<String,Object> params) { System.out.println(params); return params; }
请求:/test?name=fsx&age=18&age=28
。打印
从结果看出:
它不能传一key多值情况
若出现相同的key,以在最前面的key的值为准。
Map实例是一个LinkedHashMap
实例
一次性把请求头信息都拿到:数据类型支出写MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map
。
示例 1 2 3 4 5 6 @ResponseBody @GetMapping("/test") public Object test (@RequestHeader Map<String, Object> headers) { headers.forEach((k, v) -> System.out.println(k + "-->" + v)); return headers; }
请求打印:
1 2 3 4 5 6 7 8 9 10 11 12 host--> localhost:8080 connection--> keep-alive cache-control--> max-age=0 upgrade-insecure-requests--> 1 user-agent--> Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 sec-fetch-mode--> navigate sec-fetch-user--> ?1 accept--> text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 sec-fetch-site--> none accept-encoding--> gzip, deflate, br accept-language--> zh-CN,zh;q=0.9 cookie--> JSESSIONID=123456789
不过强烈不建议直接使用Map
,而是使用HttpHeaders
类型。这么写@RequestHeader HttpHeaders headers
,获取的时候更为便捷。
MapMethodProcessor
它处理Map类型,但没有标注任何注解的情况 ,它的执行顺序是很靠后的,所以有点兜底的意思。
这个处理器同时也解释了:为何你方法入参上写个Map、HashMap、ModelMap
等等就可以非常便捷的获取到模型的值的原因~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 { return mavContainer.getModel(); } }
固定参数类型
参数比如是SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream
等
这种方式使用得其实还比较多的。比如平时我们需要用Servlet源生的API:HttpServletRequest, HttpServletResponse
肿么办? 在Spring MVC
内就特别特别简单,只需要在入参上声明:就可以直接使用啦~
ServletRequestMethodArgumentResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @Nullable private static Class<?> pushBuilder; static { try { pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder" , ServletRequestMethodArgumentResolver.class.getClassLoader()); } catch (ClassNotFoundException ex) { pushBuilder = null ; } } @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(); if (WebRequest.class.isAssignableFrom(paramType)) { if (!paramType.isInstance(webRequest)) { throw new IllegalStateException ( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } return webRequest; } if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { return resolveNativeRequest(webRequest, paramType); } return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class)); } private <T> T resolveNativeRequest (NativeWebRequest webRequest, Class<T> requiredType) { T nativeRequest = webRequest.getNativeRequest(requiredType); if (nativeRequest == null ) { throw new IllegalStateException ( "Current request is not of type [" + requiredType.getName() + "]: " + webRequest); } return nativeRequest; } @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()); } throw new UnsupportedOperationException ("Unknown parameter type: " + paramType.getName()); } private static class PushBuilderDelegate { @Nullable public static Object resolvePushBuilder (HttpServletRequest request, Class<?> paramType) { PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null && !paramType.isInstance(pushBuilder)) { throw new IllegalStateException ( "Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder); } return pushBuilder; } } }
看到这你应该明白,以后你需要使用这些参数的话,直接在方法上申明即可,不需要自己再去get了,又是一种依赖注入的效果体现有木有~
ServletResponseMethodArgumentResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter (MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (ServletResponse.class.isAssignableFrom(paramType) || OutputStream.class.isAssignableFrom(paramType) || Writer.class.isAssignableFrom(paramType)); } @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(); if (ServletResponse.class.isAssignableFrom(paramType)) { return resolveNativeResponse(webRequest, paramType); } return resolveArgument(paramType, resolveNativeResponse(webRequest, ServletResponse.class)); } private <T> T resolveNativeResponse (NativeWebRequest webRequest, Class<T> requiredType) { T nativeResponse = webRequest.getNativeResponse(requiredType); if (nativeResponse == null ) { throw new IllegalStateException ( "Current response is not of type [" + requiredType.getName() + "]: " + webRequest); } return nativeResponse; } 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(); } throw new UnsupportedOperationException ("Unknown parameter type: " + paramType); } }
SessionStatusMethodArgumentResolver
支持SessionStatus
。值为:mavContainer.getSessionStatus();
UriComponentsBuilderMethodArgumentResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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
来得到URL的各个部分,以及构建URL都是非常的方便的。
RedirectAttributesMethodArgumentResolver
和重定向属性RedirectAttributes
相关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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; } }
如果涉及到重定向:多个视图见传值,使用它还是比较方便的。
ModelMethodProcessor
允许你入参里写:org.springframework.ui.Model
、RedirectAttributes
、RedirectAttributesModelMap
、ConcurrentModel
、ExtendedModelMap
等等
ModelAttributeMethodProcessor
ModelAttributeMethodProcessor
:主要是针对 被 @ModelAttribute
注解修饰且不是普通类型(通过 !BeanUtils.isSimpleProperty
来判断)的参数。
1 2 3 4 5 6 7 8 9 10 public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver , HandlerMethodReturnValueHandler { @Override public boolean supportsParameter (MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this .annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); } }
基于ContentType
消息转换器类型
我们使用非常频繁的@RequestBody
是怎么封装请求体的呢???这块使用非常广泛的地方却还木有讲解到,因为它的处理方式和前面的不太一样。
利用HttpMessageConverter
将输入流 转换成对应的参数
AbstractMessageConverterMethodArgumentResolver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver { private static final Set<HttpMethod> SUPPORTED_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH); private static final Object NO_VALUE = new Object (); protected final Log logger = LogFactory.getLog(getClass()); protected final List<HttpMessageConverter<?>> messageConverters; protected final List<MediaType> allSupportedMediaTypes; private final RequestResponseBodyAdviceChain advice; public AbstractMessageConverterMethodArgumentResolver (List<HttpMessageConverter<?>> converters) { this (converters, null ); } public AbstractMessageConverterMethodArgumentResolver (List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) { Assert.notEmpty(converters, "'messageConverters' must not be empty" ); this .messageConverters = converters; this .allSupportedMediaTypes = getAllSupportedMediaTypes(converters); this .advice = new RequestResponseBodyAdviceChain (requestResponseBodyAdvice); } private static List<MediaType> getAllSupportedMediaTypes (List<HttpMessageConverter<?>> messageConverters) { Set<MediaType> allSupportedMediaTypes = new LinkedHashSet <>(); for (HttpMessageConverter<?> messageConverter : messageConverters) { allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); } List<MediaType> result = new ArrayList <>(allSupportedMediaTypes); MediaType.sortBySpecificity(result); return Collections.unmodifiableList(result); } RequestResponseBodyAdviceChain getAdvice () { return this .advice; } @Nullable protected <T> Object readWithMessageConverters (NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpInputMessage inputMessage = createInputMessage(webRequest); return readWithMessageConverters(inputMessage, parameter, paramType); } ... }
说明:此抽象类并没有实现resolveArgument()
这个接口方法,而只是提供了一些protected
方法,作为工具方法给子类调用,比如最为重要的这个方法:readWithMessageConverters()
就是利用消息转换器解析HttpInputMessage
的核心。
它的继承树如下:
RequestPartMethodArgumentResolver
它用于解析参数被@RequestPart修饰,或者参数类型是MultipartFile | Servlet 3.0提供的javax.servlet.http.Part类型(并且没有被@RequestParam修饰),数据通过 HttpServletRequest获取
当属性被标注为@RequestPart的话,那就会经过HttpMessageConverter结合Content-Type来解析,这个效果特别像@RequestBody的处理方式~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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); } private String getPartName (MethodParameter methodParam, @Nullable RequestPart requestPart) { String partName = (requestPart != null ? requestPart.name() : "" ); if (partName.isEmpty()) { partName = methodParam.getParameterName(); if (partName == null ) { throw new IllegalArgumentException ("Request part name for argument type [" + methodParam.getNestedParameterType().getName() + "] not specified, and parameter name information not found in class file either." ); } } return partName; } }
此处理器用于解析@RequestPart
参数类型,它和多部分文件上传有关。关于Spring MVC
中的文件上传,此处就不便展开了。后面有个专题专门讲解Spring MVC
中的上传、下载~
####AbstractMessageConverterMethodProcessor
命名为Processor
说明它既能处理入参,也能处理返回值,当然本文的关注点是方法入参(和HttpMessageConverter
相关)。
请求body体 一般是一段字符串/字节流,查询参数可以看做URL的一部分,这两个是位于请求报文的不同地方。
表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。
响应body体 则是response返回的具体内容,对于一个普通的html页面,body里面就是页面的源代码。对于HttpMessage
响应体里可能就是个json串(但无强制要求)。
响应体一般都会结合Content-Type
一起使用,告诉客户端只有知道这个头了才知道如何渲染。
AbstractMessageConverterMethodProcessor
源码稍显复杂,它和Http协议、内容协商有很大的关联:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet <>(Arrays.asList( "txt" , "text" , "yml" , "properties" , "csv" , "json" , "xml" , "atom" , "rss" , "png" , "jpe" , "jpeg" , "jpg" , "gif" , "wbmp" , "bmp" )); private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet <>( Arrays.asList("audio" , "image" , "video" )); private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES = Arrays.asList(MediaType.ALL, new MediaType ("application" )); private static final Type RESOURCE_REGION_LIST_TYPE = new ParameterizedTypeReference <List<ResourceRegion>>() { }.getType(); private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper (); private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper (); static { rawUrlPathHelper.setRemoveSemicolonContent(false ); rawUrlPathHelper.setUrlDecode(false ); } private final ContentNegotiationManager contentNegotiationManager; private final PathExtensionContentNegotiationStrategy pathStrategy; private final Set<String> safeExtensions = new HashSet <>(); protected AbstractMessageConverterMethodProcessor (List<HttpMessageConverter<?>> converters) { this (converters, null , null ); } protected AbstractMessageConverterMethodProcessor (List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager contentNegotiationManager) { this (converters, contentNegotiationManager, null ); } protected AbstractMessageConverterMethodProcessor (List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) { super (converters, requestResponseBodyAdvice); this .contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager ()); this .pathStrategy = initPathStrategy(this .contentNegotiationManager); this .safeExtensions.addAll(this .contentNegotiationManager.getAllFileExtensions()); this .safeExtensions.addAll(WHITELISTED_EXTENSIONS); } private static PathExtensionContentNegotiationStrategy initPathStrategy (ContentNegotiationManager manager) { Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class; PathExtensionContentNegotiationStrategy strategy = manager.getStrategy(clazz); return (strategy != null ? strategy : new PathExtensionContentNegotiationStrategy ()); } protected ServletServerHttpResponse createOutputMessage (NativeWebRequest webRequest) { HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Assert.state(response != null , "No HttpServletResponse" ); return new ServletServerHttpResponse (response); } protected <T> void writeWithMessageConverters (T value, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); writeWithMessageConverters(value, returnType, inputMessage, outputMessage); } @SuppressWarnings({"rawtypes", "unchecked"}) protected <T> void writeWithMessageConverters (@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {... } ... }
本类的核心是各式各样的HttpMessageConverter
消息转换器,因为最终的write都是交给它们去完成。
此抽象类里,它完成了内容协商~
既然父类都已经完成了这么多事,那么子类自然就非常的简单的。看看它的两个具体实现子类:
RequestResponseBodyMethodProcessor
顾名思义,它负责处理@RequestBody
这个注解的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @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(); 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); } }
HttpEntityMethodProcessor
用于处理HttpEntity
和RequestEntity
类型的入参的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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); Type paramType = getHttpEntityType(parameter); if (paramType == null ) { throw new IllegalArgumentException ("HttpEntity parameter '" + parameter.getParameterName() + "' in method " + parameter.getMethod() + " is not parameterized" ); } Object body = readWithMessageConverters(webRequest, parameter, paramType); if (RequestEntity.class == parameter.getParameterType()) { return new RequestEntity <>(body, inputMessage.getHeaders(), inputMessage.getMethod(), inputMessage.getURI()); } else { return new HttpEntity <>(body, inputMessage.getHeaders()); } } }
注意:这里可没有validate校验了,这也是经常被面试问到的:使用HttpEntity
和@RequestBody
有什么区别呢?
从代码里可以直观的看到:有了抽象父类后,子类需要做的事情已经很少了,只需要匹配参数类型、做不同的返回而已。关于它俩的使用案例,此处不用再展示了,因为各位平时工作中都在使用,再熟悉不过了。但针对他俩的使用,我总结出如下几个小细节,供以参考:
@RequestBody/HttpEntity它的参数(泛型)类型允许是Map
方法上的和类上的@ResponseBody都可以被继承,但@RequestBody不可以
@RequestBody它自带有Bean Validation校验能力(当然需要启用),HttpEntity更加的轻量和方便
HttpEntity/RequestEntity
所在包是:org.springframework.http
,属于spring-web@RequestBody
位于org.springframework.web.bind.annotation
,同样属于spring-web
最后还落了一个ErrorsMethodArgumentResolver
,在这里补充一下:
ErrorsMethodArgumentResolver
它用于在方法参数可以写Errors
类型,来拿到数据校验结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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()); 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()); } }
Spring MVC参数处理器的注册与顺序
到这里,一个不落的把Spring MVC内置提供的参数处理器ArgumentResolver说了个遍。 前面我有提到过:参数处理对处理器的顺序是敏感的,因此我们需要关注Spring MVC最终的执行顺序,这时候我们的聚合容器HandlerMethodArgumentResolverComposite就出场了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList <>(); private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap <>(256 ); ... public void clear () { this .argumentResolvers.clear(); } @Override public boolean supportsParameter (MethodParameter parameter) { return getArgumentResolver(parameter) != null ; } @Override @Nullable public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null ) { throw new IllegalArgumentException ("Unsupported parameter type [" + parameter.getParameterType().getName() + "]." + " supportsParameter should be called first." ); } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } ... @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; } }
缺省情况Spring MVC
注册的处理器(顺序)如下:
它初始化处的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 RequestMappingHandlerAdapter: @Override public void afterPropertiesSet () { ... if (this .argumentResolvers == null ) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this .argumentResolvers = new HandlerMethodArgumentResolverComposite ().addResolvers(resolvers); } if (this .initBinderArgumentResolvers == null ) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this .initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite ().addResolvers(resolvers); } ... }
注意:这里面initBinderArgumentResolvers
最终只会有12个处理器,因为它的注册方法如下(也是这个顺序):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers () { List<HandlerMethodArgumentResolver> resolvers = new ArrayList <>(); 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 ExpressionValueMethodArgumentResolver (getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver ()); resolvers.add(new RequestAttributeMethodArgumentResolver ()); resolvers.add(new ServletRequestMethodArgumentResolver ()); resolvers.add(new ServletResponseMethodArgumentResolver ()); if (getCustomArgumentResolvers() != null ) { resolvers.addAll(getCustomArgumentResolvers()); } resolvers.add(new RequestParamMethodArgumentResolver (getBeanFactory(), true )); return resolvers; }
前面有提到过说标注有@InitBInder
注解里也可以写很多类型的参数,但因为它只会有12个处理器,所以有些参数它是不能写的(比如@RequestBody
、Errors
等等这种都是不能写的),不用一一枚举,做到心中有数就成。