Can a Spring Boot @Restcontroller Be Enabled/Disabled Using Properties

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

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.

Enable and disable endpoints at runtime with Spring boot

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.

Spring Boot, Maven, disable specific RestController in the derived project

I think the crucial fact here to understand is that Spring works at runtime only, while maven matters in build time.

So maven sees that api2 depends on api1 so it understands that both modules have to be included in the artifact (in the case of spring boot its a big jar with all modules inside).

Now, when spring starts - it "takes for granted" that all modules are accessible, and depending on spring configurations it just defines beans to be loaded and processed, all rest controllers are among these beans of course.

So I assume, you don't mind having two modules in the artifact (and in classpath).

In this case, you shouldn't touch the maven part at all, but when the spring boot application starts it has to be "instructed" somehow that some rest controllers have to be excluded. The point is that it should be done not in terms of modules ("hey, spring, this controller belongs to module api2, so it has to be excluded"), but in terms of business "jargon". For example, api1 contains all "admin" functionality and api2 contains all "applicative" stuff. So, if you work with Java configurations, for example, you can do the following:

Inside module api1:

@Configuration
@ConditionalOnProperty(name = "admin.enabled", havingValue=true)
public class AdminControllersConfiguration {

@Bean
public AdminControllerFromModuleApi1 adminController() {
return new AdminControllerFromModuleApi1();
}
}

}

In module api2 you just define your rest controllers in a similar way but without "@ConditionalOnProperty" annotation.

The thing with this annotation is that it allows to "switch off" beans or entire configurations like in my example.

So, when you start api2, you just define in "application.properties" or something the following:

admin.enabled=false

And your controllers won't be "deployed" by spring although physically the files are certainly in the classpath.

Of course, since spring allows different types of configurations, this method might not be applicable to your project, but the idea is still the same.

Is there any way to enable/disable a annotation by property in Spring Boot?

  1. You can add @ConditionalOnProperty to your controller and specify the property based on which that controller's bean to be initialized or not.
@ConditionalOnProperty(name = "enable.test.controller", havingValue = "true")

  1. And for @CrossOrigin, if you want to add this annotation to single controller, you can specify the origins attribute in the annotation and specify the allowed origin URLs.

OR

You can create a global CORS configuration which can be easily turned off or on using a profile. for eg. @Profile("CORS_enabled_profile")

    public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
}
};
}

Spring MVC web application - enabling / disabling controller from property

If you are using Spring 3.1+, make the controller available only in the test profile:

@Profile("test")
class TestController {
...
}

then enable that profile by e.g. passing the following system property at Tomcat boot:

-Dspring.profiles.active=test

To disable the controller simply omit the given profile.

Spring Boot - Is it possible to disable an end-point

You can use @ConditionalOnExpression annotation.

public class MyController {

@ConditionalOnExpression("${my.controller.enabled:false}")
public String endpoint1() {...}

public String endpoint2() {...}
}

In application.properties, you indicates that controller is enabled by default

my.controller.enabled=true

ConditionalOnExpression sets false your property, and doesn't allow access to end-point



Related Topics



Leave a reply



Submit