Spring Boot 在运行时启用和禁用端点

您所在的位置:网站首页 springboot禁用actuator端口 Spring Boot 在运行时启用和禁用端点

Spring Boot 在运行时启用和禁用端点

2024-06-27 11:32| 来源: 网络整理| 查看: 265

1、概览

在 Spring Boot 运行时,出于一些维护目的,我们可能需要动态地启用、禁用某些端点。

在本教程中,我们将学习如何使用 Spring Cloud、Spring Actuator 和 Apache 的 Commons Configuration 等几个常用库在运行时启用和禁用 Spring Boot 端点。

2、项目设置

以下是 Spring Boot 项目的关键设置。

2.1、Maven 依赖

首先,在 pom.xml 文件中添加 spring-boot-starter-actuator 依赖,用于暴露 /refresh 端点:

org.springframework.boot spring-boot-starter-actuator 2.7.5

接下来,添加 spring-cloud-starter 依赖,因为稍后需要使用其 @RefreshScope 注解来重载 Environment 中的属性源:

org.springframework.cloud spring-cloud-starter 3.1.5

还必须在项目 pom.xml 文件的 中添加 Spring Cloud 的 BOM,以便 Maven 使用兼容版本的 spring-cloud-starter:

org.springframework.cloud spring-cloud-dependencies 2021.0.5 pom import

最后,由于我们需要在运行时重新加载文件的功能,所以还要添加 commons-configuration 依赖:

commons-configuration commons-configuration 1.10 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 的状态。以及如何通过简单的 regex (正则)切换一组端点,即 /bar1 和 /bar2 的状态。

2.4、配置 DynamicEndpointFilter

要在运行时切换一组端点的状态,可以使用 Filter。通过使用 endpoint.regex 表达式匹配请求的端点。在匹配成功时允许请求,匹配失败时响应 503 HTTP 状态码。

定义 DynamicEndpointFilter 类,继承 OncePerRequestFilter:

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); } }

注意,endpoint.regex 属性的初始值为 .*,也就是允许所有请求。

3、通过 Environment Properties 切换状态

在本节中,我们将学习如何通过 Environment Properties 热重载 extra.properties 文件中的配置值。

3.1、重载配置

首先使用 FileChangedReloadingStrategy 为 PropertiesConfiguration 定义一个 Bean:

@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; }

注意,属性源是通过 application.properties 文件中的 dynamic.endpoint.config.location 属性设置的。此外,根据 spring.properties.refreshDelay 属性的定义,重新加载的延迟时间为 1 秒。

接下来,定义 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 dynamicEndpointFilterFilterRegistrationBean( EnvironmentConfigBean environmentConfigBean) { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new DynamicEndpointFilter(environmentConfigBean.getEnvironment())); registrationBean.addUrlPatterns("*"); return registrationBean; } 3.2、验证

首先,运行应用程序并访问 /bar1 或 /bar2 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

不出所料,返回了 200 OK HTTP 响应,因为 endpoint.regex 属性的初始值就是启用所有端点。

接下来,更改 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

/foo 端点还是正常可访问的,说明配置OK。

4、通过 Spring Cloud 和 Actuator 切换状态

在本节中,我们将学习另一种方法,即使用 @RefreshScope 注解和 Actuator /refresh 端点在运行时切换 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; } // get、set 方法省略 }

接下来,需要创建封装类(如 ReloadableProperties 和 ReloadablePropertySource),使这些属性可被发现和重新加载。

最后,更新 API Handler,使用 EndpointRefreshConfigBean 的实例来控制切换流:

@GetMapping("/foo") public ResponseEntity fooHandler() { if (endpointRefreshConfigBean.isFoo()) { return ResponseEntity.status(200).body("foo"); } else { return ResponseEntity.status(503).body("endpoint is unavailable"); } } 4.2、验证

首先,当 endpoint.foo 属性的值设置为 true 时,验证 /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 端点来重新加载属性源。

因此,先执行 /actuator/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、利弊

这 2 种实现方式各有利弊。

首先,使用 /refresh 端点时,控制粒度可以比基于时间的文件重载更精细。应用不会在后台进行额外的 I/O 调用。不过,在分布式系统中,需要确保为所有节点调用 /refresh 端点。

其次,使用 @RefreshScope 注解管理配置 Bean 需要明确定义 EndpointRefreshConfigBean 类中的成员变量,以便与 extra.properties 文件中的属性进行映射。因此,每当添加或删除属性时,这种方法都会增加修改配置 Bean 代码的开销。

最后,我想说,使用脚本可以轻松解决第一个问题,而第二个问题则与我们利用属性的方式有关。如果我们在 Filter 中使用基于正则的 URL 表达式,那么我们就可以用一个属性控制多个端点,而无需修改配置 Bean 的代码。

5、总结

在本文中,我们探索了在 Spring Boot 应用程序中运行时切换 API 端点可用状态的多种策略。在此实现中,我们利用了一些核心概念,如属性的热重载和 @RefreshScope 注解。

参考:https://www.baeldung.com/spring-boot-enable-disable-endpoints-at-runtime



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3