HandlerMethod及子類主要用於封裝方法調用相關信息,子類還提供調用,參數准備和返回值處理的職責.
分析下各個類的職責吧(順便做分析目錄):
HandlerMethod 封裝方法定義相關的信息,如類,方法,參數等.
使用場景:HandlerMapping時會使用
InvocableHandlerMethod 添加參數准備,方法調用功能
使用場景:執行使用@ModelAttribute注解會使用
ServletInvocableHandlerMethod 添加返回值處理職責,ResponseStatus處理
使用場景:執行http相關方法會使用,比如調用處理執行
1. HandlerMethod
HandlerMethod其實可以簡單理解為保持方法信息的pojo.
所以這邊主要就是看下定義的屬性:
1 package org.springframework.web.method; 2 public class HandlerMethod { 3 /** 什麼鬼,給子類提供logger,到現在為止源碼中不多見 */ 4 protected final Log logger = LogFactory.getLog(HandlerMethod.class); 5 // 方法所在的類,如果是String類型,可以去容器中獲取 6 private final Object bean; 7 // 方法 8 private final Method method; 9 // 類管理的容器 10 private final BeanFactory beanFactory; 11 // 方法的參數 12 private final MethodParameter[] parameters; 13 // 如果方法是bridged方法,則對應原始方法 14 private final Method bridgedMethod; 15 // ... 16 }
大部分應該是看看注釋就能理解了,我們解釋下下面:
這邊所有的熟悉都是final類型的,不可修改,所以如果出現修改需要new.
如果bean是string,是在createWithResolvedBean找容器獲取實例的.
MethodParameter類封裝了參數相關的信息.
提供獲取返回值,判斷是否void類型,還有讀取注解
createWithResolvedBean邏輯其實很簡單:
確認下如果bean是String類型的,那麼從容器BeanFactory中獲取,並使用private HandlerMethod(HandlerMethod handlerMethod, Object handler) new一個新的.
1 // HandlerMethod 2 public HandlerMethod createWithResolvedBean() { 3 Object handler = this.bean; 4 if (this.bean instanceof String) { 5 String beanName = (String) this.bean; 6 handler = this.beanFactory.getBean(beanName); 7 } 8 return new HandlerMethod(this, handler); 9 }
MethodParameter,可以根據method方法和parameterIndex(參數下標)唯一確定,其他屬性都可以根據他們兩獲取.
由於反射中沒有參數名的信息,而這邊需要,所以Spring添加了一個參數名查找器ParameterNameDiscover.
這邊返回值的類型是存儲在parameters屬性中的,下標用-1區分.
MethodParameter在HandlerMethod有兩個內部類的子類.
1 package org.springframework.core; 2 public class MethodParameter { 3 // 參數所在方法 4 private final Method method; 5 // 參數的構造方法 6 private final Constructor constructor; 7 // 參數下標 8 private final int parameterIndex; 9 // 參數類型 10 private Class<?> parameterType; 11 // Type類型的參數類型 12 private Type genericParameterType; 13 // 參數使用的注解 14 private Annotation[] parameterAnnotations; 15 // 參數名查找器 16 private ParameterNameDiscoverer parameterNameDiscoverer; 17 // 參數名 18 private String parameterName; 19 // 參數嵌套級別,如Map<String>中map為1,string為2 20 private int nestingLevel = 1; 21 // 每層的下標 22 /** Map from Integer level to Integer type index */ 23 Map<Integer, Integer> typeIndexesPerLevel; 24 // 什麼鬼?就一個構造方法用了,其他都沒有使用 25 Map<TypeVariable, Type> typeVariableMap; 26 // ... 27 }
2. InvocableHandlerMethod
習慣性的,看到Invocable,會想到Spring會不會定義一個接口來定義可調用概念,不過沒有.
這邊添加了2個職責:參數准備和方法執行.
參數准備委托HandlerMethodArgumentResolver進行具體的解析.解析的時候需要用到WebDataBinder,所以順便帶上.對參數解析器有興趣可以移步這裡
2.1 先來看看參數准備工作部分吧.
查找某個參數值的邏輯:
a, 先委托參數名查找器獲取參數名
b,從外部提供的參數清單中查找值(竟然是根據類型判斷的)
c,如果沒有直接提供,使用參數解析器創建
d,如果還是沒有獲得,直接報錯
1 package org.springframework.web.method.support; 2 3 public class InvocableHandlerMethod extends HandlerMethod { 4 // 參數解析器 5 private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite(); 6 // 參數解析器需要用到 7 private WebDataBinderFactory dataBinderFactory; 8 // 參數名查找器 9 private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); 10 11 private Object[] getMethodArgumentValues( 12 NativeWebRequest request, ModelAndViewContainer mavContainer, 13 Object... providedArgs) throws Exception { 14 15 MethodParameter[] parameters = getMethodParameters(); 16 Object[] args = new Object[parameters.length]; 17 for (int i = 0; i < parameters.length; i++) { 18 MethodParameter parameter = parameters[i]; 19 parameter.initParameterNameDiscovery(parameterNameDiscoverer); 20 // 查找參數名 21 GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); 22 // 從提供的參數值providedArgs中找值 23 args[i] = resolveProvidedArgument(parameter, providedArgs); 24 if (args[i] != null) { 25 continue; 26 } 27 // 使用參數解析器解析 28 if (argumentResolvers.supportsParameter(parameter)) { 29 try { 30 args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); 31 continue; 32 } catch (Exception ex) { 33 throw ex; 34 } 35 } 36 // 參數獲取不到是需要報錯的 37 if (args[i] == null) { 38 String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); 39 throw new IllegalStateException(msg); 40 } 41 } 42 return args; 43 } 44 45 private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) { 46 if (providedArgs == null) { 47 return null; 48 } 49 for (Object providedArg : providedArgs) { 50 // 竟然是根據類型判斷的 51 if (parameter.getParameterType().isInstance(providedArg)) { 52 return providedArg; 53 } 54 } 55 return null; 56 } 57 58 // ... 59 60 }
2.2 方法執行
這邊的邏輯其實很簡單:
委托獲取方法執行需要的參數
強制將方法變為可用
處理方法執行過程中的異常
1 package org.springframework.web.method.support; 2 3 public class InvocableHandlerMethod extends HandlerMethod { 4 // ... 5 public final Object invokeForRequest(NativeWebRequest request, 6 ModelAndViewContainer mavContainer, 7 Object... providedArgs) throws Exception { 8 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); 9 Object returnValue = invoke(args); 10 return returnValue; 11 } 12 13 private Object invoke(Object... args) throws Exception { 14 ReflectionUtils.makeAccessible(this.getBridgedMethod()); 15 try { 16 return getBridgedMethod().invoke(getBean(), args); 17 } 18 catch (IllegalArgumentException e) { 19 String msg = getInvocationErrorMessage(e.getMessage(), args); 20 throw new IllegalArgumentException(msg, e); 21 } 22 catch (InvocationTargetException e) { 23 // Unwrap for HandlerExceptionResolvers ... 24 Throwable targetException = e.getTargetException(); 25 if (targetException instanceof RuntimeException) { 26 throw (RuntimeException) targetException; 27 } 28 else if (targetException instanceof Error) { 29 throw (Error) targetException; 30 } 31 else if (targetException instanceof Exception) { 32 throw (Exception) targetException; 33 } 34 else { 35 String msg = getInvocationErrorMessage("Failed to invoke controller method", args); 36 throw new IllegalStateException(msg, targetException); 37 } 38 } 39 } 40 41 }
3. ServletInvocableHandlerMethod
委托HandlerMethodReturnValueHandler添加返回值處理功能
添加@ResponseStatus注解支持.
這邊使用的@ResponseStatus注解兩個屬性:value狀態碼,reason 寫入response的說明文字
3.1 設置response status時的邏輯:
responseStatus沒設置就返回
responseReason存在則進入error
把responseStatus設置到request,RedirectView需要使用
1 // ServletInvocableHandlerMethod 2 private void setResponseStatus(ServletWebRequest webRequest) throws IOException { 3 if (this.responseStatus == null) { 4 return; 5 } 6 7 if (StringUtils.hasText(this.responseReason)) { 8 webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason); 9 } 10 else { 11 webRequest.getResponse().setStatus(this.responseStatus.value()); 12 } 13 14 // to be picked up by the RedirectView 15 webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus); 16 }
3.2 invokeAndHandle
委托父類執行請求
添加ResponseStatus支持
然後判斷怎麼樣才是執行完畢,滿足一下任意一個:
request的notModified為真,使用@ResponseStatus注解,mavContainer的requestHandled為真
委托HandlerMethodReturnValueHandler封裝返回值
1 // ServletInvocableHandlerMethod 2 public final void invokeAndHandle(ServletWebRequest webRequest, 3 ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { 4 5 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 6 7 setResponseStatus(webRequest); 8 9 if (returnValue == null) { 10 if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { 11 mavContainer.setRequestHandled(true); 12 return; 13 } 14 } 15 else if (StringUtils.hasText(this.responseReason)) { 16 mavContainer.setRequestHandled(true); 17 return; 18 } 19 20 mavContainer.setRequestHandled(false); 21 22 try { 23 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); 24 } 25 catch (Exception ex) { 26 if (logger.isTraceEnabled()) { 27 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); 28 } 29 throw ex; 30 } 31 }