spring 引导 @RestController 可以 enabled/disabled 使用属性吗?

Can a spring boot @RestController be enabled/disabled using properties?

给定 "standard" spring 引导应用程序 @RestController,例如

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

是否存在阻止端点启动的注释或技术根本if/unless某个应用程序属性exists/doesn不存在.

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

我不关心粒度:即 enabling/disabling 只是一个方法或整个 class 都可以。


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

我找到了一个使用 @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",组件启动,否则不启动。

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

这是我的回答:

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

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

这是我的工作代码片段:

@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

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

application.properties:

spring.profiles.active=@activatedProperties@

申请-local.properties:

 //some config

申请-prod.properties:

//some config

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

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

然后在您的应用程序中,您可以通过添加

enable/disable 任何 spring bean
@Profile("put here profile name")

例如:

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

//do some stuff

}

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

创建的构建时才会创建

-Dspring.profiles.active=local

在某些情况下,@ConditionalOnXXX 无法工作,例如,依赖于另一个bean 实例来检查条件。 (XXXCondition class 无法调用 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;
    }
}