tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2

前言

    HandleAdapter作爲spring mvc中最重要的組件沒有之一,源碼中隱藏着非常多對於項目編碼有益的知識點。原理(十一)已經介紹了HandleAdapter的基本原理和具體實現類RequestMappingHandlerAdapter處理請求的主要流程。
    首先,上篇文章的要點開始於AbstractHandlerMethodAdapter的handle方法。在handle方法中調用了RequestMappingHandlerAdapter的handleInternal方法,而handleInternal方法中代碼邏輯中invokeHandlerMethod方法統領了後續處理請求的整個流程。本文主要就是invokeHandlerMethod方法進行分析,並且簡要介紹其中使用到的一些組件的原理。
    其次,上篇文章還介紹了貫穿整個處理流程的數據體ModelAndViewContainer,其中包含的參數可以參考下面這張圖。
tomcat + spring mvc原理(十一):spring mvc請求的適配處理和返回-brief.png

處理流程

@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引用代碼中已經做了詳細的註釋,下面這張圖再對主要流程進行歸納總結,方便大家參考。文章後續會對流程分步分析,以全面探求最終實現的原理細節。
tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-detailprocess.png
    根據參數的分類可知,參與這個流程的參數不僅包括請求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時使用了三個參數:

  1. 註釋了@ModelAtrribute的方法
  2. 上一步中獲取的WebDataBinderFactory
  3. 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;
  ······
}
  1. Executable是Constructor和Method的父類,executable用來存儲方法。
  2. 方法所在的類使用containingClass存儲。
  3. parameterIndex代表該參數在方法中的序號。parameterType存儲參數的類型,genericParameterType是Type類型的參數類型。參數名是parameterName。
  4. Parameter是複合的參數類,裏面存儲了序號、參數名、方法等,並不一定必須。
  5. 還有兩個類似List這種複合參數的指標。nestingLevel代表嵌套級別,List直接作爲參數的話,List嵌套級別爲1, Integer嵌套級別爲2。對於Integer,還有對應的序號,typeIndexesPerLevel就是存儲對應嵌套層數的參數的序數。
  6. 一般來說使用反射無法獲取參數名,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方法可以分解爲兩部分。

  1. 獲取參數:
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
  1. 調用接口方法執行請求:
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中參數值的常規解析
    tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-requestresolver.png
        這一類在請求體request參數值解析中發揮了比較大的作用,主要成員是上圖所示的黑色方框中繼承AbstractNamedValueMethodArgumentResolver的子類。

      1. AbstractCookieValueMethodArgumentResolver及其子類ServletCookieValueMethodArgumentResolver用作@CookieValue註解的Cookie值的解析。
      1. PathVariableMethodArgumentResolver用來解析@PathVariable參數值。注:在請求時可以使用{}來設置可變的url路徑參數,比如可以設置匹配的url爲/book/{bookname},而參數中使用@PathVariable String bookname就能解析出傳入的bookname的值。
      1. MatrixVariableMethodArgumentResolver處理@MatrixVariable註解的參數。注:可以在url中使用";“爲”{}"變量添加限定,比如Get /book/effective-java;author=JoshuaBloch,使用@MatrixVariable就能將author=JoshuaBloch這一限定讀取出來。
      1. RequestHeaderMethodArgumentResolver用來解析@RequestHeader註解的參數值,獲取header的信息。
      1. 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接口的子類。

  • 請求體中參數的解析
    tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-bodyresolver.png
        第三小類是用來解析請求的請求體中的數據。上面的繼承圖譜顯示了AbstractMessageConverterMethodProcessor及其子類實現了HandlerMethodReturnValueHandler接口,這個組件是在後面設置返回值這一步發揮作用的。命名後綴爲Processor而不是Resolver意味着這些類不只處理請求,還用來處理返回結果。

    1. RequestPartMethodArgumentResolver用來獲取@RequestPart註解的參數值。@RequestPart可以獲取Multipart類型的數據,如果參數類型是Multipart,就從消息體中獲取返回MultipartFile;如果參數不是Multipart類型,可以進行自動轉換。
    1. HttpEntityMethodProcessor負責HttpEntity、RequestEntity參數的解析,HttpEntity及子類RequestEntity不僅可以獲取請求體中的內容,而且還可以獲取請求頭中的內容。除此之外,HttpEntityMethodProcessor實現了HandlerMethodReturnValueHandler的方法,還作爲返回ResponseEntity結果的處理器。在這種情況下,return參數再作爲視圖名,供視圖解析器使用,而是直接生成http的響應。
    1. RequestResponseBodyMethodProcessor可以用來獲取請求體中的內容,對應@RequestBody註解的參數值解析。這個功能目前使用比較多,一般會在POST的請求體中寫入json數據,這個數據能夠被RequestResponseBodyMethodProcessor解析,賦值給java POJO類。類似於HttpEntityMethodProcessor,RequestResponseBodyMethodProcessor也可以用來處理返回的數據,@ResponseBody(或@RestController)註解的功能實現就是由這個類提供的功能,可以將數據結構解析爲json後直接返回。@RestController註解是@Controller和@ResponseBody註解的組合。
  • Servlet請求相關
    tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-servletresolver.png
        這個小類使用就比較少,一般情況下用不到這些解析器。

    1. ServletRequestMethodArgumentResolver用來解析WebRequest、ServletRequest、MultipartRequst、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod等類型參數值,都可以通過request獲取。
    1. ServletResponseMethodArgumentResolver用來解析ServletResponse、OutputStream、Writer類型的參數。
    1. RequestAttributeMethodArgumentResolver用來解析@RequestAttribute註解的參數值。@RequestAttribute註解的參數必須是@ModelAttributes註解添加到model中或者在Filter使用servletRequest.setAttribute添加的參數,如果不是,則會報錯。

    第二大類是解析model中的參數值解析。 這幾個類中不只有model參數值的解析,也有返回結果model的設置,所以實現了HandlerMethodReturnValueHandler接口且後綴爲Processor。
tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-modelresolver.png

  1. MapMethodProcessor解析Map型參數的參數值,會直接返回mavContainer中的model。還用來處理Map類型的返回值。

  2. ModelAttributeMethodProcessor解析註釋了@ModelAttribute的方法的參數值,同時處理該方法的返回值。

  3. ServletModelAttributeMethodProcessor對2中的父類做了補丁,可以使用ServletRequestDataBinder代替父類中的WebDataBinder進行數據綁定。

    第三大類包括了session、重定向和一些補充參數值的解析。
tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-mangle.png

  1. ExpressionValueMethodArgumentResolver用來解析@Value註解的參數。這個註解在Controller方法參數的作用和在普通bean中的作用一樣,都是注入環境property值。
  2. SessionAttributeMethodArgumentResolver用來解析@SessionAttribute註解的參數。
  3. SessionStatusMethodArgumentResolver用來解析SessionStatus類型參數,以上兩個都是和session有關的解析器。
  4. RedirectAttributesMethodArgumentResolver用來RedirectAttributes類型的參數,RedirectAttributes可以設置參數用於重定向。
  5. UriComponentsBuilderMethodArgumentResolver用於解析UriComponentsBuilder類型的參數。
  6. 解析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,這種類型的處理器上文已經介紹過了,下面就不再涉及。我對剩下的其他的處理器進行了整理。
tomcat + spring mvc原理(十二):spring mvc請求的適配處理和返回2-returnvaluehandler.png

  1. AsyncTaskMethodReturnValueHandler,異步請求處理,負責WebAsyncTask類型的返回值。
  2. HandlerMethodReturnValueHandlerComposite複合類,用來管理返回值處理器。
  3. CallableMethodReturnValueHandler,異步請求返回值處理,負責Callable類型的返回值。
  4. DeferredResultMethodReturnValueHandler,異步請求處理,負責DeferredResult類型。
  5. HttpHeadersReturnValueHandler,負責HttpHeaders類型的返回值。
  6. ModelAndViewMethodReturnValueHandler,負責ModelAndView類型的返回值,是傳統spring mvc模式使用比較多的處理器,現在流行的RESTful服務模式已經不再需要。
  7. ModelAndViewResolverMethodReturnValueHandler,處理所有返回值,一般放在最後作爲補充。
  8. ResponseBodyEmitterReturnValueHandler,負責ResponseBodyEmitter、子類SseEmitter和封裝在ResponseEntity中的同等類型,非常高級的應用,需要研究一下。
  9. StreamingResponseBodyReturnValueHandler支持StreamingResponseBody或者ResponseEntity返回值的處理,異步I/O請求返回流管理類型。
  10. ViewMethodReturnValueHandler,支持View類型返回值的處理。
  11. ViewNameMethodReturnValueHandler,用於處理void和String類型的返回值,主要就是將返回的String作爲viewname設置到mavContainer中。

    目前業務中使用最多的是直接返回json數據,而不需要spring mvc處理視圖相關的工作,所以使用最多的其實是同爲參數值解析器的RequestResponseBodyMethodProcessor處理器。在這個處理器中,會使用Converter進行類型解析,生成json數據,最後使用Response流直接返回http應答。這意味着,spring mvc在這個流程之後的視圖解析相關組件,已經不能失去作用,被廢棄掉了。除了RequestResponseBodyMethodProcessor需要注意,還有上面提到的一些異步類型的處理器,對於高併發可能有幫助,值得學習。

    以上是對HandleAdapter整個原理的簡要分析,構造和設置解析器、處理器和設置其他參數的部分省略了,但這對於HandleAdapter如何發揮作用的理解並沒有太大影響。

發佈了37 篇原創文章 · 獲贊 2 · 訪問量 6956
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章