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.