tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2
前言
HandleAdapter作爲spring mvc中最重要的組件沒有之一,源碼中隱藏着非常多對於項目編碼有益的知識點。原理(十一)已經介紹了HandleAdapter的基本原理和具體實現類RequestMappingHandlerAdapter處理請求的主要流程。
首先,上篇文章的要點開始於AbstractHandlerMethodAdapter的handle方法。在handle方法中調用了RequestMappingHandlerAdapter的handleInternal方法,而handleInternal方法中代碼邏輯中invokeHandlerMethod方法統領了後續處理請求的整個流程。本文主要就是invokeHandlerMethod方法進行分析,並且簡要介紹其中使用到的一些組件的原理。
其次,上篇文章還介紹了貫穿整個處理流程的數據體ModelAndViewContainer,其中包含的參數可以參考下面這張圖。
處理流程
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//1. 創建ServletWebRequest,在ArgumentResolver進行參數解析時使用
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//2. 獲取DataBinderFactory,處理和@initBinder相關的參數
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//3. 創建ModelFactory,處理model相關的操作
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//4. 創建ServletInvocableHandlerMethod,用來執行最終的請求處理
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//5. 設置參數解析器argumentResolvers的List
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//6. 設置返回結果的處理器List
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
//7. 設置DataBinderFactory
invocableMethod.setDataBinderFactory(binderFactory);
//8. 設置參數名獲取器
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//9. 創建ModelAndViewContainer
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//(如果有)設置重定向的model參數
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//10. 初始化model
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
//後面主要是異步處理相關
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//11. 傳入參數,處理請求
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
//12. 獲取返回結果
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
invokeHandlerMethod引用代碼中已經做了詳細的註釋,下面這張圖再對主要流程進行歸納總結,方便大家參考。文章後續會對流程分步分析,以全面探求最終實現的原理細節。
根據參數的分類可知,參與這個流程的參數不僅包括請求request,還有一部分model參數、session緩存參數和重定向model參數。這些參數的準備也需要分類討論,請求request只需要做一個轉換(註釋1),根據request和response生成ServletWebRequest,用在後續的argumentResolver的參數解析中。model參數和session參數的設置都在ModelFactory中,session參數使用了專用組件SessionAttributesHandler和SessionAttributeStore工具。處理請求和設置結果返回值統一在ServletInvocableHandlerMethod類中,這個類繼承自HandlerMethod類,設置了WebDataBinderFactory、HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler等組件協助。最後,getModelAndView(註釋12)從ModelAndViewContainer和ServletWebRequest獲得了最終的ModelAndView結果。
request轉換
第一步由request和response生成ServletWebRequest,ServletWebRequest中包含了HttpServletRequest和HttpServletResponse這兩個類的實例,其他參數還有HttpSession的實例。統一構造這樣一個類必要性,可能是爲了後續HandlerMethodArgumentResolver參數解析的方便?這裏存在一些疑問。
獲取WebDataBinderFactory
spring mvc中的Binder可以對請求中輸入的參數進行預處理,通過@IntiBinder註解獲取Binder後能夠註冊一個編輯器。例如,可以在Controller中定義如下的方法:
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true));
}
這樣,這個Controller下所有GET接口傳入的符合“yyyy-MM-dd HH:mm:ss”的String參數,都可以被自動轉化爲Date類型。spring mvc支持多種的編輯器,包括URL、URI、String、Date等。如果使用@ControllerAdvice註解,還可以定義全局或者特定包、特定類的Binder。如果有興趣可以自行查閱相關資料。
第2步中getBinderFactory方法創建了一個WebDataBinderFactory,同時把對應接口(HandlerMethod)的@InitBinder方法都設置到工廠類中。在後續參數解析時,HandlerMethodArgumentResolver或用到這個構造的WebDataBinderFactory實例。
創建ModelFactory和初始化model
第3步和第10步都是和ModelFactory有關。創建factory和初始化model過程中,就包含了上面流程圖中設置session參數和model參數這兩步。因爲model模型在現在的服務端業務中已經使用很少,所以這部分內容只做簡略介紹。
getModelFactory方法在創建ModelFactory時使用了三個參數:
- 註釋了@ModelAtrribute的方法
- 上一步中獲取的WebDataBinderFactory
- SessionAttributeHandler
在方法上註解@ModelAttribute,可以在Controller中的接口處理請求之前,添加或修改model中的屬性值。sessionAttributeHandler是用來處理@SessionAttribute註解相關的處理器。@SessionAttribute註解用來向session中添加參數。
第10步中的initModel方法所做的事情比較簡單,就是在第3步的基礎上,將@SessionAttribute和@ModelAttribute註解中取出的參數設置到ModelAndViewContainer的實例對象mavContainer中。
方法和方法的參數
WebDataBinderFactory、HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler等組件構造後,都被置入ServletInvocableHandlerMethod類中,實際發揮作用也是在類中被調用。在原理(九)中,HandlerMapping將對應請求的接口方法(HandlerMethod)搜索出來,最終的目的就是在這裏被使用。ServletInvocableHandlerMethod就繼承自HandlerMethod,具備HandlerMethod所有特性。
使用接口方法(HandlerMethod)處理請求,需要存儲方法(Method)和方法參數。
public class HandlerMethod {
private final Object bean;
@Nullable
private final BeanFactory beanFactory;
private final Class<?> beanType;
private final Method method;
private final Method bridgedMethod;
private final MethodParameter[] parameters;
......
private volatile List<Annotation[][]> interfaceParameterAnnotations;
······
}
HandlerMethod中存儲了接口方法所在類的bean和BeanFactroy實例。beanFactory可以在bean爲beanName(這時Object爲String),用來獲取真正的bean。method是用來存儲接口方法Method實例或者是bridge method。bridge Method是由java虛擬機的特殊機制產生,如果感興趣自行查閱資料,這裏只需要知道method和brigedMethod總會至少有一個存儲了方法本身。方法的參數使用MethodParameter的數組來存儲。interfaceParameterAnnotations是對應接口上的各種註解。
方法參數信息需要有參數序號、參數名、參數類型等,其中參數還可能複合結構,MethodParameter類在設計時需要考慮這一層。
public class MethodParameter {
private final Executable executable;
private final int parameterIndex;
@Nullable
private volatile Parameter parameter;
private int nestingLevel;
/** Map from Integer level to Integer type index. */
@Nullable
Map<Integer, Integer> typeIndexesPerLevel;
@Nullable
private volatile Class<?> containingClass;
@Nullable
private volatile Class<?> parameterType;
@Nullable
private volatile Type genericParameterType;
@Nullable
private volatile Annotation[] parameterAnnotations;
@Nullable
private volatile ParameterNameDiscoverer parameterNameDiscoverer;
@Nullable
private volatile String parameterName;
······
}
- Executable是Constructor和Method的父類,executable用來存儲方法。
- 方法所在的類使用containingClass存儲。
- parameterIndex代表該參數在方法中的序號。parameterType存儲參數的類型,genericParameterType是Type類型的參數類型。參數名是parameterName。
- Parameter是複合的參數類,裏面存儲了序號、參數名、方法等,並不一定必須。
- 還有兩個類似List這種複合參數的指標。nestingLevel代表嵌套級別,List直接作爲參數的話,List嵌套級別爲1, Integer嵌套級別爲2。對於Integer,還有對應的序號,typeIndexesPerLevel就是存儲對應嵌套層數的參數的序數。
- 一般來說使用反射無法獲取參數名,ParameterNameDiscoverer就是用來獲取參數名的組件。如果使用默認的DefaultParameterNameDiscoverer類,這個類使用多重策略,確保能夠查找到參數名。一是Java 8標準反射機制,回調基於ASM的反射機制查找class文件中的debug信息,獲取參數名。二是Kotlin反射機制。如果Kotlin的反射機制可用,則會被第一個使用。
參數解析與方法調用
invocableMethod.invokeAndHandle(webRequest, mavContainer);
invokeAndHandle方法的首先調用了:
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
。invokeForRequest方法的具體代碼邏輯是由ServletInvocableHandlerMethod的父類InvocableHandlerMethod實現,而InvocableHandlerMethod又繼承自HandlerMethod。InvocableHandlerMethod主要的工作是參數的解析。
public class InvocableHandlerMethod extends HandlerMethod {
private static final Object[] EMPTY_ARGS = new Object[0];
@Nullable
private WebDataBinderFactory dataBinderFactory;
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
......
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Arguments: " + Arrays.toString(args));
}
return this.doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (this.logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
this.logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
@Nullable
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(this.getBridgedMethod());
try {
return this.getBridgedMethod().invoke(this.getBean(), args);
} catch (IllegalArgumentException var4) {
this.assertTargetBean(this.getBridgedMethod(), this.getBean(), args);
String text = var4.getMessage() != null ? var4.getMessage() : "Illegal argument";
throw new IllegalStateException(this.formatInvokeError(text, args), var4);
} catch (InvocationTargetException var5) {
Throwable targetException = var5.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException)targetException;
} else if (targetException instanceof Error) {
throw (Error)targetException;
} else if (targetException instanceof Exception) {
throw (Exception)targetException;
} else {
throw new IllegalStateException(this.formatInvokeError("Invocation failure", args), targetException);
}
}
}
}
InvocableHandlerMethod的WebDataBinderFactory工廠類可以對參數進行預處理,被用在了參數解析的過程中。parameterNameDiscoverer設置了默認值DefaultParameterNameDiscoverer,原理在上一節已經介紹了。HandlerMethodArgumentResolverComposite是HandlerMethodArgumentResolver的複合類,存儲所有獲取並設置到InvocableHandlerMethod的參數解析器。如果瞭解HandlerMapping的匹配條件的設計(原理(十)),會發現這兩者之間存在比較大的相識性。這個類是參數解析的重要組件,後面會詳細介紹。
invokeForRequest方法可以分解爲兩部分。
- 獲取參數:
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
- 調用接口方法執行請求:
return this.doInvoke(args);
getMethodArgumentValues會對HandlerMethod的每一個參數依次獲取對應的傳入參數值,內部的for循環所做的就是這樣一件事。首先使用parameterNameDiscoverer獲取參數名,然後如果傳入參數providedArgs已經提供了參數值,就直接獲取。在invokeAndHandle方法中可以看到,providedArgs這個參數實際沒有設置。那麼這樣就會調用HandlerMethodArgumentResolverComposite類的supportsParameter方法驗證是否支持解析此參數,接着使用resolveArgument方法解析並獲取請求中的參數值。所有參數值獲取之後,通過數組args返回。
參數值獲取之後,doInvoke方法就很簡單了,運用反射調用method的invoke方法,按順序輸入所有參數值就能夠獲得返回結果Object對象。
參數解析的分析
參數值解析最重要的部分就是HandlerMethodArgumentResolver類。
參數解析器的管理
HandlerMethodArgumentResolverComposite實現了HandlerMethodArgumentResolver接口,內部持有所有獲取的參數解析器,能夠根據傳入的參數類型檢索到對應的參數解析器進行解析。由於對於服務器來說,參數值解析非常頻繁,HandlerMethodArgumentResolverComposite並不是單純的遍歷檢索,也做了檢索的優化。
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList();
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap(256);
......
public boolean supportsParameter(MethodParameter parameter) {
return this.getArgumentResolver(parameter) != null;
}
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
} else {
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
if (result == null) {
Iterator var3 = this.argumentResolvers.iterator();
while(var3.hasNext()) {
HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
}
最初,所有註冊的HandlerMethodArgumentResolver在invokeHandlerMethod方法中設置到argumentResolvers裏。argumentResolverCache是ConcurrentHashMap類型。ConcurrentHashMap線程安全且高效的HashMap,一般用來處理高併發的情況。這裏argumentResolverCache用作一種緩存機制,提高
HandlerMethodArgumentResolver的檢索效率。
每當需要判斷是否支持某種類型的參數值解析,調用supportsParameter方法時,或者實際進行參數值解析時,都需要先獲取對應參數值解析的HandlerMethodArgumentResolver。getArgumentResolver
方法會現在argumentResolverCache使用參數類型MethodParameter作爲key查找對應參數值解析器,如果沒有,再去List中遍歷。獲取的對應解析器會存入緩存中,這樣,下次此類參數的解析會更加的快速。
獲取到對應參數的HandlerMethodArgumentResolver後,HandlerMethodArgumentResolverComposite的resolveArgument方法調用對應參數值解析器的resolveArgument來獲取最終的參數值。
參數解析器的分類
參數解析器的種類非常多,除了上文的“管理器”,剩下實現HandlerMethodArgumentResolver的類還有31個,我大致將這些分成五大類。
第一大類主要是解析request中的參數,也包括response中需要的參數。這一大類又可以分爲四小類。
-
request中參數值的常規解析
這一類在請求體request參數值解析中發揮了比較大的作用,主要成員是上圖所示的黑色方框中繼承AbstractNamedValueMethodArgumentResolver的子類。-
- AbstractCookieValueMethodArgumentResolver及其子類ServletCookieValueMethodArgumentResolver用作@CookieValue註解的Cookie值的解析。
-
- PathVariableMethodArgumentResolver用來解析@PathVariable參數值。注:在請求時可以使用{}來設置可變的url路徑參數,比如可以設置匹配的url爲/book/{bookname},而參數中使用@PathVariable String bookname就能解析出傳入的bookname的值。
-
- MatrixVariableMethodArgumentResolver處理@MatrixVariable註解的參數。注:可以在url中使用";“爲”{}"變量添加限定,比如Get /book/effective-java;author=JoshuaBloch,使用@MatrixVariable就能將author=JoshuaBloch這一限定讀取出來。
-
- RequestHeaderMethodArgumentResolver用來解析@RequestHeader註解的參數值,獲取header的信息。
-
- RequestParamMethodArgumentResolver平常使用較多,可以獲取@RequestParam註解需要的參數值。
-
-
request中參數的map解析
上述request中的參數值除了cookie之外,其他都可以以Map的形式獲取,以@RequestParam爲例:
@GetMapping("get")
public String test(@RequestParam Map<String,Object> paramsMap){
return paramsMap.get("s1").toString() + " " +paramsMap.get("s2").toString();
}
,這時不再需要匹配參數名,同時業務接口中也能同時獲得參數名和參數值。這種方法獲取參數值的解析器是上圖藍色方框中的子類,包括PathVariableMapMethodArgumentResolver、MatrixVariableMapMethodArgumentResolver、RequestHeaderMapMethodArgumentResolver、RequestParamMapMethodArgumentResolver這四個直接實現HandlerMethodArgumentResolver接口的子類。
-
請求體中參數的解析
第三小類是用來解析請求的請求體中的數據。上面的繼承圖譜顯示了AbstractMessageConverterMethodProcessor及其子類實現了HandlerMethodReturnValueHandler接口,這個組件是在後面設置返回值這一步發揮作用的。命名後綴爲Processor而不是Resolver意味着這些類不只處理請求,還用來處理返回結果。 -
- RequestPartMethodArgumentResolver用來獲取@RequestPart註解的參數值。@RequestPart可以獲取Multipart類型的數據,如果參數類型是Multipart,就從消息體中獲取返回MultipartFile;如果參數不是Multipart類型,可以進行自動轉換。
-
- HttpEntityMethodProcessor負責HttpEntity、RequestEntity參數的解析,HttpEntity及子類RequestEntity不僅可以獲取請求體中的內容,而且還可以獲取請求頭中的內容。除此之外,HttpEntityMethodProcessor實現了HandlerMethodReturnValueHandler的方法,還作爲返回ResponseEntity結果的處理器。在這種情況下,return參數再作爲視圖名,供視圖解析器使用,而是直接生成http的響應。
-
- RequestResponseBodyMethodProcessor可以用來獲取請求體中的內容,對應@RequestBody註解的參數值解析。這個功能目前使用比較多,一般會在POST的請求體中寫入json數據,這個數據能夠被RequestResponseBodyMethodProcessor解析,賦值給java POJO類。類似於HttpEntityMethodProcessor,RequestResponseBodyMethodProcessor也可以用來處理返回的數據,@ResponseBody(或@RestController)註解的功能實現就是由這個類提供的功能,可以將數據結構解析爲json後直接返回。@RestController註解是@Controller和@ResponseBody註解的組合。
-
Servlet請求相關
這個小類使用就比較少,一般情況下用不到這些解析器。 -
- ServletRequestMethodArgumentResolver用來解析WebRequest、ServletRequest、MultipartRequst、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod等類型參數值,都可以通過request獲取。
-
- ServletResponseMethodArgumentResolver用來解析ServletResponse、OutputStream、Writer類型的參數。
-
- RequestAttributeMethodArgumentResolver用來解析@RequestAttribute註解的參數值。@RequestAttribute註解的參數必須是@ModelAttributes註解添加到model中或者在Filter使用servletRequest.setAttribute添加的參數,如果不是,則會報錯。
第二大類是解析model中的參數值解析。 這幾個類中不只有model參數值的解析,也有返回結果model的設置,所以實現了HandlerMethodReturnValueHandler接口且後綴爲Processor。
-
MapMethodProcessor解析Map型參數的參數值,會直接返回mavContainer中的model。還用來處理Map類型的返回值。
-
ModelAttributeMethodProcessor解析註釋了@ModelAttribute的方法的參數值,同時處理該方法的返回值。
-
ServletModelAttributeMethodProcessor對2中的父類做了補丁,可以使用ServletRequestDataBinder代替父類中的WebDataBinder進行數據綁定。
第三大類包括了session、重定向和一些補充參數值的解析。
- ExpressionValueMethodArgumentResolver用來解析@Value註解的參數。這個註解在Controller方法參數的作用和在普通bean中的作用一樣,都是注入環境property值。
- SessionAttributeMethodArgumentResolver用來解析@SessionAttribute註解的參數。
- SessionStatusMethodArgumentResolver用來解析SessionStatus類型參數,以上兩個都是和session有關的解析器。
- RedirectAttributesMethodArgumentResolver用來RedirectAttributes類型的參數,RedirectAttributes可以設置參數用於重定向。
- UriComponentsBuilderMethodArgumentResolver用於解析UriComponentsBuilder類型的參數。
- 解析Errors類型的參數,一般是Errors或者BindingResult,可以從model中獲取。
第四大類算不上參數解析器,在官方文檔中稱之爲向後兼容的適配器,包括AbstractWebArgumentResolverAdapter和ServletWebArgumentResolverAdapter兩個類。ServletWebArgumentResolverAdapter繼承了AbstractWebArgumentResolverAdapter,是最終能被實例化的參數解析器的適配工具。除了向後兼容,ServletWebArgumentResolverAdapter還可以用來作爲自定義參數解析器的“註冊工具”,詳細內容可以參考這篇文章:SpringBoot配置參數解析器。
具體參數值解析器的原理可以期待更詳細的代碼分析,這裏內容已經溢出了,不做深入探討。
設置返回值
參數值解析後,會使用反射調用method的invoke,完成請求的處理。
return this.getBridgedMethod().invoke(this.getBean(), args);
返回值是Object,需要進行後續的處理。這樣結束了
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
的調用,整個處理流程又回到了ServletInvocableHandlerMethod類的invokeAndHandle方法中。ServletInvocableHandlerMethod繼承自InvocableHandlerMethod,ServletInvocableHandlerMethod主要做的是返回結果的設置。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
繼續invokeAndHandle方法。setResponseStatus(webRequest)根據@ResponseStatus註解設置Response狀態,後面的代碼根據返回值和應答來判斷是否直接返回,不需要returnValueHandler處理。需要處理的返回結果會最終由ServletInvocableHandlerMethod持有的HandlerMethodReturnValueHandlerComposite類對象returnValueHandlers的handleReturnValue方法。看到XXXXComposite,和上文內容相聯繫,應該明白這個類是一個返回值處理器HandlerMethodReturnValueHandler的複合類,用來管理不同的處理器。
//HandlerMethodReturnValueHandlerComposite.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
handleReturnValue方法先調用selectHandler方法選擇對應返回值的處理器,具體的實現方式是遍歷所有處理器,使用處理器的supportsReturnType方法來判斷。獲取處理器後,接着使用處理器的handleReturnValue,傳入返回值returnValue、返回值類型returnType、Model和View相關的mavContainer、複合請求和應答等信息的webRequest,處理返回值。
HandlerMethodReturnValueHandler接口和HandlerMethodArgumentResolver類似,定義的方法只有兩個。
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
這兩個方法上面已經介紹了,supportsReturnType判斷該處理器是否支持此返回值的處理,handleReturnValue用來最終處理返回值。
返回值處理器的分類
上面在介紹參數值解析器時,其實也包括了一些返回值的處理器。以XXXProcessor形式命名的解析器,既實現了HandlerMethodArgumentResolver的接口,也實現了HandlerMethodReturnValueHandler,這種類型的處理器上文已經介紹過了,下面就不再涉及。我對剩下的其他的處理器進行了整理。
- AsyncTaskMethodReturnValueHandler,異步請求處理,負責WebAsyncTask類型的返回值。
- HandlerMethodReturnValueHandlerComposite複合類,用來管理返回值處理器。
- CallableMethodReturnValueHandler,異步請求返回值處理,負責Callable類型的返回值。
- DeferredResultMethodReturnValueHandler,異步請求處理,負責DeferredResult類型。
- HttpHeadersReturnValueHandler,負責HttpHeaders類型的返回值。
- ModelAndViewMethodReturnValueHandler,負責ModelAndView類型的返回值,是傳統spring mvc模式使用比較多的處理器,現在流行的RESTful服務模式已經不再需要。
- ModelAndViewResolverMethodReturnValueHandler,處理所有返回值,一般放在最後作爲補充。
- ResponseBodyEmitterReturnValueHandler,負責ResponseBodyEmitter、子類SseEmitter和封裝在ResponseEntity中的同等類型,非常高級的應用,需要研究一下。
- StreamingResponseBodyReturnValueHandler支持StreamingResponseBody或者ResponseEntity返回值的處理,異步I/O請求返回流管理類型。
- ViewMethodReturnValueHandler,支持View類型返回值的處理。
- ViewNameMethodReturnValueHandler,用於處理void和String類型的返回值,主要就是將返回的String作爲viewname設置到mavContainer中。
目前業務中使用最多的是直接返回json數據,而不需要spring mvc處理視圖相關的工作,所以使用最多的其實是同爲參數值解析器的RequestResponseBodyMethodProcessor處理器。在這個處理器中,會使用Converter進行類型解析,生成json數據,最後使用Response流直接返回http應答。這意味着,spring mvc在這個流程之後的視圖解析相關組件,已經不能失去作用,被廢棄掉了。除了RequestResponseBodyMethodProcessor需要注意,還有上面提到的一些異步類型的處理器,對於高併發可能有幫助,值得學習。
以上是對HandleAdapter整個原理的簡要分析,構造和設置解析器、處理器和設置其他參數的部分省略了,但這對於HandleAdapter如何發揮作用的理解並沒有太大影響。