前言
上文 介绍了Spring MVC用于处理入参的处理器:HandlerMethodReturnValueHandler它的作用,以及介绍了最为常用的两个参数处理器子类:PathVariableMethodArgumentResolver和RequestParamMethodArgumentResolver。由于该体系的重要以及庞大,本文将接着继续讲解~
第一类:基于Name(续)
RequestHeaderMethodArgumentResolver
@RequestHeader注解,可以把Request请求header部分的值绑定到方法的参数上。
public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { // 必须标注@RequestHeader注解,并且不能,不能,不能是Map类型 // 有的小伙伴会说:`@RequestHeader Map headers`这样可以接收到所有的请求头啊 // 其实不是本类的功劳,是`RequestHeaderMapMethodArgumentResolver`的作用 @Override public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(RequestHeader.class) && !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())); } // 理解起来很简单:可以单值,也可以List/数组 @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; } } }
此处理器能处理的是我们这么来使用:
@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; }
请求头截图:
结果打印(集合封装成功了,证明逗号分隔是可以被封装成集合/数组的):
gzip, deflate, br [gzip, deflate, br]
Tip:注解指定的value值(key值)是不
区分大小写的
RequestAttributeMethodArgumentResolver
处理必须标注有@RequestAttribute
注解的参数,原理说这一句话就够了。
return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
SessionAttributeMethodArgumentResolver
同上(注解不一样,scope不一样而已)
AbstractCookieValueMethodArgumentResolver(抽象类)
对解析标注有@CookieValue的做了一层抽象,子类负责从request里拿值(该抽象类不合请求域绑定)。
public abstract class AbstractCookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { ... @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(CookieValue.class); } @Override protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException { throw new MissingRequestCookieException(name, parameter); } ... // 并木有实现核心resolveName方法 }
ServletCookieValueMethodArgumentResolver
指定了从HttpServletRequest
去拿cookie值。
public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValueMethodArgumentResolver { private UrlPathHelper urlPathHelper = new UrlPathHelper(); ... 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"); // 工具方法,底层是:request.getCookies() Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName); // 如果用javax.servlet.http.Cookie接受值,就直接返回了 if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) { return cookieValue; } else if (cookieValue != null) { // 否则返回cookieValue return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue()); } else { return null; } } }
一般我们这么来用:
@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值,然后请求
控制台打印如下:
javax.servlet.http.Cookie@401ef395 123456
Tips:在现在restful风格下,cookie使用得是很少的了。一般用于提升用户体验方面~
MatrixVariableMethodArgumentResolver
标注有@MatrixVariable注解的参数的处理器。Matrix:矩阵,这个注解是Spring3.2新提出来的,增强Restful的处理能力(配合@PathVariable使用),比如这类URL的解析就得靠它:/owners/42;q=11/pets/21;s=23;q=22。
关于@MatrixVariable它的使用案例,我找了两篇靠谱文章给你参考:
参考一
参考二
// @since 3.2 public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { // @MatrixVariable注解是必须的。然后技能处理普通类型,也能处理Map @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建议还是只使用在业务层~
// @since 3.1 public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { // 唯一构造函数 支持占位符、SpEL public ExpressionValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) { super(beanFactory); } //必须标注有@Value注解 @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Value.class); } @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { Value ann = parameter.getParameterAnnotation(Value.class); return new ExpressionValueNamedValueInfo(ann); } private static final class ExpressionValueNamedValueInfo extends NamedValueInfo { // 这里name传值为固定值 因为只要你的key不是这个就木有问题 // required传固定值false // defaultValue:取值为annotation.value() --> 它天然支持占位符和SpEL嘛 private ExpressionValueNamedValueInfo(Value annotation) { super("@Value", false, annotation.value()); } } // 这里恒返回null,因此即使你的key是@Value,也是不会采纳你的传值的哟~ @Override @Nullable protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception { // No name to resolve return null; } }
根本原理其实只是利用了defaultValue支持占位符和SpEL的特性而已。给个使用示例:
// 在MVC子容器中导入外部化配置 @Configuration @PropertySource("classpath:my.properties") // 此处有键值对:test.myage = 18 @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(成字符串拼接了)。