Spring Boot 在运行时启用和禁用端点 |
您所在的位置:网站首页 › springboot禁用actuator端口 › Spring Boot 在运行时启用和禁用端点 |
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 |