Let's say I have the following controller:
@RestController
public class MyController {
@GetMapping("v1/remain")
public MyObject getRemain() {
// ...
}
}
How can I enable or disable this endpoint at runtime dynamically with Spring boot? Also, is it possible to change this without having to restart the application?
You can either use @ConditionalOnExpression or @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);
}
}
Now if you want the above controller to work, you need to add following in application.properties file.
my.controller.enabled=true
Without the above statement, it will behave like the above controller don't exist.
Similiarly,
@ConditionalOnProperty("my.property")
behaves exactly same as above; if the property is present and "true", the component works, otherwise it doesn't.
To dynamically reload beans when a property changes, you could use Spring boot actuator + Spring cloud so that you have access to the /actuator/refresh
endpoint.
This can be done by adding the following dependencies:
<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>
The latter does require that you add the BOM for Spring cloud, which is:
<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>
Now you can enable the /actuator/refresh
endpoint by setting the following property:
management.endpoints.web.exposure.include=refresh
This will allow you to send a POST
call to /actuator/refresh
, which will return an array of all changed properties.
By using the /actuator/refresh
endpoint, it also allows you to use the @RefreshScope
annotation to recreate beans. However, there are a few limitations:
@RefreshScope
recreates the bean without re-evaluating conditionals that might have changed due to the refresh. That means that this solution doesn't work with @RefreshScope
, as seen in the comment section of this question.@RefreshScope
doesn't work nicely with filters either, as seen in this issue.That means you have two options:
Add the @RefreshScope
to the controller and do the conditional logic by yourself, for example:
@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();
}
}
This means you would have to add this condition to all endpoints within your controller. I haven't verified if you could use this with aspects.
Another solution is to not use @RefreshScope
to begin with, and to lazily fetch the property you want to validate. This allows you to use it with a filter, for example:
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());
}
}
}
You'll have to register the filter as well, for example by using:
@Bean
public FilterRegistrationBean<FooFilter> fooFilter(Environment environment) {
FilterRegistrationBean<FooFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new FooFilter(environment));
bean.addUrlPatterns("/api/foo");
return bean;
}
Please note, this approach only fetches the property dynamically from the Environment
. Refreshing the Environment
itself still requires you to use the /actuator/refresh
endpoint.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With