知識庫 / Spring / Spring Boot RSS 訂閱

在運行時啓用和禁用端點(Spring Boot)

Spring Boot
HongKong
5
11:51 AM · Dec 06 ,2025

1. 概述

應用程序中的端點是與應用程序交互的機制。例如,在計劃外的維護窗口期間,我們可能希望暫時限制應用程序與外部的交互。

在本教程中,我們將學習如何在 Spring Boot 應用程序的運行時啓用和禁用端點,使用一些流行的庫,例如 Spring Cloud、Spring Actuator 和 Apache 的 Commons Configuration。

2. 設置

在本節中,我們將重點關注為我們的 Spring Boot 項目設置關鍵方面。

2.1. Maven 依賴

首先,我們需要讓 Spring Boot 應用程序暴露 /refresh 端點,因此,我們將在項目的 pom.xml 文件中添加 spring-boot-starter-actuator 依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>3.1.5</version>
</dependency>

接下來,由於稍後我們需要使用 @RefreshScope 註解來重新加載環境中的屬性源,所以我們添加 spring-cloud-starter 依賴項:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter</artifactId>
    <version>3.1.5</version>
</dependency>

此外,我們還需要在項目的 pom.xml 的依賴管理部分添加 BOM(字節序標記)給 Spring Cloud,以便 Maven 使用與 spring-cloud-starter 兼容的版本:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2021.0.5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

最後,由於我們需要在運行時重新加載文件,我們還需要添加 commons-configuration 依賴項:

<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.10</version>
</dependency>

2.2. 配置

首先,將配置添加到 application.properties文件中,以啓用應用程序中的 /refresh端點:

management.server.port=8081
management.endpoints.web.exposure.include=refresh

接下來,讓我們定義一個額外的源,我們可以用來重新加載屬性:

dynamic.endpoint.config.location=file:extra.properties

此外,我們還需要在 application.properties 文件中定義 spring.properties.refreshDelay 屬性:

spring.properties.refreshDelay=1

最後,讓我們為 extra.properties 文件添加兩個屬性:

endpoint.foo=false
endpoint.regex=.*

在後續部分,我們將理解這些附加屬性的核心意義。

2.3 API 端點

首先,讓我們定義一個示例 GET API,其路徑為 /foo

@GetMapping("/foo")
public String fooHandler() {
    return "foo";
}

接下來,我們定義了兩個可用的 GET API,分別位於 /bar1/bar2 路徑上:

@GetMapping("/bar1")
public String bar1Handler() {
    return "bar1";
}

@GetMapping("/bar2")
public String bar2Handler() {
    return "bar2";
}

在後續章節中,我們將學習如何切換單個端點,例如 foo。 此外,我們還將學習如何切換一組端點,即 bar1bar2,這些端點可以通過簡單的正則表達式進行識別。

2.4. 配置 DynamicEndpointFilter

要隨時切換一組端點,我們可以使用 Filter。 通過使用 endpoint.regex 模式匹配請求的端點,我們可以允許成功匹配或返回 503 HTTP 響應狀態碼,用於不成功的匹配。

因此,讓我們通過擴展 OncePerRequestFilter定義 DynamicEndpointFilter

public class DynamicEndpointFilter extends OncePerRequestFilter {
    private Environment environment;

    // ...
}

進一步,我們需要通過覆蓋 doFilterInternal()方法來添加模式匹配的邏輯:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
  FilterChain filterChain) throws ServletException, IOException {
    String path = request.getRequestURI();
    String regex = this.environment.getProperty("endpoint.regex");
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(path);
    boolean matches = matcher.matches();

    if (!matches) {
        response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service is unavailable");
    } else {
        filterChain.doFilter(request,response);
    }
}

我們必須注意到 <em>endpoint.regex</em> 屬性的初始值為“.*”,這允許所有請求通過此 Filter

3. 使用環境屬性切換

本節將學習如何通過從 extra.properties 文件中獲取環境屬性來執行熱重載。

3.1. 重新加載配置

為此,我們首先定義一個使用 FileChangedReloadingStrategy 的 Bean,用於 PropertiesConfiguration

@Bean
@ConditionalOnProperty(name = "dynamic.endpoint.config.location", matchIfMissing = false)
public PropertiesConfiguration propertiesConfiguration(
  @Value("${dynamic.endpoint.config.location}") String path,
  @Value("${spring.properties.refreshDelay}") long refreshDelay) throws Exception {
    String filePath = path.substring("file:".length());
    PropertiesConfiguration configuration = new PropertiesConfiguration(
      new File(filePath).getCanonicalPath());
    FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy();
    fileChangedReloadingStrategy.setRefreshDelay(refreshDelay);
    configuration.setReloadingStrategy(fileChangedReloadingStrategy);
    return configuration;
}

需要注意的是,這些屬性的來源是通過 dynamic.endpoint.config.location 屬性在 application.properties 文件中確定的。此外,重新加載發生在 1 秒的延遲後,這是由 spring.properties.refreshDelay 屬性定義的。

接下來,我們需要在運行時讀取特定於端點的屬性。因此,讓我們定義 EnvironmentConfigBean,並使用屬性獲取器:

@Component
public class EnvironmentConfigBean {

    private final Environment environment;

    public EnvironmentConfigBean(@Autowired Environment environment) {
        this.environment = environment;
    }

    public String getEndpointRegex() {
        return environment.getProperty("endpoint.regex");
    }

    public boolean isFooEndpointEnabled() {
        return Boolean.parseBoolean(environment.getProperty("endpoint.foo"));
    }

    public Environment getEnvironment() {
        return environment;
    }
}

接下來,讓我們創建一個 FilterRegistrationBean 來註冊 DynamicEndpointFilter

@Bean
@ConditionalOnBean(EnvironmentConfigBean.class)
public FilterRegistrationBean<DynamicEndpointFilter> dynamicEndpointFilterFilterRegistrationBean(
  EnvironmentConfigBean environmentConfigBean) {
    FilterRegistrationBean<DynamicEndpointFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new DynamicEndpointFilter(environmentConfigBean.getEnvironment()));
    registrationBean.addUrlPatterns("*");
    return registrationBean;
}

3.2. 驗證

首先,讓我們運行應用程序並訪問 bar1</em/> 或 bar2</em/> API:

$ curl -iXGET http://localhost:9090/bar1
HTTP/1.1 200 
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 4
Date: Sat, 12 Nov 2022 12:46:32 GMT

bar1

正如預期的那樣,我們得到了 由於我們保留了 endpoint.regex 屬性的初始值,從而使所有端點都啓用,從而獲得了 200 OK HTTP 響應。

接下來,通過在 extra.properties 文件中修改 endpoint.regex 屬性,只啓用 /foo 端點。

endpoint.regex=.*/foo

讓我們繼續,看看我們是否能夠訪問 /bar1 API 端點:

$ curl -iXGET http://localhost:9090/bar1
HTTP/1.1 503 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 12 Nov 2022 12:56:12 GMT
Connection: close

{"timestamp":1668257772354,"status":503,"error":"Service Unavailable","message":"Service is unavailable","path":"/springbootapp/bar1"}

正如預期的那樣,DynamicEndpointFilter 已禁用此端點並返回包含 HTTP 503 狀態碼的錯誤響應。

最後,我們還可以檢查我們是否能夠訪問 /foo API 端點:

$ curl -iXGET http://localhost:9090/foo
HTTP/1.1 200 
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 3
Date: Sat, 12 Nov 2022 12:57:39 GMT

foo

太棒了!看起來我們已經搞定了。

4. 使用 Spring Cloud 和 Actuator 切換 API

本節將介紹一種使用 <em/>@RefreshScope</em/> 註解和 Actuator 的 <em/>/refresh</em/> 端點在運行時切換 API 端點的替代方法。

4.1. 使用 @RefreshScope 的端點配置

首先,我們需要定義端點切換配置 Bean 並使用 @RefreshScope 註解它:

@Component
@RefreshScope
public class EndpointRefreshConfigBean {

    private boolean foo;
    private String regex;

    public EndpointRefreshConfigBean(@Value("${endpoint.foo}") boolean foo, 
      @Value("${endpoint.regex}") String regex) {
        this.foo = foo;
        this.regex = regex;
    }
    // getters and setters
}

接下來,我們需要使這些屬性可發現並可刷新,通過創建包裝類,如 ReloadablePropertiesReloadablePropertySource 來實現。

最後,讓我們更新我們的 API 處理程序,使用 EndpointRefreshConfigBean 實例來控制切換流程:

@GetMapping("/foo")
public ResponseEntity<String> fooHandler() {
    if (endpointRefreshConfigBean.isFoo()) {
        return ResponseEntity.status(200).body("foo");
    } else {
        return ResponseEntity.status(503).body("endpoint is unavailable");
    }
}

4.2. 驗證

首先,當 /foo 端點的值設置為 true 時,驗證 endpoint.foo 屬性時:

$ curl -isXGET http://localhost:9090/foo
HTTP/1.1 200
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 3
Date: Sat, 12 Nov 2022 15:28:52 GMT

foo

接下來,我們endpoint.foo值設置為false,並檢查端點是否仍然可訪問:

endpoint.foo=false

我們會注意到 /foo 端點仍然啓用。這是因為我們需要通過調用 /refresh 端點來刷新屬性源。 讓我們現在就執行一次:

$ curl -Is --request POST 'http://localhost:8081/actuator/refresh'
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Sat, 12 Nov 2022 15:34:24 GMT

最後,讓我們嘗試訪問 /foo 端點:

$ curl -isXGET http://localhost:9090/springbootapp/foo
HTTP/1.1 503
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 23
Date: Sat, 12 Nov 2022 15:35:26 GMT
Connection: close

endpoint is unavailable

刷新後,我們可以看到該端點已被禁用。

4.3. 優缺點

Spring Cloud 和 Actuator 方法與直接從環境中獲取屬性相比,具有優勢和劣勢。

首先,當我們依賴 /refresh 端點時,相比基於時間的文件自動刷新策略,我們擁有更精細的控制。因此,應用程序不會在後台進行不必要的 I/O 調用。然而,在分佈式系統中,我們需要確保為所有節點調用 /refresh 端點。

其次,使用帶有 @RefreshScope 註解的配置 Bean 需要我們明確地在 EndpointRefreshConfigBean 類中定義成員變量,以便將其映射到 extra.properties 文件中的屬性。因此,這種方法會帶來在添加或刪除屬性時,修改配置 Bean 的開銷。

最後,我們也需要注意的是,腳本可以輕鬆解決第一個問題,而第二個問題則更具體地取決於我們如何利用屬性。如果我們在 Filter 中使用基於正則表達式的 URL 模式,則可以使用單個屬性通過單個端點控制多個端點,而無需修改配置 Bean。

5. 結論

在本文中,我們探討了在 Spring Boot 應用程序中運行時切換 API 端點的多種策略。 在進行此操作時,我們利用了核心概念,例如屬性的熱重載和 @RefreshScope 註解。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.