從原理層面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【一起學Spring MVC】
每篇一句
想當火影的人沒有近道可尋,當上火影的人同樣無路可退
前言
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
它只負責準備資料,封裝資料,而而不提供具體使用的方式方法~
看看它的繼承樹:
它主要有兩個子類:InvocableHandlerMethod
和ServletInvocableHandlerMethod
,從命名就知道他倆都是有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
和處理請求的Method
;InvocableHandlerMethod
增加了方法引數解析和呼叫方法的能力;ServletInvocableHandlerMethod
在此基礎上在增加了如下三個能力:
- 對
@ResponseStatus
註解的支援
1.當一個方法註釋了@ResponseStatus
後,響應碼就是註解上的響應碼。 並且,並且如果returnValue=null或者reason不為空(不為null且不為“”),將中斷處理直接返回(不再渲染頁面) - 對返回值
returnValue
的處理
1. 對返回值的處理是使用HandlerMethodReturnValueHandlerComposite
完成的 對非同步處理結果的處理
使用示例
文首說了,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
定製化處理的時候,學會使用它一樣是非常有必要的。
在最後還提示大家一個你可能沒有關心到的小細節:
HandlerMethod
位於org.springframework.web.method
包下,且是3.1後才有的MethodParameter
位於org.springframework.core
核心包中。2.0
就存在了
相關閱讀
【小家Spring】Spring MVC容器的web九大元件之---HandlerAdapter原始碼詳解---一篇文章帶你讀懂返回值處理器HandlerMethodReturnValueHandler
知識交流
==The last:如果覺得本文對你有幫助,不妨點個讚唄。當然分享到你的朋友圈讓更多小夥伴看到也是被作者本人許可的~
==
若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群
。
若群二維碼失效,請加wx號:fsx641385712
(或者掃描下方wx二維碼)。並且備註:"java入群"
字樣,會手動邀請入群
若文章
格式混亂
或者圖片裂開
,請點選`:原文連結-原文連結-原文連結