獲取Java後端專案的所有controller介面資訊(一)

語言: CN / TW / HK

highlight: monokai

「這是我參與2022首次更文挑戰的第1天,活動詳情檢視:2022首次更文挑戰

前言

在我們進行後端業務開發時,一個經常需要的小功能就是去獲取我們所有編寫的介面及其請求資訊。可以用於介面測試,或者開發一些自定義的工具。

在開發過程中,我們一般會配置swagger來對於我們的controller進行測試,也經常會使用postman、curl等工具來模擬傳送請求,但是很多時候這些工具並不能滿足我們的所有需求,需要進行定製化開發。例如筆者所在的團隊,除了日常使用swagger之外,同時基於所有的介面資訊維護了一個工具來方便進行一些定製化的需求開發、日常的業務維護以及線上問題的處理。

那麼如何獲取所需要的介面資訊呢?

結合所查詢的資訊,以及筆者的親身實踐,大致總結了以下三種方法 1. 通過 RequestMappingHandlerMapping 2. 通過 Swagger 3. 通過 Spring Boot Actuator

作為一個系列文章,本文章首先介紹最基礎的方法——通過 RequestMappingHandlerMapping 獲取

什麼是 RequestMappingHandlerMapping

直接看原始碼註釋 java /** * Creates {@link RequestMappingInfo} instances from type and method-level * {@link RequestMapping @RequestMapping} annotations in * {@link Controller @Controller} classes. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Sam Brannen * @since 3.1 */ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware { ...... } 正如註釋中所說,該類用於處理被 @Controller 所註解類中型別和方法級別的 @RequestMapping 並建立 RequestMappingInfo 例項,即用來建立並儲存所有的請求對映資訊。而在參考文件2中對其的描述如下

The RequestMappingHandlerMapping is used to maintain the mapping of the request URI to the handler. Once the handler is obtained, the DispatcherServlet dispatches the request to the appropriate handler adapter, which then invokes the handlerMethod().

即儲存了從請求路徑到對應handler的對映,DispatcherServlet 將請求交給 RequestMappingHandlerAdapter 來處理,呼叫對應儲存的 handle 方法

RequestMappingInfo

RequestMappingInfo 中儲存了哪些東西呢, 我們看原始碼註釋 java /** * Request mapping information. Encapsulates the following request mapping conditions: * <ol> * <li>{@link PatternsRequestCondition} * <li>{@link RequestMethodsRequestCondition} * <li>{@link ParamsRequestCondition} * <li>{@link HeadersRequestCondition} * <li>{@link ConsumesRequestCondition} * <li>{@link ProducesRequestCondition} * <li>{@code RequestCondition} (optional, custom request condition) * </ol> * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */ public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> { ...... } 可以看出,其封裝瞭如下幾個資訊

  1. PatternsRequestCondition 請求路徑資訊
  2. RequestMethodsRequestCondition http方法資訊
  3. ParamsRequestCondition query或者表單資訊
  4. HeadersRequestCondition 請求頭資訊
  5. HeadersRequestCondition 請求的Content-Type資訊
  6. ProducesRequestCondition 所需的Accept資訊
  7. 其他自定義的請求條件

因此一個很簡單的想法便可以從腦海中浮現,如果在SpringMVC啟動完成之後,我們去獲取 RequestMappingHandlerMapping 物件,便可以從中獲取到所有的介面資訊

如何做到呢?監聽容器啟動事件!

ApplicationListener@EventListener

ApplicationListener 介面用於實現容器的事件監聽,其設計採用觀察者模式,需要實現 onApplicationEvent 方法,其中泛型 E 即為所關心的容器事件,其他事件會被過濾 ```java public interface ApplicationListener extends EventListener {

/* * Handle an application event. * @param event the event to respond to / void onApplicationEvent(E event); } `` 為了方便,也可以使用註解@EventListener` 來進行實現

具體實現

直接上程式碼,其中隱藏自定義處理的內容 ```java @Slf4j @Component public class FrontToolListener implements ApplicationListener {

private static final String REQUEST_BEAN_NAME = "requestMappingHandlerMapping";

// 實現事件監聽方法
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    // 需要暴露資訊,因此線上環境不掃描
    if (判斷是否是線上環境) {
        log.info("線上環境,不掃描controller");
        return;
    }

    // 判斷RequestMappingHandlerMapping是否存在,若不存在則不掃描
    ApplicationContext applicationContext = event.getApplicationContext();
    if (!applicationContext.containsBean(REQUEST_BEAN_NAME)) {
        log.info("{}不存在, 掃描跳過", REQUEST_BEAN_NAME);
        return;
    }

    log.info("{}存在,開始掃描controller方法", REQUEST_BEAN_NAME);
    // 獲取所有的介面方法資訊
    RequestMappingHandlerMapping requestMapping =
        applicationContext.getBean(REQUEST_BEAN_NAME, RequestMappingHandlerMapping.class);
    Map<RequestMappingInfo, HandlerMethod> infoMap = requestMapping.getHandlerMethods();

    // 省略自定義處理資訊
    ......
}

} ```

一些需要注意的問題

  1. 筆者自定義工具需要暴露介面資訊,故線上環境不允許進行掃描,否則存在安全性問題,預發環境原則上也不允許進行掃描,除非你可以保證預發環境不會被外部訪問
  2. 在某些專案內可能不存在 RequestMappingHandlerMapping,需要手動進行注入,什麼情況下不存在筆者還在研究中, 注入的程式碼如下 ```java @Slf4j @EnableWebMvc @Configuration public class FrontToolConfig implements WebMvcConfigurer {

    @Bean @ConditionalOnMissingBean(name = "requestMappingHandlerMapping") public RequestMappingHandlerMapping requestMappingHandlerMapping() { log.info("RequestMappingHandlerMapping不存在,開始注入"); return new RequestMappingHandlerMapping(); } `` 3.ContextRefreshedEvent` 事件可能會觸發多次,導致我們的監聽函式多次被執行,其本質是因為存在多個ioc容器從而多次觸發容器事件,解決方法可以參考這篇文章 實現ApplicationListener 事件被觸發兩次的問題 或者自己新增一個static的bool值來標識是否已經處理完成

參考文件

  1. Get All Endpoints in Spring Boot | Baeldung
  2. Types of Spring HandlerAdapters | Baeldung
  3. Spring Application Context Events | Baeldung
  4. 實現ApplicationListener 事件被觸發兩次的問題