每篇一句
黃金的導電性最好,為什麼電腦主機闆還是要用銅?
飛機最快,為什麼還有人做火車?
清華大學最好,為什麼還有人去普通學校?
因為資源都是有限的,我們現實生活中必須兼顧成本與産出的平衡
前言
上文 介紹了
Spring MVC
用于處理入參的處理器:
HandlerMethodReturnValueHandler
它的作用,以及介紹了最為常用的兩個參數處理器子類:
PathVariableMethodArgumentResolver
和
RequestParamMethodArgumentResolver
。由于該體系的重要以及龐大,本文将接着繼續講解~
第一類:基于 Name
(續)
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
注解的參數,原理說這一句話就夠了。
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
(成字元串拼接了)。
另外,我看到網上有不少人說如果把這個
@PropertySource("classpath:my.properties")
放在根容器的config檔案裡導入,controller層就使用
@Value
/占位符擷取不到值了,其實這是**
不正确
**的。理由如下:
Spring MVC
子容器在建立時:
initWebApplicationContext()
if (cwac.getParent() == null) {
cwac.setParent(rootContext); // 設定上父容器(根容器)
}
AbstractApplicationContext:如下代碼
// 相當于子容器的環境會把父容器的Enviroment合并進來
@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) {
// 完全的從parent裡所有的PropertySources裡拷貝一份進來
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
this.propertySources.addLast(ps);
}
}
...
}
這就是為什麼說即使你是在根容器裡使用的
@PropertySource
導入的外部資源,子容器也可以使用的原因(因為子容器會把父環境給
merge
一份過來)。
但是,但是,但是:如果你是使用形如 PropertyPlaceholderConfigurer
這種方式導進來的,那是會有容器隔離效應的~
第二類:參數類型是 Map
的
Map
資料來源同上,隻是參數類型是Map
這類解析器我認為是對第一類的有些處理器的一種補充,它依賴上面的相關注解。
你是否想過通過
@RequestParam
一次性全給封裝進一個
Map
裡,然後再自己分析?同樣的本類處理器給
@RequestHeader
、
@PathVariable
、
@MatrixVariable
都賦予了這種能力~
PathVariableMapMethodArgumentResolver
// @since 3.2 晚一個版本号
public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
// 必須标注@PathVariable注解 并且類型是Map,并且注解不能有value值
// 處理情況和PathVariableMethodArgumentResolver形成了互補
@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 {
... // 處理上極其簡單,把所有的路徑參數使用Map裝着傳回即可
}
}
RequestParamMapMethodArgumentResolver
它依賴的方法是:
HttpServletRequest#getParameterMap()
、
MultipartRequest#getMultiFileMap()
、
MultipartRequest#getFileMap()
等,出現于
Spring 3.1
。
示範一把:
@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<String,String>
RequestHeaderMapMethodArgumentResolver
一次性把請求頭資訊都拿到:資料類型支出寫
MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map
。執行個體如下:
@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
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
,擷取的時候更為便捷。
MatrixVariableMapMethodArgumentResolver
略。
MapMethodProcessor
它處理Map類型,但沒有标注任何注解的情況,它的執行順序是很靠後的,是以有點兜底的意思。
// @since 3.1
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType());
}
// 處理邏輯非常簡單粗暴:把Model直接傳回~~~~
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getModel();
}
}
使用案例:略。
這個處理器同時也解釋了:為何你方法入參上寫個 Map、HashMap、ModelMap
等等就可以非常便捷的擷取到模型的值的原因~
第三類:固定參數類型
參數比如是 SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream
等
這種方式使用得其實還比較多的。比如平時我們需要用Servlet源生的API:
HttpServletRequest, HttpServletResponse
腫麼辦? 在
Spring MVC
内就特别特别簡單,隻需要在入參上聲明:就可以直接使用啦~
ServletRequestMethodArgumentResolver
// 它支援到的可不僅僅是ServletRequest,多到令人發指
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
// 連Servlet 4.0的PushBuilder都支援了(Spring5.0以上版本支援的)
@Nullable
private static Class<?> pushBuilder;
static {
try {
pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder",
ServletRequestMethodArgumentResolver.class.getClassLoader());
} catch (ClassNotFoundException ex) {
// Servlet 4.0 PushBuilder not found - not supported for injection
pushBuilder = null;
}
}
// 支援"注入"的類型,可謂多多益善
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) || // webRequest.getNativeRequest(requiredType)
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) || //request.getSession()
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || //PushBuilderDelegate.resolvePushBuilder(request, paramType);
Principal.class.isAssignableFrom(paramType) || //request.getUserPrincipal()
InputStream.class.isAssignableFrom(paramType) || // request.getInputStream()
Reader.class.isAssignableFrom(paramType) || //request.getReader()
HttpMethod.class == paramType || //HttpMethod.resolve(request.getMethod());
Locale.class == paramType || //RequestContextUtils.getLocale(request)
TimeZone.class == paramType || //RequestContextUtils.getTimeZone(request)
ZoneId.class == paramType); //RequestContextUtils.getTimeZone(request);
}
}
看到這你應該明白,以後你需要使用這些參數的話,直接在方法上申明即可,不需要自己再去get了,又是一種依賴注入的效果展現有木有~
ServletResponseMethodArgumentResolver
// @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);
}
...
}
}
SessionStatusMethodArgumentResolver
支援
SessionStatus
。值為:
mavContainer.getSessionStatus();
UriComponentsBuilderMethodArgumentResolver
// @since 3.1
public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {
// UriComponentsBuilder/ ServletUriComponentsBuilder
@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);
return ServletUriComponentsBuilder.fromServletMapping(request);
}
}
通過
UriComponentsBuilder
來得到URL的各個部分,以及建構URL都是非常的友善的。
RedirectAttributesMethodArgumentResolver
和重定向屬性
RedirectAttributes
相關。
// @since 3.1
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 {
ModelMap redirectAttributes;
// 把DataBinder傳入到RedirectAttributesModelMap裡面去~~~~
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
:主要是針對 被
@ModelAttribute
注解修飾且不是普通類型(通過
!BeanUtils.isSimpleProperty
來判斷)的參數。
// @since 3.1
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
// 标注有@ModelAttribute它會處理
// 若沒有标注(隻要不是“簡單類型”),它也會兜底處理
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
}
關于
@ModelAttribute
這塊的使用,參見這裡
總結
本文介紹完了四大類的前面三種類型,其中最為常用的是前兩種類型的使用,希望大家可以掌握,和好好發揮~
相關閱讀
HandlerMethodArgumentResolver:Controller入參自動封裝器(将方法參數parameter解析為參數值)【享學Spring MVC】
從原理層面掌握@ModelAttribute的使用(核心原理篇)【享學Spring MVC】
從原理層面掌握@ModelAttribute的使用(使用篇)【享學Spring MVC】
HandlerMethodArgumentResolver(一):Controller方法入參自動封裝器(将參數parameter解析為值)【享學Spring MVC】
HandlerMethodArgumentResolver(二):Map參數類型和固定參數類型【享學Spring MVC】
HandlerMethodArgumentResolver(三):基于HttpMessageConverter消息轉換器的參數處理器【享學Spring MVC】
關注A哥
Author | A哥(YourBatman) |
---|---|
個人站點 | www.yourbatman.cn |
[email protected] | |
微 信 | fsx641385712 |
| |
公衆号 | BAT的烏托邦(ID:BAT-utopia) |
知識星球 | BAT的烏托邦 |
每日文章推薦 | 每日文章推薦 |