可以使用属性启用/禁用 Spring Boot @RestController 吗?

Posted

技术标签:

【中文标题】可以使用属性启用/禁用 Spring Boot @RestController 吗?【英文标题】:Can a spring boot @RestController be enabled/disabled using properties? 【发布时间】:2015-07-09 14:16:26 【问题描述】:

给定一个带有@RestController 的“标准”spring boot 应用程序,例如

@RestController
@RequestMapping(value = "foo", produces = "application/json;charset=UTF-8")
public class MyController 
    @RequestMapping(value = "bar")
    public ResponseEntity<String> bar(
        return new ResponseEntity<>("Hello world", HttpStatus.OK);
    

如果/除非某个应用程序属性存在/不存在,是否存在阻止端点启动的注释或技术根本

注意:测试方法内部的属性并爆炸不是解决方案,因为端点将存在。

我不关心粒度:即启用/禁用一个方法或整个类都很好。


因为配置文件不是属性,所以通过配置文件进行控制并不能解决我的问题。

【问题讨论】:

【参考方案1】:

我使用@ConditionalOnExpression找到了一个简单的解决方案:

@RestController
@ConditionalOnExpression("$my.controller.enabled:false")
@RequestMapping(value = "foo", produces = "application/json;charset=UTF-8")
public class MyController 
    @RequestMapping(value = "bar")
    public ResponseEntity<String> bar(
        return new ResponseEntity<>("Hello world", HttpStatus.OK);
    

加上这个注释,除非我有

my.controller.enabled=true

在我的application.properties 文件中,控制器根本不会启动。

你也可以使用更方便的:

@ConditionalOnProperty("my.property")

其行为与上述完全相同;如果属性存在且"true",则组件启动,否则不启动。

【讨论】:

您可能需要考虑@ConditionalOnProperty,因为它比 SpEL 评估略快。试试@ConditionalOnProperty(prefix="my.controller", name="enabled") 谢谢,关于可以应用此注释的级别的另一个说明:***.com/questions/30065945/… 在 RestController 之后使用 ConditionalOnProperty 或 ConditionalOnExpression 对我不起作用。正在创建 Bean 在 AdminController RestController 的日志中仍然可以访问 URL:DozerInitializer - Dozer JMX MBean [org.dozer.jmx:type=DozerAdminController] 自动注册到 Platform MBean 服务器有什么帮助吗? 这个解决方案的问题是,如果你改变了属性,除非你使用spring cloud进行配置,否则你将不得不重启服务器。 @user666 最佳实践将配置作为(系统测试)部署包的一部分,因此如果您遵循最佳实践,则需要重新启动。无论如何,这种控制通常是“功能切换”,因此激活将是有计划的更改,而不是临时的。对于 ad hoc,您可能会通过应用程序外部的网络来控制它,例如通过负载平衡器。【参考方案2】:

添加到这个问题和另一个问题here.

这是我的回答:

我实际上会使用@RefreshScope Bean,然后当您想在运行时停止 Rest Controller 时,只需将所述控制器的属性更改为 false。

SO 的 link 引用了在运行时更改属性。

这是我的工作代码的 sn-ps:

@RefreshScope
@RestController
class MessageRestController(
    @Value("\$message.get.enabled") val getEnabled: Boolean,
    @Value("\$message:Hello default") val message: String
) 
    @GetMapping("/message")
    fun get(): String 
        if (!getEnabled) 
            throw NoHandlerFoundException("GET", "/message", null)
        
        return message
    

还有其他使用过滤器的替代方法:

@Component
class EndpointsAvailabilityFilter @Autowired constructor(
    private val env: Environment
): OncePerRequestFilter() 
    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) 
        val requestURI = request.requestURI
        val requestMethod = request.method
        val property = "$requestURI.substring(1).replace("/", ".")." +
                "$requestMethod.toLowerCase().enabled"
        val enabled = env.getProperty(property, "true")
        if (!enabled.toBoolean()) 
            throw NoHandlerFoundException(requestMethod, requestURI, ServletServerHttpRequest(request).headers)
        
        filterChain.doFilter(request, response)
    

My Github explaining how to disable at runtime

【讨论】:

如果路径包含变量怎么办?【参考方案3】:

在某些情况下,@ConditionalOnXXX 不能工作,例如,依赖另一个 bean 实例来检查条件。 (XXXCondition 类不能调用 bean)。

在这种情况下,请在 Java 配置文件中注册控制器。

查看源码(Spring webmvc 5.1.6):

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.isHandler(Class<?>)
 
       @Override
       protected boolean isHandler(Class<?> beanType) 
              return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                           AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
       

应该在控制器 bean 的类型级别上添加 @RequestMapping 注解。见:

@RequestMapping // Make Spring treat the bean as request handler
public class MyControllerA implements IMyController 
    @RequestMapping(path =  "/path1" )
    public .. restMethod1(...) 
  ........
    


@RequestMapping // Make Spring treat the bean as request handler
public class MyControllerB implements IMyController 
    @RequestMapping(path =  "/path1" )
    public .. restMethod1(...) 
  ........
    


@Configuration
public class ControllerConfiguration 

    /**
     *
     * Programmatically register Controller based on certain condition.
     *
     */
    @Bean
    public IMyController myController() 
        IMyController controller;
        if (conditionA) 
            controller = new MyControllerA();
         else 
            controller = new MyControllerB();
        
        return controller;
    

【讨论】:

【参考方案4】:

我认为这个问题来自您为不同的环境使用不同的 application.properties 文件这一事实。在这种情况下,您可以使用 spring 配置文件并将配置分隔到具有配置文件名称后缀的不同文件中,例如:

application.properties:

spring.profiles.active=@activatedProperties@

应用程序本地属性:

 //some config

application-prod.properties:

//some config

然后在您的构建参数中,您可以通过添加选项来指定您要构建的环境:

-Dspring.profiles.active= //<-put here profile local or prod

然后在您的应用程序中,您可以通过添加启用/禁用任何 Spring bean

@Profile("put here profile name")

例如:

@RestController
@Profile("local")
@RequestMapping("/testApi")
public class RestForTesting

//do some stuff


现在我的 RestForTesting 将仅在我运行使用

创建的构建时创建

-Dspring.profiles.active=local

【讨论】:

没有。这个问题与配置文件无关,配置文件只是管理属性的众多方法之一。相反,我只想将端点部署到非生产环境 - 我不能让端点以任何形式存在于生产环境中。 我之前尝试过,向控制器添加 @Profile 注释没有任何作用。

以上是关于可以使用属性启用/禁用 Spring Boot @RestController 吗?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot:为执行器端点禁用 https

Spring MVC Web 应用程序 - 从属性启用/禁用控制器

在初始化时加载 Spring Boot 属性并尊重所有属性并根据属性文件中的值控制 @Aspect

如何在 Spring Boot 中启用 HTTP 响应缓存

Spring启用/禁用带有配置文件的嵌入式tomcat

spring boot 导入xml配置文件所需注解和禁用自动配置类的注解