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

时间:2019-06-02 11:57:46

标签: spring spring-boot spring-mvc

假设我有以下控制器:

@RestController
public class MyController {
    @GetMapping("v1/remain")
    public MyObject getRemain() {
        // ...
    }
}

如何在运行时通过Spring Boot动态启用或禁用此端点?另外,是否可以在不重新启动应用程序的情况下更改此设置?

2 个答案:

答案 0 :(得分:2)

要在属性更改时动态重新加载Bean,可以使用Spring启动执行器+ Spring云,以便您可以访问/actuator/refresh端点。

这可以通过添加以下依赖项来完成:

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

后者确实要求您添加Spring云的BOM,即:

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

现在,您可以通过设置以下属性来启用/actuator/refresh端点:

 management.endpoints.web.exposure.include=refresh

这将允许您向POST发送/actuator/refresh调用,这将返回所有已更改属性的数组。


通过使用/actuator/refresh端点,它还允许您使用@RefreshScope批注来重新创建bean。但是,有一些限制:

  1. @RefreshScope重新创建Bean,而不重新评估可能由于刷新而改变的条件。这意味着this solution@RefreshScope不兼容,如the comment section of this question所示。
  2. @RefreshScope也不适合与过滤器配合使用,如this issue所示。

这意味着您有两个选择:

  1. @RefreshScope添加到控制器中,并自己执行条件逻辑,例如:

    @RefreshScope
    @RestController
    @RequestMapping("/api/foo")
    public class FooController {
        @Value("${foo.controller.enabled}")
        private boolean enabled;
    
        @GetMapping
        public ResponseEntity<String> getFoo() {
            return enabled ? ResponseEntity.of("bar") : ResponseEntity.notFound().build();
        }
    }
    

    这意味着您必须将此条件添加到控制器内的所有端点。我尚未验证您是否可以将其用于各个方面。

  2. 另一种解决方案是不使用@RefreshScope ,而是懒惰地获取要验证的属性。这使您可以将其与过滤器一起使用,例如:

    public class FooFilter extends OncePerRequestFilter {
        private Environment environment;
    
        public FooFilter(Environment environment) {
            this.environment = environment;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            if ("true".equalsIgnoreCase(environment.getProperty("foo.controller.enabled"))) {
                filterChain.doFilter(request, response);
            } else {
                response.setStatus(HttpStatus.NOT_FOUND.value());
            }
        }
    }
    

    您还必须注册过滤器,例如使用:

    @Bean
    public FilterRegistrationBean<FooFilter> fooFilter(Environment environment) {
        FilterRegistrationBean<FooFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new FooFilter(environment));
        bean.addUrlPatterns("/api/foo");
        return bean;
    }
    

    请注意,此方法仅从Environment动态获取属性。刷新Environment本身仍然需要您使用/actuator/refresh端点。

答案 1 :(得分:0)

您可以使用@ConditionalOnExpression或@ConditionalOnProperty

@RestController
@ConditionalOnExpression("${my.property:false}")
@RequestMapping(value = "my-end-point", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
    @RequestMapping(value = "endpoint1", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> endpoint1(
        return new ResponseEntity<>("Hello world", HttpStatus.OK);
    }
}

现在,如果要使以上控制器正常工作,则需要在application.properties文件中添加以下内容。

my.controller.enabled=true

没有上面的声明,它将表现为上面的控制器不存在。

类似地

@ConditionalOnProperty("my.property")

与上述行为完全相同;如果该属性存在且为“ true”,则该组件有效,否则无效。