從原理層面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【一起學Spring MVC】

  • 2019 年 10 月 3 日
  • 筆記

每篇一句

想當火影的人沒有近道可尋,當上火影的人同樣無路可退

前言

HandlerMethod它作為Spring MVC的非公開API,可能絕大多數小夥伴都對它比較陌生,但我相信你對它又不是那麼的生疏,因為你可能沒用過但肯定見過。
比如Spring MVC的攔截器HandlerInterceptor的攔截方法的第三個入參Object handler,雖然它是Object類型,但其實絕大部分情況下我們都會當作HandlerMethod來使用;又比如我之前的這篇講RequestMappingHandlerMapping的文章也大量的提到過HandlerMethod這個類。

經由我這麼「忽悠」,你是否覺得它還是相對比較重要的一個類了呢?不管你信不信,反正我是這麼認為的:HandlerMethod它是理解Spring MVC不可或缺的一個類,甚至可以說是你希望參與到Spring MVC的訂製化裡面來不可忽略的一個關鍵API。

HandlerMethod

HandlerMethod它不是一個介面,也不是個抽象類,且還是public的。HandlerMethod封裝了很多屬性,在訪問請求方法的時候可以方便的訪問到方法、方法參數、方法上的註解、所屬類等並且對方法參數封裝處理,也可以方便的訪問到方法參數的註解等資訊。

// @since 3.1  public class HandlerMethod {        // Object類型,既可以是個Bean,也可以是個BeanName      private final Object bean;      // 如果是BeanName,拿就靠它拿出Bean實例了~      @Nullable      private final BeanFactory beanFactory;      private final Class<?> beanType; // 該方法所屬的類      private final Method method; // 該方法本身      private final Method bridgedMethod; // 被橋接的方法,如果method是原生的,它的值同method      // 封裝方法參數的類實例,**一個MethodParameter就是一個入參**      // MethodParameter也是Spring抽象出來的一個非常重要的概念      private final MethodParameter[] parameters;      @Nullable      private HttpStatus responseStatus; // http狀態碼(畢竟它要負責處理和返回)      @Nullable      private String responseStatusReason; // 如果狀態碼里還要複數原因,就是這個欄位  可以為null          // 通過createWithResolvedBean()解析此handlerMethod實例的handlerMethod。      @Nullable      private HandlerMethod resolvedFromHandlerMethod;      // 標註在**介面入參**上的註解們(此處數據結構複雜,List+二維數組)      @Nullable      private volatile List<Annotation[][]> interfaceParameterAnnotations;        // 它的構造方法眾多  此處我只寫出關鍵的步驟      public HandlerMethod(Object bean, Method method) {          ...          this.beanType = ClassUtils.getUserClass(bean);          this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);          this.parameters = initMethodParameters();          ...          evaluateResponseStatus();      }      // 這個構造方法拋出了一個異常NoSuchMethodException      public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {          ...          this.method = bean.getClass().getMethod(methodName, parameterTypes);          this.parameters = initMethodParameters();          ...          evaluateResponseStatus();      }      // 此處傳的是BeanName      public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {          ...          // 這部判斷:這個BeanName是必須存在的          Class<?> beanType = beanFactory.getType(beanName);          if (beanType == null) {              throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");          }          this.parameters = initMethodParameters();          ...          evaluateResponseStatus();      }        // 供給子類copy使用的      protected HandlerMethod(HandlerMethod handlerMethod) { ... }        // 所有構造都執行了兩個方法:initMethodParameters和evaluateResponseStatus        // 初始化該方法所有的入參,此處使用的是內部類HandlerMethodParameter      // 注意:處理了泛型的~~~      private MethodParameter[] initMethodParameters() {          int count = this.bridgedMethod.getParameterCount();          MethodParameter[] result = new MethodParameter[count];          for (int i = 0; i < count; i++) {              HandlerMethodParameter parameter = new HandlerMethodParameter(i);              GenericTypeResolver.resolveParameterType(parameter, this.beanType);              result[i] = parameter;          }          return result;      }        // 看看方法上是否有標註了@ResponseStatus註解(介面上或者父類 組合註解上都行)      // 若方法上沒有,還會去所在的類上去看看有沒有標註此註解      // 主要只解析這個註解,把它的兩個屬性code和reason拿過來,最後就是返回它倆了~~~      // code狀態碼默認是HttpStatus.INTERNAL_SERVER_ERROR-->(500, "Internal Server Error")      private void evaluateResponseStatus() {          ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);          if (annotation == null) {              annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class);          }          if (annotation != null) {              this.responseStatus = annotation.code();              this.responseStatusReason = annotation.reason();          }      }      ... // 省略所有屬性的get方法(無set方法)        // 返回方法返回值的類型  此處也使用的MethodParameter      public MethodParameter getReturnType() {          return new HandlerMethodParameter(-1);      }      // 注意和上面的區別。舉個列子:比如方法返回的是Object,但實際return 「fsx」字元串      // 那麼上面返回永遠是Object.class,下面你實際的值是什麼類型就是什麼類型      public MethodParameter getReturnValueType(@Nullable Object returnValue) {          return new ReturnValueMethodParameter(returnValue);      }        // 該方法的返回值是否是void      public boolean isVoid() {          return Void.TYPE.equals(getReturnType().getParameterType());      }      // 返回標註在方法上的指定類型的註解   父方法也成      // 子類ServletInvocableHandlerMethod對下面兩個方法都有複寫~~~      @Nullable      public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {          return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);      }      public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {          return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);      }          // resolvedFromHandlerMethod雖然它只能被構造進來,但是它實際是銅鼓調用下面方法賦值      @Nullable      public HandlerMethod getResolvedFromHandlerMethod() {          return this.resolvedFromHandlerMethod;      }      // 根據string類型的BeanName把Bean拿出來,再new一個HandlerMethod出來~~~這才靠譜嘛      public HandlerMethod createWithResolvedBean() {          Object handler = this.bean;          if (this.bean instanceof String) {              Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");              String beanName = (String) this.bean;              handler = this.beanFactory.getBean(beanName);          }          return new HandlerMethod(this, handler);      }        public String getShortLogMessage() {          return getBeanType().getName() + "#" + this.method.getName() + "[" + this.method.getParameterCount() + " args]";      }          // 這個方法是提供給內部類HandlerMethodParameter來使用的~~ 它使用的數據結構還是蠻複雜的      private List<Annotation[][]> getInterfaceParameterAnnotations() {          List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;          if (parameterAnnotations == null) {              parameterAnnotations = new ArrayList<>();                // 遍歷該方法所在的類所有的實現的介面們(可以實現N個介面嘛)              for (Class<?> ifc : this.method.getDeclaringClass().getInterfaces()) {                    // getMethods:拿到所有的public的方法,包括父介面的  介面里的私有方法可不會獲取來                  for (Method candidate : ifc.getMethods()) {                      // 判斷這個介面方法是否正好是當前method複寫的這個~~~                      // 剛好是複寫的方法,那就添加進來,標記為介面上的註解們~~~                      if (isOverrideFor(candidate)) {                          // getParameterAnnotations返回的是個二維數組~~~~                          // 因為參數有多個,且每個參數前可以有多個註解                          parameterAnnotations.add(candidate.getParameterAnnotations());                      }                  }              }              this.interfaceParameterAnnotations = parameterAnnotations;          }          return parameterAnnotations;      }          // 看看內部類的關鍵步驟      protected class HandlerMethodParameter extends SynthesizingMethodParameter {          @Nullable          private volatile Annotation[] combinedAnnotations;          ...            // 父類只會在本方法拿,這裡支援到了介面級別~~~          @Override          public Annotation[] getParameterAnnotations() {              Annotation[] anns = this.combinedAnnotations;              if (anns == null) { // 都只需要解析一次                  anns = super.getParameterAnnotations();                  int index = getParameterIndex();                  if (index >= 0) { // 有入參才需要去分析嘛                      for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) {                          if (index < ifcAnns.length) {                              Annotation[] paramAnns = ifcAnns[index];                              if (paramAnns.length > 0) {                                  List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);                                  merged.addAll(Arrays.asList(anns));                                  for (Annotation paramAnn : paramAnns) {                                      boolean existingType = false;                                      for (Annotation ann : anns) {                                          if (ann.annotationType() == paramAnn.annotationType()) {                                              existingType = true;                                              break;                                          }                                      }                                      if (!existingType) {                                          merged.add(adaptAnnotation(paramAnn));                                      }                                  }                                  anns = merged.toArray(new Annotation[0]);                              }                          }                      }                  }                  this.combinedAnnotations = anns;              }              return anns;          }      }        // 返回值的真正類型~~~      private class ReturnValueMethodParameter extends HandlerMethodParameter {          @Nullable          private final Object returnValue;          public ReturnValueMethodParameter(@Nullable Object returnValue) {              super(-1); // 此處傳的-1哦~~~~ 比0小是很有意義的              this.returnValue = returnValue;          }          ...          // 返回值類型使用returnValue就行了~~~          @Override          public Class<?> getParameterType() {              return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());          }      }  }

可以看到HandlerMethod它持有的屬性是非常多的,提供的能力也是很強的。
但是不知道小夥伴有沒有發現,雖然它持有了目標的Method,但是它並沒有提供invoke執行它的能力,如果你要執行它還得自己把Method拿去自己執行。

所以總的來說它的職責還是很單一的:HandlerMethod它只負責準備數據,封裝數據,而而不提供具體使用的方式方法~

看看它的繼承樹:
在這裡插入圖片描述
它主要有兩個子類:InvocableHandlerMethodServletInvocableHandlerMethod,從命名就知道他倆都是有invoke調用能力的~

InvocableHandlerMethod

它是對HandlerMethod的擴展,增加了調用能力。這個能力在Spring MVC可是非常非常重要的,它能夠在調用的時候,把方法入參的參數都封裝進來(從HTTP request里,當然藉助的必然是HandlerMethodArgumentResolver

// @since 3.1  public class InvocableHandlerMethod extends HandlerMethod {      private static final Object[] EMPTY_ARGS = new Object[0];        // 它額外提供的幾個屬性,可以看到和數據綁定、數據校驗就扯上關係了~~~        // 用於產生數據綁定器、校驗器      @Nullable      private WebDataBinderFactory dataBinderFactory;      // HandlerMethodArgumentResolver用於入參的解析      private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();      // 用於獲取形參名      private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();        ... // 省略構造函數 全部使用super的      // 它自己的三大屬性都使用set方法設置進來~~~並且沒有提供get方法      // 也就是說:它自己內部使用就行了~~~        // 在給定請求的上下文中解析方法的參數值後調用該方法。 也就是說:方法入參里就能夠自動使用請求域(包括path里的,requestParam里的、以及常規對象如HttpSession這種)      // 解釋下providedArgs作用:調用者可以傳進來,然後直接doInvoke()的時候原封不動的使用它      //(彌補了請求域沒有所有對象的不足,畢竟有些對象是用戶自定義的嘛~)      @Nullable      public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {          // 雖然它是最重要的方法,但是此處不講,因為核心原來還是`HandlerMethodArgumentResolver`          // 它只是把解析好的放到對應位置里去~~~          // 說明:這裡傳入了ParameterNameDiscoverer,它是能夠獲取到形參名的。          // 這就是為何註解里我們不寫value值,通過形參名字來匹配也是ok的核心原因~          Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);          if (logger.isTraceEnabled()) { // trace資訊,否則日誌也特多了~              logger.trace("Arguments: " + Arrays.toString(args));          }          return doInvoke(args);      }        // doInvoke()方法就不說了,就是個普通的方法調用      // ReflectionUtils.makeAccessible(getBridgedMethod());      // return getBridgedMethod().invoke(getBean(), args);  }

對於最後的invoke(),說明一點:這裡可是執行的目標方法getBean()哦~~~

這個子類主要提供的能力就是提供了invoke調用目標Bean目標方法的能力,在這個調用過程中可大有文章可為,當然最為核心的邏輯可是各種各樣的HandlerMethodArgumentResolver來完成的,詳見下文有分曉。
InvocableHandlerMethod這個子類雖然它提供了調用了能力,但是它卻依舊還沒有和Servlet的API綁定起來,畢竟使用的是Spring自己通用的的NativeWebRequest,so很容易想到它還有一個子類就是干這事的~

ServletInvocableHandlerMethod

它是對InvocableHandlerMethod的擴展,它增加了返回值和響應狀態碼的處理,另外在ServletInvocableHandlerMethod有個內部類ConcurrentResultHandlerMethod繼承於它,支援異常調用結果處理,Servlet容器下Controller在查找適配器時發起調用的最終就是ServletInvocableHandlerMethod

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {      private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");        // 處理方法返回值      @Nullable      private HandlerMethodReturnValueHandlerComposite returnValueHandlers;        // 構造函數略        // 設置處理返回值的HandlerMethodReturnValueHandler      public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {          this.returnValueHandlers = returnValueHandlers;      }          // 它不是複寫,但是是對invokeForRequest方法的進一步增強  因為調用目標方法還是靠invokeForRequest      // 本處是把方法的返回值拿來進一步處理~~~比如狀態碼之類的      public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {          Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);          // 設置HttpServletResponse返回狀態碼 這裡面還是有點意思的  因為@ResponseStatus#code()在父類已經解析了  但是子類才用          setResponseStatus(webRequest);              // 重點是這一句話:mavContainer.setRequestHandled(true); 表示該請求已經被處理過了          if (returnValue == null) {                // Request的NotModified為true 有@ResponseStatus註解標註 RequestHandled=true 三個條件有一個成立,則設置請求處理完成並返回              if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {                  mavContainer.setRequestHandled(true);                  return;              }          // 返回值不為null,@ResponseStatus存在reason 同樣設置請求處理完成並返回          } else if (StringUtils.hasText(getResponseStatusReason())) {              mavContainer.setRequestHandled(true);              return;          }            // 前邊都不成立,則設置RequestHandled=false即請求未完成          // 繼續交給HandlerMethodReturnValueHandlerComposite處理          // 可見@ResponseStatus的優先順序還是蠻高的~~~~~          mavContainer.setRequestHandled(false);          Assert.state(this.returnValueHandlers != null, "No return value handlers");          try {                // 關於對方法返回值的處理,參見:https://blog.csdn.net/f641385712/article/details/90370542              this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);          } catch (Exception ex) {              if (logger.isTraceEnabled()) {                  logger.trace(formatErrorForReturnValue(returnValue), ex);              }              throw ex;          }      }        // 設置返回的狀態碼到HttpServletResponse 裡面去      private void setResponseStatus(ServletWebRequest webRequest) throws IOException {          HttpStatus status = getResponseStatus();          if (status == null) { // 如果調用者沒有標註ResponseStatus.code()此註解  此處就忽略它              return;          }            HttpServletResponse response = webRequest.getResponse();          if (response != null) {              String reason = getResponseStatusReason();                // 此處務必注意:若有reason,那就是sendError  哪怕你是200哦~              if (StringUtils.hasText(reason)) {                  response.sendError(status.value(), reason);              } else {                  response.setStatus(status.value());              }          }            // 設置到request的屬性,把響應碼給過去。為了在redirect中使用          // To be picked up by RedirectView          webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);      }        private boolean isRequestNotModified(ServletWebRequest webRequest) {          return webRequest.isNotModified();      }          // 這個方法RequestMappingHandlerAdapter里有調用      ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {          return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));      }        // 內部類們      private class ConcurrentResultMethodParameter extends HandlerMethodParameter {          @Nullable          private final Object returnValue;          private final ResolvableType returnType;          public ConcurrentResultMethodParameter(Object returnValue) {              super(-1);              this.returnValue = returnValue;              // 主要是這個解析 兼容到了泛型類型 比如你的返回值是List<Person> 它也能把你的類型拿出來              this.returnType = (returnValue instanceof ReactiveTypeHandler.CollectedValuesList ?                      ((ReactiveTypeHandler.CollectedValuesList) returnValue).getReturnType() :                      ResolvableType.forType(super.getGenericParameterType()).getGeneric());          }            // 若返回的是List  這裡就是List的類型哦  下面才是返回泛型類型          @Override          public Class<?> getParameterType() {              if (this.returnValue != null) {                  return this.returnValue.getClass();              }              if (!ResolvableType.NONE.equals(this.returnType)) {                  return this.returnType.toClass();              }              return super.getParameterType();          }            // 返回泛型類型          @Override          public Type getGenericParameterType() {              return this.returnType.getType();          }              // 即使實際返回類型為ResponseEntity<Flux<T>>,也要確保對@ResponseBody-style處理從reactive 類型中收集值          // 是對reactive 的一種兼容          @Override          public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {              // Ensure @ResponseBody-style handling for values collected from a reactive type              // even if actual return type is ResponseEntity<Flux<T>>              return (super.hasMethodAnnotation(annotationType) ||                      (annotationType == ResponseBody.class && this.returnValue instanceof ReactiveTypeHandler.CollectedValuesList));          }      }          // 這個非常有意思   內部類繼承了自己(外部類) 進行增強      private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {          // 返回值          private final MethodParameter returnType;            // 此構造最終傳入的handler是個Callable          // result方法返回值 它支援支援異常調用結果處理          public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {              super((Callable<Object>) () -> {                  if (result instanceof Exception) {                      throw (Exception) result;                  } else if (result instanceof Throwable) {                      throw new NestedServletException("Async processing failed", (Throwable) result);                  }                  return result;              }, CALLABLE_METHOD);                  // 給外部類把值設置上  因為wrapConcurrentResult一般都先調用,是對本類的一個增強              if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {                  setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);              }              this.returnType = returnType;          }          ...      }  }

HandlerMethod用於封裝Handler和處理請求的MethodInvocableHandlerMethod增加了方法參數解析和調用方法的能力;ServletInvocableHandlerMethod在此基礎上在增加了如下三個能力:

  1. @ResponseStatus註解的支援
    1.當一個方法注釋了@ResponseStatus後,響應碼就是註解上的響應碼並且,並且如果returnValue=null或者reason不為空(不為null且不為「」),將中斷處理直接返回(不再渲染頁面)
  2. 對返回值returnValue的處理
    1. 對返回值的處理是使用HandlerMethodReturnValueHandlerComposite完成的
  3. 非同步處理結果的處理

    使用示例

    文首說了,HandlerMethod作為一個非公開API,如果你要直接使用起來,還是稍微要費點勁的。
    但本文還是給出一個Demo,給出小夥伴們最為關心也是對你們最有用的一個需求:ModelFactory.getNameForParameter(parameter)這個靜態方法是給入參生成默認名稱的,當然默認處理方案最底層依賴的是它Conventions.getVariableNameForParameter(parameter),為了驗證這塊對象、Object、List等等常用數據結構的默認處理,此處我藉助HandlerMethod一次性全部列印出這個結論:

@Getter  @Setter  @ToString  public class Person {        @NotNull      private String name;      @NotNull      @Positive      private Integer age;        public Object demoMethod(Person person, Object object,                               List<Integer> intList, List<Person> personList,                               Set<Integer> intSet, Set<Person> personSet,                               Map<String, Object> myMap,                               String name, Integer age,                               int number, double money) {          return "hello parameter";      }  }

藉助HandlerMethod完成此測試用例

    public static void main(String[] args) {          // 準備一個HandlerMethod          HandlerMethod handlerMethod = new HandlerMethod(new Person(), getPersonSpecfyMethod());          // 拿到該方法所有的參數          MethodParameter[] methodParameters = handlerMethod.getMethodParameters();          for (MethodParameter parameter : methodParameters) {              Class<?> parameterType = parameter.getParameterType();              String nameForParameter = ModelFactory.getNameForParameter(parameter);              System.out.println("類型" + parameterType.getName() + "--->預設的modelKey是:" + nameForParameter);          }      }        private static Method getPersonSpecfyMethod() {          for (Method method : Person.class.getMethods())              if (method.getName().equals("demoMethod"))                  return method;          return null;      }

運行,列印結果如下:

類型com.fsx.bean.Person--->預設的modelKey是:person  類型java.lang.Object--->預設的modelKey是:object  類型java.util.List--->預設的modelKey是:integerList  類型java.util.List--->預設的modelKey是:personList  類型java.util.Set--->預設的modelKey是:integerList // 可以看到即使是set 名稱也是同List的  類型java.util.Set--->預設的modelKey是:personList  類型java.util.Map--->預設的modelKey是:map  類型java.lang.String--->預設的modelKey是:string  類型java.lang.Integer--->預設的modelKey是:integer  類型int--->預設的modelKey是:int  類型double--->預設的modelKey是:double

這個結果是不同類型對應的預設的ModelKey,希望小夥伴們能夠記下來,這對理解和正確使用@SessionAttribute、@ModelAttribute都是很重要的~

總結

HandlerMethod雖然接觸少,但並不影響它的重要性。在理解Spring MVC的處理流程上它很重要,在與使用者關係較大的攔截器HandlerInterceptor訂製化處理的時候,學會使用它一樣是非常有必要的。

在最後還提示大家一個你可能沒有關心到的小細節:

  1. HandlerMethod位於org.springframework.web.method包下,且是3.1後才有的
  2. MethodParameter位於org.springframework.core核心包中。2.0就存在了

相關閱讀

【小家Spring】Spring MVC容器的web九大組件之—HandlerAdapter源碼詳解—一篇文章帶你讀懂返回值處理器HandlerMethodReturnValueHandler

知識交流

==The last:如果覺得本文對你有幫助,不妨點個讚唄。當然分享到你的朋友圈讓更多小夥伴看到也是被作者本人許可的~==

若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。並且備註:"java入群" 字樣,會手動邀請入群

若文章格式混亂或者圖片裂開,請點擊`:原文鏈接-原文鏈接-原文鏈接