7

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?

2 Answers 2

13

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.

Sign up to request clarification or add additional context in comments.

8 Comments

When the program runs when it is true to false nothing happens
How do I change the configuration in runtime mode to apply to the program? (property dynamic)
How to "property dynamic"?
This is a static behavior not a dynamic one.
How can i use same functionality on controller's end-point?. Means i want to disable only end-point not whole controller.
|
4

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:

  1. @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.
  2. @RefreshScope doesn't work nicely with filters either, as seen in this issue.

That means you have two options:

  1. 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.

  2. 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.

2 Comments

+1. If we don't want to get into the additional complexity of spring cloud. A JMX method can be an easy alternative.
@HimanshuBhardwaj Spring Boot Actuator also allows you to call endpoints using JMX if I'm not mistaken. However, enabling the refresh actuator endpoint relies on using the spring-cloud-starter (doesn't require any other Spring cloud stuff though). If you don't want to rely on Spring Cloud, you could write your own refresh endpoint. A good starting point is this code from Spring Cloud.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.