Spring MVC 源碼筆記

也不指望有人看... 不過如果你看了的話, 希望你能告訴我你的感受, 以及可以改進的地方!

Spring MVC 源碼筆記

關鍵類分析

WebMvcConfigurationSupport
    默認注冊了很多東西,如 HandlerMapping 幾個實現(xiàn), HandlerAdaptor 幾個實現(xiàn)

HandlerMapping
    添加容器內所有帶有 RequestMaping 的類的公開方法到 mappings 中存起來
        (AbstractHandlerMethodMapping#afterPropertiesSet中)
    根據(jù) request 的 uri 查找對應的 HandlerMethod, 步驟概述:
        把RequestMapping注解內的 path 作為 key 保持到一個 map1
        其他信息封裝成 mapping 作為 key 也保持到另一個 map2
        根據(jù) uri去 map1 獲取 mapping, 再根據(jù) mapping 獲取 HandlerMethod
        封裝成 Match 對象, 與其他匹配對象做比較后, 返回 HandlerMethod

HandlerAdapter
    初始化參數(shù)解析,返回值解析等
        (RequestMappingHandlerAdapter#afterPropertiesSet)
    根據(jù) handler 確定對應的 HandlerAdapter, 然后 HandlerAdapter 負責執(zhí)行這個 handler
    如 RequestMappingHandlerAdapter 則負責執(zhí)行 HandlerMethod
        簡單說就是封裝 HandlerMethod, 根據(jù)參數(shù)值設置參數(shù), 然后調用方法, 再處理返回值封裝成 ModelAndView
    另外,這里如果使用了@ResponseBody,會進入 RequestResponseBodyMethodProcessor
        然后使用 messageConverters(json)寫入到響應流
        最后 mv 也直接返回null, 不需要render了.

ViewResolver
    負責將 ModelAndView 解析成HTML, 如JSP, FreeMarker

HandlerExecutionChian
    管理攔截器和封裝Handler, 負責攔截器的實際調用邏輯實現(xiàn)
    
DispatcherServlet
    調度整個HTTP請求響應流程, 調用各個子組件負責執(zhí)行處理方法, 解析視圖, 處理異常等.

SSM項目 Spring 容器和 Spring MVC 容器的創(chuàng)建過程及關系

Spring 容器的創(chuàng)建

org.springframework.web.context.ContextLoader#initWebApplicationContext
1.先判斷此方法是否重復運行, 若重復則報異常.
2.記錄日志, 記錄開始時間用于計算啟動消耗時間
3.將容器對象存儲在本地而非 servletContext, 防止 ServletContext 關閉后無法訪問
4.根據(jù)配置的類名, 實例化一個容器并賦值給 this.context
5.標準代碼, 但其實就是 setParent(null), 前提是不擴展子類咯. 也就是說這個就是頂級容器了
6.根據(jù) web.xml 的配置對容器做相應的配置, 初始化, 將 ServletContext 存入到 environment 對象中; 執(zhí)行 InitializerClass, 調用容器 refresh 完成容器加載
7.打印容器初始化完成日志和記錄耗時

總結: 監(jiān)聽 ServletContext 創(chuàng)建完畢的事件, 然后創(chuàng)建一個 web 容器, 配置一些東西(id, parent, environment)并初始化(refresh)

MVC 子容器的創(chuàng)建

1.首先是 DispatchServlet 本身是一個 Servlet, 因此他有生命周期 init(), 其父類 init() 會將 ServletConfig 的配置(web.xml 中的 initParams)與 servlet 對象綁定, 這樣 DispatchServlet 對象的字段就有值了.
2.然后在 DispatchServlet 的父類 FrameworkServlet#initServletBean 中, 會創(chuàng)建一個容器并使其加載.(詳細見 FrameworkServlet#initWebApplicationContext() )

總結: 就是利用 Servlet 的生命周期只會執(zhí)行一次 init 的特性, 查找父容器, 創(chuàng)建子容器.

org.springframework.web.servlet.HttpServletBean#init
    // 1.把 servletConfig 的 initParameters 都加入 PropertySource, 根據(jù) requiredProperties 判斷所需配置項是否齊全
        // 2.將配置項綁定到 Servlet 對象(this) 上(子類的字段也算).
        // 3.調用子類初始化方法
  
org.springframework.web.servlet.FrameworkServlet#initServletBean
    // 0.父類的 init 執(zhí)行完, 此時 contextConfigLocation 已經有值了
        // 1.記錄日志和開始時間
        // 2.創(chuàng)建子容器, 與父容器綁定, 做一些配置, 調用 refresh() 完成加載.
        // 3.打印日志, 打印耗時

  
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
    // 1.從 ServletContext 中獲取 Spring 容器.(那么誰放進去的呢? 沒錯, 就是 ContextLoader,監(jiān)聽了 ServletContext 事件)
        // 2.若容器在構造方法處被注入(可能是注解開發(fā) Spring MVC 的方式會觸發(fā)), 先忽視  -- 來自 web.xml 形式啟動分析
        // 3.根據(jù)綁定得到的配置的 contextClass 創(chuàng)建一個子容器, 進行一些配置和初始化, 調用 refresh() 完成容器加載
        // 4.防止子容器不支持 refresh, 或子容器不是剛剛創(chuàng)建的, 因此手動觸發(fā) onRefresh(), 這個方法會加載一些默認的 bean(用處很大的那種)
        // 5.將容器設置到 ServletContext 中(根據(jù)配置, 默認允許)
        // 6.返回創(chuàng)建的子容器對象

  
org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext
    // 1.根據(jù)綁定得到的配置設置容器 id, 若無則生成默認的
        // 2.設置 ServletContext/ServletConfig 對象
        // 3.為子容器添加一個監(jiān)聽只監(jiān)聽子容器刷新事件的監(jiān)聽器, 用于容器加載完畢后調用 FrameworkServlet.onApplicationEvent()
        // 4.將 ServletConfig/ServletContext 加入到 environment 中.
        // 5.執(zhí)行 web.xml 中配置的 Initializers, 為子容器做的初始化
        // 6.調用子容器的 refresh(), 完成容器的加載

  
org.springframework.web.servlet.DispatcherServlet#initStrategies
    // 加載文件上傳處理 bean
        // 加載國際化處理 bean
        // 加載主題切換處理 bean
        // 加載 HandlerMapping bean
        // 加載 HandlerAdapter bean
        // 加載異常處理 bean
        // 加載 HttpServletRequest 轉視圖名稱處理策略 bean
        // 加載視圖解析器
        // 加載 FlashMap 管理 bean

        // 每一個加載的邏輯都是類似的, 先從容器中根據(jù) Xxx.class getBean
        // 若無, 則調用 getDefaultStrategy() 從 DispatcherServlet.properties 配置文件中讀取
        

DispatchServlet 的創(chuàng)建

1.Tomcat 會創(chuàng)建配置在 web.xml 的 Servlet
2.接著會觸發(fā) init 方法, init 調用了創(chuàng)建子容器的方法后, 還添加了容器加載完畢事件監(jiān)聽來回調 DispatcherServlet#onRefresh
3.DispatcherServlet#onRefresh 會 initStrategies() 加載很多策略接口 bean.

總結: 和子容器的創(chuàng)建息息相關.

Dispatch 過程

1.覆蓋 HttpServlet 的 service 方法, 調用 FrameworkServlet#processRequest()
2.此方法中進行一些上下文的準備工作, 以及處理日志, 異常(非 controller 異常), 然后調用 DispatcherServlet#doService()
3.doService() 中對 request 做一些準備工作, 然后調用 DispatcherServlet#doDispatch()
4.doDispatch() 先用 handlerMappings 查找合適的 handler(并加入攔截器鏈), 再通過 handlerAdapters 得到 handler 的適配器, 在合適的地方觸發(fā)攔截器; 然后調用適配器的 handle() 得到 ModelAndView
5.得到 ModelAndView 后, 先判斷是否捕獲到了異常, 是則調用 handlerExceptionResolvers 的 resolveException() 處理異常
6.接著, 調用 viewResolvers 的 resolveViewName, 將 viewName 解析成一個 View 對象
7.調用 View 對象的 render(), 將視圖通過 response 響應到前端.

總結: handlerMappings 找 handler 并包裝攔截器鏈, handlerAdapters 找可執(zhí)行的 HandlerAdapter, viewResolvers 解析視圖, 渲染視圖.

超長源碼注釋

// 入口 HttpServlet.service
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {

  HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
  if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    processRequest(request, response);
  }
  else {
    super.service(request, response);
  }
}

接著進入到 processRequest, 我只寫注釋了啊

org.springframework.web.servlet.FrameworkServlet#processRequest
    // 1.記錄啟動時間
        // 2.準備國際化處理上下文
        // 3.準備屬性管理上下文
        // 4.獲取并緩存一個 asyncManager(異步)
        // 5.初始化兩個 ContextHolder
        // 6.處理文件上傳, 根據(jù)不同策略查找可執(zhí)行的 controller 方法, 查到后加入攔截器, 再通過適配器來執(zhí)行 controller 方法, 然后把得到的 ModelAndView 解析成視圖對象, 并渲染到前端.
        // 7.重置兩個 ContextHolder
        // 8.打印日志, 發(fā)布事件

  
org.springframework.web.servlet.DispatcherServlet#doService
    // 1.打印日志, 記錄請求信息
        // 2.保存一份請求上下文的快照, 這樣嵌套的請求在回歸時可以恢復數(shù)據(jù)
        // 3.將容器中的一些 bean 配置到請求上下文中
        // 4.處理文件上傳, 根據(jù)不同策略查找可執(zhí)行的 controller 方法, 查到后加入攔截器, 再通過適配器來執(zhí)行 controller 方法, 然后把得到的 ModelAndView 解析成視圖對象, 并渲染到前端.
        // 5.若需要, 將快照恢復到請求上下文

  
org.springframework.web.servlet.DispatcherServlet#doDispatch
    // 1.準備一些變量
        // 2.判斷并處理文件上傳請求, 若是, processedRequest 則變?yōu)?MultipartHttpServletRequest 類型的對象.
        // 3.遍歷之前加載的 handlerMappings, 調用 getHandler 接口獲取執(zhí)行鏈對象并返回. 找不到則返回 null
        // 4.根據(jù) handler 獲取合適的適配器
        // 5.遍歷執(zhí)行攔截器的 preHandle(), 若遇到 renturn false, 則結束 doDispatch
        // 6.執(zhí)行實際的 controller 的方法得到 ModelAndView 對象.
        // 7.處理默認的 viewName
        // 8.遍歷執(zhí)行攔截器的 postHandle()
        // 9.若有異常則處理異常: 遍歷之前加載的異常處理器策略類, 調用 resolveException()
        //10.若無異常則根據(jù)需要根據(jù) ModelAndView的對象的視圖名配合之前加載的視圖解析器獲取 View 對象, 再調用 render() 渲染視圖.
        //11.最后執(zhí)行攔截器的 triggerAfterCompletion

  
org.springframework.web.servlet.DispatcherServlet#getHandler
    // 遍歷之前加載的 handlerMappings, 調用其 getHandler 接口獲取 handler 和攔截器封裝成 chain 對象并返回. 找不到則返回 null
  

org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
    // 遍歷之前加載的 handlerAdapters, 調用 supports 判斷是否支持 handler, 支持則返回 adapter
  
  
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
    // 從頭到尾遍歷攔截器, 執(zhí)行 preHandle(), 若攔截器返回 false, 則立刻 triggerAfterCompletion, 然后 return false
  
  
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
    // 若有異常則處理異常: 遍歷之前加載的異常處理器策略類, 調用 resolveException()
        // 若無異常則根據(jù)需要根據(jù)視圖名和視圖解析器渲染視圖
        // 最后執(zhí)行攔截器的 triggerAfterCompletion

  
org.springframework.web.servlet.DispatcherServlet#processHandlerException
    // 遍歷之前加載的異常處理器策略類, 調用 resolveException()
  
  
org.springframework.web.servlet.DispatcherServlet#render
    // 先遍歷視圖解析器, 根據(jù)視圖名獲取實際視圖對象
        // 最后調用實際視圖對象的 render (渲染方法)

  
org.springframework.web.servlet.DispatcherServlet#resolveViewName
    // 遍歷視圖解析器, 根據(jù)視圖名獲取實際視圖對象

某些實現(xiàn)原理

1.攔截器原理
2.默認的 HandlerMapping/HandlerAdapter 在何時加入?
3.參數(shù)是如何與 HTTP 請求 body 綁定的(序列化, 格式化, 綁定)?
4.參數(shù)校驗是如何進行的?
5.有關參數(shù)與返回值的一些攔截與干預(@RequestBody, @ResponseBody).

攔截器原理

何時加入?
    從 WebMvcConfigurationSupport 的子類中調用 addInterceptors 
    添加一些攔截器和攔截器的路徑配置 InterceptorRegistry 和 MappedInterceptor
    實現(xiàn)攔截器路徑匹配, 在 new HandlerExecutionChian 時判斷

何時執(zhí)行?
    DispatcherServlet 負責在正確的時機調用 HandlerExecutionChian 來調用 preHanlde 等方法.
    拿到 HandlerExecutionChian 后調用 preHanlde
    HandlerAdapter 執(zhí)行完 handler 后, 調用 postHandle
    解析視圖并渲染到response之后, 調用 afterCompletion
    如果中途出現(xiàn)異常, 或 preHandle 提前結束, 則也調用 afterCompletion

總結
    DispatcherServlet 去調用 HandlerExecutionChian 去調用 攔截器具體方法. 
    復雜點是添加一個攔截器到被加入到 HandlerExecutionChian 比較復雜一點, 以及帶路徑匹配的攔截器實現(xiàn)略復雜一些.

默認的 HandlerMapping/HandlerAdapter 在何時加入?

org.springframework.web.servlet.DispatcherServlet#initStrategies
protected void initStrategies(ApplicationContext context) {
  // 加載文件上傳處理 bean
  // 加載國際化處理 bean
  // 加載主題切換處理 bean
  // 加載 HandlerMapping bean
  // 加載 HandlerAdapter bean
  // 加載異常處理 bean
  // 加載 HttpServletRequest 轉視圖名稱處理策略 bean
  // 加載視圖解析器
  // 加載 FlashMap 管理 bean

  // 每一個 init 的邏輯都是類似的, 先從容器中根據(jù) Xxx.class getBean
  // 若無, 則調用 getDefaultStrategy() 從 DispatcherServlet.properties 配置文件中讀取
  initMultipartResolver(context);
  initLocaleResolver(context);
  initThemeResolver(context);
  initHandlerMappings(context);
  initHandlerAdapters(context);
  initHandlerExceptionResolvers(context);
  initRequestToViewNameTranslator(context);
  initViewResolvers(context);
  initFlashMapManager(context);
}

參數(shù)是如何與 HTTP 請求 body 綁定的(序列化, 格式化, 綁定)?

1.在適配器執(zhí)行 handler 的時候, 即 RequestMappingHandlerAdapter#invokeHandlerMethod
2.此方法會進行一些其他處理, 然后準備執(zhí)行方法前解析參數(shù), 即在 InvocableHandlerMethod#getMethodArgumentValues() 中
3.這個方法中會遍歷每一個參數(shù), 再遍歷配置的所有 resolvers, 通過 supportsParameter 接口判斷是否支持參數(shù)解析, 是則調用 resolveArgument 接口獲得實參
4.這其中, 最重要的是 resolvers, 其一般在 RequestMappingHandlerAdapter#getDefaultArgumentResolvers() 中添加默認和用戶自定義的 resoloves.
5.如 RequestResponseBodyMethodProcessor#resolveArgument() 用于處理帶 @RequestBody 注解的參數(shù).  

總結: 適配器執(zhí)行具體方法前, 先用反射獲取這個方法的 參數(shù)(形參)集合, 挨個遍歷從 resolvers 找支持解析的類來解析, 得到的返回值作為實參先存起來, 最后調用具體方法時就可以帶上實參們執(zhí)行就實現(xiàn)了將 HTTP 的數(shù)據(jù)綁定到 controller 的方法參數(shù)上的功能.

參考 RequestResponseBodyMethodProcessor#resolveArgument()

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  // 調用 readWithMessageConverters 讀取 body 的數(shù)據(jù), 再序列化 json 成相應的 Java Bean
  // 使用 binder 檢查 arg 的值是否與 @Valid 的那些注解相關的規(guī)則相符, 若有錯誤, 則拋異常.

  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);
}

參數(shù)校驗是如何進行的?

// 參考上面的代碼
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());
  }
}
// 這段代碼 validateIfApplicable() 就進行了參數(shù)校驗, 代碼如下:
// 邏輯是: 遍歷參數(shù)所有的注解, 包含 validatedAnn 或以 Valid 開頭的注解都進行校驗
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
  Annotation[] annotations = parameter.getParameterAnnotations();
  for (Annotation ann : annotations) {
    Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
    if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
      Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
      Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
      binder.validate(validationHints);
      break;
    }
  }
}

// 然后是 validate 具體的對象.
// 邏輯是: 遍歷所有的 validators, 挨個調用 validate 校驗
public void validate(Object... validationHints) {
  Object target = getTarget();
  Assert.state(target != null, "No target to validate");
  BindingResult bindingResult = getBindingResult();
  // Call each validator with the same binding result
  for (Validator validator : getValidators()) {
    if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
      ((SmartValidator) validator).validate(target, bindingResult, validationHints);
    }
    else if (validator != null) {
      validator.validate(target, bindingResult);
    }
  }
}
// 再往下就沒啦... Spring 沒有具體的實現(xiàn), 所以要導入 Hibernate 的啥啥啥包, 這是有原因的.

有關參數(shù)與返回值的一些攔截與干預(@RequestBody, @ResponseBody).

// 參考 RequestMappingHandlerAdapter#afterPropertiesSet()
public void afterPropertiesSet() {
  // Do this first, it may add ResponseBody advice beans
  initControllerAdviceCache();

  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);
  }
  if (this.returnValueHandlers == null) {
    List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
  }
}

由上可知, argumentResolvers/returnValueHandlers 都是此時初始化的, 再點進去看代碼發(fā)現(xiàn), 除了默認的, 還有用戶自定義的 customArgumentResolvers/customReturnValueHandlers;

// 尋找字段的引用發(fā)現(xiàn)
WebMvcConfigurationSupport#addReturnValueHandlers
WebMvcConfigurationSupport#addArgumentResolvers
// 這兩個方法可以繼承后加入加入自己的 ArgumentResolvers/ReturnValueHandlers, 也就是項目里面繼承 WebMvcConfiguration 的那個類, 重寫這兩個方法, 加入自己的類即可實現(xiàn)對參數(shù)/返回值的干預; 常用于統(tǒng)一加密/解密, 記錄日志等.

除此之外, 兩邊的默認值都有 RequestResponseBodyMethodProcessor, 這就是用于處理@RequestBody/@ResponseBody 的了, 往里面深入的看, 能看到其使用 messageConverters 讀取 body 數(shù)據(jù), 然后也會在對應的地方觸發(fā) advice 的方法.

// 參考 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters
for (HttpMessageConverter<?> converter : this.messageConverters) {
  Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
  GenericHttpMessageConverter<?> genericConverter =
    (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
  if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
      (targetClass != null && converter.canRead(targetClass, contentType))) {
    if (message.hasBody()) {
      HttpInputMessage msgToUse =
        getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
      body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
              ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
      body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
    }
    else {
      body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
    }
    break;
  }
}

參數(shù)的N種綁定方式

// 參考 RequestMappingHandlerAdapter#getDefaultArgumentResolvers
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
  List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

  // Annotation-based argument resolution
  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 ServletModelAttributeMethodProcessor(false));
  resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
  resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
  resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
  resolvers.add(new RequestHeaderMapMethodArgumentResolver());
  resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
  resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
  resolvers.add(new SessionAttributeMethodArgumentResolver());
  resolvers.add(new RequestAttributeMethodArgumentResolver());

  // Type-based argument resolution
  resolvers.add(new ServletRequestMethodArgumentResolver());
  resolvers.add(new ServletResponseMethodArgumentResolver());
  resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
  resolvers.add(new RedirectAttributesMethodArgumentResolver());
  resolvers.add(new ModelMethodProcessor());
  resolvers.add(new MapMethodProcessor());
  resolvers.add(new ErrorsMethodArgumentResolver());
  resolvers.add(new SessionStatusMethodArgumentResolver());
  resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

  // Custom arguments
  if (getCustomArgumentResolvers() != null) {
    resolvers.addAll(getCustomArgumentResolvers());
  }

  // Catch-all
  resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
  resolvers.add(new ServletModelAttributeMethodProcessor(true));

  return resolvers;
}

看幾個常見的

1.RequestParamMethodArgumentResolver: 負責解析帶 @RequestParam 注解的普通參數(shù)
2.RequestParamMapMethodArgumentResolver: 負責解析帶 @RequestParam 的 Map 參數(shù)....
3.PathVariableMethodArgumentResolver/PathVariableMapMethodArgumentResolver: 同上解析帶 @PathVariable 注解的參數(shù)
4.RequestResponseBodyMethodProcessor: 負責解析帶 @RequestBody 的參數(shù)
5.RequestHeaderMethodArgumentResolver: 負責解析帶 @RequestHeader 的參數(shù) (表示沒用過)
6.ServletRequestMethodArgumentResolver: 負責解析 HttpServletRequest 等類型的參數(shù)(即 req, 用的賊多)
7.ServletResponseMethodArgumentResolver: 負責解析 HttpServletResponse 等類型的參數(shù)(即 resp, 下載接口沒少用)
8.RequestParamMethodArgumentResolver: 啥都解析.... 即不帶任何注解的普通類型.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容