您好,登錄后才能下訂單哦!
Spring Boot中怎么定義接口,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
我們?cè)?Controller 中定義接口的時(shí)候,一般都是像下面這樣:
@GetMapping("/01") public String hello(Map<String,Object> map) { map.put("name", "javaboy"); return "forward:/index"; }
估計(jì)很少有人會(huì)把接口方法定義成 private 的吧?那我們不禁要問,如果非要定義成 private 的方法,那能運(yùn)行起來嗎?
帶著這個(gè)疑問,我們開始今天的源碼解讀~
在我們使用 Spring Boot 的時(shí)候,經(jīng)常會(huì)看到 HandlerMethod 這個(gè)類型,例如我們?cè)诙x攔截器的時(shí)候,如果攔截目標(biāo)是一個(gè)方法,則 preHandle 的第三個(gè)參數(shù)就是 HandlerMethod(以下案例選自松哥之前的視頻:手把手教你 Spring Boot 自定義注解):
@Component public class IdempotentInterceptor implements HandlerInterceptor { @Autowired TokenService tokenService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } //省略... return true; } //... }
我們?cè)陂喿x SpringMVC 源碼的時(shí)候,也會(huì)反復(fù)看到這個(gè) HandlerMethod,那么它到底是什么意思?今天我想和小伙伴們捋一捋這個(gè)問題,把這個(gè)問題搞清楚了,前面的問題大家也就懂了。
可以看到,HandlerMethod 體系下的類并不多:
HandlerMethod
封裝 Handler 和具體處理請(qǐng)求的 Method。
InvocableHandlerMethod
在 HandlerMethod 的基礎(chǔ)上增加了調(diào)用的功能。
ServletInvocableHandlerMethod
在 InvocableHandlerMethod 的基礎(chǔ)上增了對(duì) @ResponseStatus 注解的支持、增加了對(duì)返回值的處理。
ConcurrentResultHandlerMethod
在 ServletInvocableHandlerMethod 的基礎(chǔ)上,增加了對(duì)異步結(jié)果的處理。
基本上就是這四個(gè),接下來松哥就來詳細(xì)說一說這四個(gè)組件。
2.1 bridgedMethod
在正式開始介紹 HandlerMethod 之前,想先和大家聊聊 bridgedMethod,因?yàn)樵?HandlerMethod 中將會(huì)涉及到這個(gè)東西,而有的小伙伴可能還沒聽說過 bridgedMethod,因此松哥在這里做一個(gè)簡(jiǎn)單介紹。
首先考考大家,下面這段代碼編譯會(huì)報(bào)錯(cuò)嗎?
public interface Animal<T> { void eat(T t); } public class Cat implements Animal<String> { @Override public void eat(String s) { System.out.println("cat eat " + s); } } public class Demo01 { public static void main(String[] args) { Animal animal = new Cat(); animal.eat(new Object()); } }
首先我們定義了一個(gè) Animal 接口,里邊定義了一個(gè) eat 方法,同時(shí)聲明了一個(gè)泛型。Cat 實(shí)現(xiàn)了 Animal 接口,將泛型也定義為了 String。當(dāng)我調(diào)用的時(shí)候,聲明類型是 Animal,實(shí)際類型是 Cat,這個(gè)時(shí)候調(diào) eat 方法傳入了 Object 對(duì)象大家猜猜會(huì)怎么樣?如果調(diào)用 eat 方法時(shí)傳入的是 String 類型那就肯定沒問題,但如果不是 String 呢?
松哥先說結(jié)論:編譯沒問題,運(yùn)行報(bào)錯(cuò)。
如果小伙伴們?cè)谧约弘娔X上寫出上面這段代碼,你會(huì)發(fā)現(xiàn)這樣一個(gè)問題,開發(fā)工具中提示的參數(shù)類型竟然是 Object,以松哥的 IDEA 為例,如下:
大家看到,在我寫代碼的時(shí)候,開發(fā)工具會(huì)給我提示,這個(gè)參數(shù)類型是 Object,有的小伙伴會(huì)覺得奇怪,明明是泛型,怎么變成 Object 了?
我們可以通過反射查看 Cat 類中到底有哪些方法,代碼如下:
public class Demo01 { public static void main(String[] args) { Method[] methods = Cat.class.getMethods(); for (Method method : methods) { String name = method.getName(); Class<?>[] parameterTypes = method.getParameterTypes(); System.out.println(name+"("+ Arrays.toString(parameterTypes) +")"); } } }
運(yùn)行結(jié)果如下:
可以看到,在實(shí)際運(yùn)行過程中,竟然有兩個(gè) eat 方法,一個(gè)的參數(shù)為 String 類型,另一個(gè)參數(shù)為 Object 類型,這是怎么回事呢?
這個(gè)參數(shù)類型為 Object 的方法其實(shí)是 Java 虛擬機(jī)在運(yùn)行時(shí)創(chuàng)建出來的,這個(gè)方法就是我們所說的 bridge method。本節(jié)的小標(biāo)題叫做 bridgedMethod,這是 HandlerMethod 源碼中的變量名,bridge 結(jié)尾多了一個(gè) d,含義變成了被 bridge 的方法,也就是參數(shù)為 String 的原方法,大家在接下來的源碼中看到了 bridgedMethod 就知道這表示參數(shù)類型不變的原方法。
2.2 HandlerMethod 介紹
接下來我們來簡(jiǎn)單看下 HandlerMethod。
在我們前面分析 HandlerMapping 的時(shí)候(參見:),里邊有涉及到 HandlerMethod,創(chuàng)建 HandlerMethod 的入口方法是 createWithResolvedBean,因此這里我們就從該方法開始看起:
public HandlerMethod createWithResolvedBean() { Object handler = this.bean; if (this.bean instanceof String) { String beanName = (String) this.bean; handler = this.beanFactory.getBean(beanName); } return new HandlerMethod(this, handler); }
這個(gè)方法主要是確認(rèn)了一下 handler 的類型,如果 handler 是 String 類型,則根據(jù) beanName 從 Spring 容器中重新查找到 handler 對(duì)象,然后構(gòu)建 HandlerMethod:
private HandlerMethod(HandlerMethod handlerMethod, Object handler) { this.bean = handler; this.beanFactory = handlerMethod.beanFactory; this.beanType = handlerMethod.beanType; this.method = handlerMethod.method; this.bridgedMethod = handlerMethod.bridgedMethod; this.parameters = handlerMethod.parameters; this.responseStatus = handlerMethod.responseStatus; this.responseStatusReason = handlerMethod.responseStatusReason; this.resolvedFromHandlerMethod = handlerMethod; this.description = handlerMethod.description; }
這里的參數(shù)都比較簡(jiǎn)單,沒啥好說的,唯一值得介紹的地方有兩個(gè):parameters 和 responseStatus。
parameters
parameters 實(shí)際上就是方法參數(shù),對(duì)應(yīng)的類型是 MethodParameter,這個(gè)類的源碼我這里就不貼出來了,主要和大家說一下封裝的內(nèi)容包括:參數(shù)的序號(hào)(parameterIndex),參數(shù)嵌套級(jí)別(nestingLevel),參數(shù)類型(parameterType),參數(shù)的注解(parameterAnnotations),參數(shù)名稱查找器(parameterNameDiscoverer),參數(shù)名稱(parameterName)等。
HandlerMethod 中還提供了兩個(gè)內(nèi)部類來封裝 MethodParameter,分別是:
HandlerMethodParameter:這個(gè)封裝方法調(diào)用的參數(shù)。
ReturnValueMethodParameter:這個(gè)繼承自 HandlerMethodParameter,它封裝了方法的返回值,返回值里邊的 parameterIndex 是 -1。
注意,這兩者中的 method 都是 bridgedMethod。
responseStatus
這個(gè)主要是處理方法的 @ResponseStatus 注解,這個(gè)注解用來描述方法的響應(yīng)狀態(tài)碼,使用方式像下面這樣:
@GetMapping("/04") @ResponseBody @ResponseStatus(code = HttpStatus.OK) public void hello4(@SessionAttribute("name") String name) { System.out.println("name = " + name); }
從這段代碼中大家可以看到,其實(shí) @ResponseStatus 注解靈活性很差,不實(shí)用,當(dāng)我們定義一個(gè)接口的時(shí)候,很難預(yù)知到該接口的響應(yīng)狀態(tài)碼是 200。
在 handlerMethod 中,在調(diào)用其構(gòu)造方法的時(shí)候,都會(huì)調(diào)用 evaluateResponseStatus 方法處理 @ResponseStatus 注解,如下:
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(); } }
可以看到,這段代碼也比較簡(jiǎn)單,找到注解,把里邊的值解析出來,賦值給相應(yīng)的變量。
這下小伙伴們應(yīng)該明白了 HandlerMethod 大概是個(gè)怎么回事。
看名字就知道,InvocableHandlerMethod 可以調(diào)用 HandlerMethod 中的具體方法,也就是 bridgedMethod。我們先來看下 InvocableHandlerMethod 中聲明的屬性:
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @Nullable private WebDataBinderFactory dataBinderFactory;
主要就是這三個(gè)屬性:
resolvers:這個(gè)不用說,參數(shù)解析器,前面的文章中松哥已經(jīng)和大家聊過這個(gè)問題了。
parameterNameDiscoverer:這個(gè)用來獲取參數(shù)名稱,在 MethodParameter 中會(huì)用到。
dataBinderFactory:這個(gè)用來創(chuàng)建 WebDataBinder,在參數(shù)解析器中會(huì)用到。
具體的請(qǐng)求調(diào)用方法是 invokeForRequest,我們一起來看下:
@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); return doInvoke(args); } @Nullable protected Object doInvoke(Object... args) throws Exception { Method method = getBridgedMethod(); ReflectionUtils.makeAccessible(method); try { if (KotlinDetector.isSuspendingFunction(method)) { return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); } return method.invoke(getBean(), args); } catch (InvocationTargetException ex) { // 省略 ... } }
首先調(diào)用 getMethodArgumentValues 方法按順序獲取到所有參數(shù)的值,這些參數(shù)值組成一個(gè)數(shù)組,然后調(diào)用 doInvoke 方法執(zhí)行,在 doInvoke 方法中,首先獲取到 bridgedMethod,并設(shè)置其可見(意味著我們?cè)?Controller 中定義的接口方法也可以是 private 的),然后直接通過反射調(diào)用即可。當(dāng)我們沒看 SpringMVC 源碼的時(shí)候,我們就知道接口方法最終肯定是通過反射調(diào)用的,現(xiàn)在,經(jīng)過層層分析之后,終于在這里找到了反射調(diào)用代碼。
最后松哥再來說一下負(fù)責(zé)參數(shù)解析的 getMethodArgumentValues 方法:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // 省略... } } return args; }
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
首先調(diào)用 getMethodParameters 方法獲取到方法的所有參數(shù)。
創(chuàng)建 args 數(shù)組用來保存參數(shù)的值。
接下來一堆初始化配置。
如果 providedArgs 中提供了參數(shù)值,則直接賦值。
查看是否有參數(shù)解析器支持當(dāng)前參數(shù)類型,如果沒有,直接拋出異常。
調(diào)用參數(shù)解析器對(duì)參數(shù)進(jìn)行解析,解析完成后,賦值。
是不是,很 easy!
ServletInvocableHandlerMethod 則是在 InvocableHandlerMethod 的基礎(chǔ)上,又增加了兩個(gè)功能:
對(duì) @ResponseStatus 注解的處理
對(duì)返回值的處理
Servlet 容器下 Controller 在查找適配器時(shí)發(fā)起調(diào)用的最終就是 ServletInvocableHandlerMethod。
這里的處理核心方法是 invokeAndHandle,如下:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { throw ex; } }
1. 首先調(diào)用父類的 invokeForRequest 方法對(duì)請(qǐng)求進(jìn)行執(zhí)行,拿到請(qǐng)求結(jié)果。
2. 調(diào)用 setResponseStatus 方法處理 @ResponseStatus 注解,具體的處理邏輯是這樣:如果沒有添加 @ResponseStatus 注解,則什么都不做;如果添加了該注解,并且 reason 屬性不為空,則直接輸出錯(cuò)誤,否則設(shè)置響應(yīng)狀態(tài)碼。這里需要注意一點(diǎn),如果響應(yīng)狀態(tài)碼是 200,就不要設(shè)置 reason,否則會(huì)按照 error 處理。
3. 接下來就是對(duì)返回值的處理了,returnValueHandlers#handleReturnValue 方法松哥在之前的文章中和大家專門介紹過,這里就不再贅述,傳送門:Spring Boot 中如何統(tǒng)一 API 接口響應(yīng)格式?。
事實(shí)上,ServletInvocableHandlerMethod 還有一個(gè)子類 ConcurrentResultHandlerMethod,這個(gè)支持異步調(diào)用結(jié)果處理,因?yàn)槭褂脠?chǎng)景較少,這里就不做介紹啦。
關(guān)于Spring Boot中怎么定義接口問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。