Can a spring boot @RestController be enabled/disabled using properties?
Adding to this question and another question here.
This is my answer:
I would actually used the @RefreshScope Bean and then when you want to stop the Rest Controller at runtime, you only need to change the property of said controller to false.
SO's link referencing to changing property at runtime.
Here are my snippets of working code:
@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
}
}
And there are other alternatives of using Filter:
@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
I found a simple solution using @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);
}
}
With this annotation added, unless I have
my.controller.enabled=true
in my application.properties
file, the controller won't start at all.
You can also use the more convenient:
@ConditionalOnProperty("my.property")
Which behaves exactly as above; if the property is present and "true"
, the component starts, otherwise it doesn't.
In some case, the @ConditionalOnXXX cannot work, for example, depends on another bean instance to check condition. (XXXCondition class cannot invoke a bean).
In such case, register controller in Java configuration file.
See source code(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));
}
Should add @RequestMapping annotation on type level for the controller bean. See:
@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;
}
}