Spring Boot 定義接口的方法是否可以聲明為 private?
我們?cè)?Controller 中定義接口的時(shí)候,一般都是像下面這樣:
估計(jì)很少有人會(huì)把接口方法定義成 private 的吧?那我們不禁要問(wèn),如果非要定義成 private 的方法,那能運(yùn)行起來(lái)嗎?
帶著這個(gè)疑問(wèn),我們開(kāi)始今天的源碼解讀~
在我們使用 Spring Boot 的時(shí)候,經(jīng)常會(huì)看到 HandlerMethod 這個(gè)類(lèi)型,例如我們?cè)诙x攔截器的時(shí)候,如果攔截目標(biāo)是一個(gè)方法,則 preHandle 的第三個(gè)參數(shù)就是 HandlerMethod(以下案例選自松哥之前的視頻:手把手教你 Spring Boot 自定義注解):
我們?cè)陂喿x SpringMVC 源碼的時(shí)候,也會(huì)反復(fù)看到這個(gè) HandlerMethod,那么它到底是什么意思?今天我想和小伙伴們捋一捋這個(gè)問(wèn)題,把這個(gè)問(wèn)題搞清楚了,前面的問(wèn)題大家也就懂了。
1.概覽
可以看到,HandlerMethod 體系下的類(lèi)并不多:
HandlerMethod
封裝 Handler 和具體處理請(qǐng)求的 Method。
在 HandlerMethod 的基礎(chǔ)上增加了調(diào)用的功能。
在 InvocableHandlerMethod 的基礎(chǔ)上增了對(duì) @ResponseStatus 注解的支持、增加了對(duì)返回值的處理。
在 ServletInvocableHandlerMethod 的基礎(chǔ)上,增加了對(duì)異步結(jié)果的處理。
基本上就是這四個(gè),接下來(lái)松哥就來(lái)詳細(xì)說(shuō)一說(shuō)這四個(gè)組件。
2.HandlerMethod
2.1 bridgedMethod
在正式開(kāi)始介紹 HandlerMethod 之前,想先和大家聊聊 bridgedMethod,因?yàn)樵?HandlerMethod 中將會(huì)涉及到這個(gè)東西,而有的小伙伴可能還沒(méi)聽(tīng)說(shuō)過(guò) bridgedMethod,因此松哥在這里做一個(gè)簡(jiǎn)單介紹。
首先考考大家,下面這段代碼編譯會(huì)報(bào)錯(cuò)嗎?
首先我們定義了一個(gè) Animal 接口,里邊定義了一個(gè) eat 方法,同時(shí)聲明了一個(gè)泛型。Cat 實(shí)現(xiàn)了 Animal 接口,將泛型也定義為了 String。當(dāng)我調(diào)用的時(shí)候,聲明類(lèi)型是 Animal,實(shí)際類(lèi)型是 Cat,這個(gè)時(shí)候調(diào) eat 方法傳入了 Object 對(duì)象大家猜猜會(huì)怎么樣?如果調(diào)用 eat 方法時(shí)傳入的是 String 類(lèi)型那就肯定沒(méi)問(wèn)題,但如果不是 String 呢?
松哥先說(shuō)結(jié)論:編譯沒(méi)問(wèn)題,運(yùn)行報(bào)錯(cuò)。
如果小伙伴們?cè)谧约弘娔X上寫(xiě)出上面這段代碼,你會(huì)發(fā)現(xiàn)這樣一個(gè)問(wèn)題,開(kāi)發(fā)工具中提示的參數(shù)類(lèi)型竟然是 Object,以松哥的 IDEA 為例,如下:
大家看到,在我寫(xiě)代碼的時(shí)候,開(kāi)發(fā)工具會(huì)給我提示,這個(gè)參數(shù)類(lèi)型是 Object,有的小伙伴會(huì)覺(jué)得奇怪,明明是泛型,怎么變成 Object 了?
我們可以通過(guò)反射查看 Cat 類(lèi)中到底有哪些方法,代碼如下:
運(yùn)行結(jié)果如下:
可以看到,在實(shí)際運(yùn)行過(guò)程中,竟然有兩個(gè) eat 方法,一個(gè)的參數(shù)為 String 類(lèi)型,另一個(gè)參數(shù)為 Object 類(lèi)型,這是怎么回事呢?
這個(gè)參數(shù)類(lèi)型為 Object 的方法其實(shí)是 Java 虛擬機(jī)在運(yùn)行時(shí)創(chuàng)建出來(lái)的,這個(gè)方法就是我們所說(shuō)的 bridge method。本節(jié)的小標(biāo)題叫做 bridgedMethod,這是 HandlerMethod 源碼中的變量名,bridge 結(jié)尾多了一個(gè) d,含義變成了被 bridge 的方法,也就是參數(shù)為 String 的原方法,大家在接下來(lái)的源碼中看到了 bridgedMethod 就知道這表示參數(shù)類(lèi)型不變的原方法。
2.2 HandlerMethod 介紹
接下來(lái)我們來(lái)簡(jiǎn)單看下 HandlerMethod。
在我們前面分析 HandlerMapping 的時(shí)候(參見(jiàn):),里邊有涉及到 HandlerMethod,創(chuàng)建 HandlerMethod 的入口方法是 createWithResolvedBean,因此這里我們就從該方法開(kāi)始看起:
這個(gè)方法主要是確認(rèn)了一下 handler 的類(lèi)型,如果 handler 是 String 類(lèi)型,則根據(jù) beanName 從 Spring 容器中重新查找到 handler 對(duì)象,然后構(gòu)建 HandlerMethod:
這里的參數(shù)都比較簡(jiǎn)單,沒(méi)啥好說(shuō)的,唯一值得介紹的地方有兩個(gè):parameters 和 responseStatus。
parameters
parameters 實(shí)際上就是方法參數(shù),對(duì)應(yīng)的類(lèi)型是 MethodParameter,這個(gè)類(lèi)的源碼我這里就不貼出來(lái)了,主要和大家說(shuō)一下封裝的內(nèi)容包括:參數(shù)的序號(hào)(parameterIndex),參數(shù)嵌套級(jí)別(nestingLevel),參數(shù)類(lèi)型(parameterType),參數(shù)的注解(parameterAnnotations),參數(shù)名稱(chēng)查找器(parameterNameDiscoverer),參數(shù)名稱(chēng)(parameterName)等。
HandlerMethod 中還提供了兩個(gè)內(nèi)部類(lèi)來(lái)封裝 MethodParameter,分別是:
HandlerMethodParameter:這個(gè)封裝方法調(diào)用的參數(shù)。
ReturnValueMethodParameter:這個(gè)繼承自 HandlerMethodParameter,它封裝了方法的返回值,返回值里邊的 parameterIndex 是 -1。
注意,這兩者中的 method 都是 bridgedMethod。
responseStatus
這個(gè)主要是處理方法的 @ResponseStatus 注解,這個(gè)注解用來(lái)描述方法的響應(yīng)狀態(tài)碼,使用方式像下面這樣:
從這段代碼中大家可以看到,其實(shí) @ResponseStatus 注解靈活性很差,不實(shí)用,當(dāng)我們定義一個(gè)接口的時(shí)候,很難預(yù)知到該接口的響應(yīng)狀態(tài)碼是 200。
在 handlerMethod 中,在調(diào)用其構(gòu)造方法的時(shí)候,都會(huì)調(diào)用 evaluateResponseStatus 方法處理 @ResponseStatus 注解,如下:
可以看到,這段代碼也比較簡(jiǎn)單,找到注解,把里邊的值解析出來(lái),賦值給相應(yīng)的變量。
這下小伙伴們應(yīng)該明白了 HandlerMethod 大概是個(gè)怎么回事。
3.InvocableHandlerMethod
看名字就知道,InvocableHandlerMethod 可以調(diào)用 HandlerMethod 中的具體方法,也就是 bridgedMethod。我們先來(lái)看下 InvocableHandlerMethod 中聲明的屬性:
主要就是這三個(gè)屬性:
resolvers:這個(gè)不用說(shuō),參數(shù)解析器,前面的文章中松哥已經(jīng)和大家聊過(guò)這個(gè)問(wèn)題了。
parameterNameDiscoverer:這個(gè)用來(lái)獲取參數(shù)名稱(chēng),在 MethodParameter 中會(huì)用到。
dataBinderFactory:這個(gè)用來(lái)創(chuàng)建 WebDataBinder,在參數(shù)解析器中會(huì)用到。
具體的請(qǐng)求調(diào)用方法是 invokeForRequest,我們一起來(lái)看下:
首先調(diào)用 getMethodArgumentValues 方法按順序獲取到所有參數(shù)的值,這些參數(shù)值組成一個(gè)數(shù)組,然后調(diào)用 doInvoke 方法執(zhí)行,在 doInvoke 方法中,首先獲取到 bridgedMethod,并設(shè)置其可見(jiàn)(意味著我們?cè)?Controller 中定義的接口方法也可以是 private 的),然后直接通過(guò)反射調(diào)用即可。當(dāng)我們沒(méi)看 SpringMVC 源碼的時(shí)候,我們就知道接口方法最終肯定是通過(guò)反射調(diào)用的,現(xiàn)在,經(jīng)過(guò)層層分析之后,終于在這里找到了反射調(diào)用代碼。
最后松哥再來(lái)說(shuō)一下負(fù)責(zé)參數(shù)解析的 getMethodArgumentValues 方法:
- 首先調(diào)用 getMethodParameters 方法獲取到方法的所有參數(shù)。
- 創(chuàng)建 args 數(shù)組用來(lái)保存參數(shù)的值。
- 接下來(lái)一堆初始化配置。
- 如果 providedArgs 中提供了參數(shù)值,則直接賦值。
- 查看是否有參數(shù)解析器支持當(dāng)前參數(shù)類(lèi)型,如果沒(méi)有,直接拋出異常。
- 調(diào)用參數(shù)解析器對(duì)參數(shù)進(jìn)行解析,解析完成后,賦值。
是不是,很 easy!
4.ServletInvocableHandlerMethod
ServletInvocableHandlerMethod 則是在 InvocableHandlerMethod 的基礎(chǔ)上,又增加了兩個(gè)功能:
- 對(duì)@ResponseStatus 注解的處理
- 對(duì)返回值的處理
Servlet 容器下 Controller 在查找適配器時(shí)發(fā)起調(diào)用的最終就是 ServletInvocableHandlerMethod。
這里的處理核心方法是 invokeAndHandle,如下:
首先調(diào)用父類(lèi)的 invokeForRequest 方法對(duì)請(qǐng)求進(jìn)行執(zhí)行,拿到請(qǐng)求結(jié)果。
調(diào)用 setResponseStatus 方法處理@ResponseStatus? 注解,具體的處理邏輯是這樣:如果沒(méi)有添加 @ResponseStatus 注解,則什么都不做;如果添加了該注解,并且 reason 屬性不為空,則直接輸出錯(cuò)誤,否則設(shè)置響應(yīng)狀態(tài)碼。這里需要注意一點(diǎn),如果響應(yīng)狀態(tài)碼是 200,就不要設(shè)置 reason,否則會(huì)按照 error 處理。
接下來(lái)就是對(duì)返回值的處理了,returnValueHandlers#handleReturnValue 方法松哥在之前的文章中和大家專(zhuān)門(mén)介紹過(guò),這里就不再贅述,傳送門(mén):Spring Boot 中如何統(tǒng)一 API 接口響應(yīng)格式?。
事實(shí)上,ServletInvocableHandlerMethod 還有一個(gè)子類(lèi) ConcurrentResultHandlerMethod,這個(gè)支持異步調(diào)用結(jié)果處理,因?yàn)槭褂脠?chǎng)景較少,這里就不做介紹啦。