BAEL-5854: Toggle Endpoints at Runtime with Spring Boot (#13014)
Co-authored-by: Tapan Avasthi <tavasthi@Tapans-MacBook-Air.local>
This commit is contained in:
parent
5bfbf38f59
commit
f9e34aa9f1
|
@ -55,6 +55,7 @@
|
|||
<module>spring-boot-mvc-2</module>
|
||||
<module>spring-boot-mvc-3</module>
|
||||
<module>spring-boot-mvc-4</module>
|
||||
<module>spring-boot-mvc-5</module>
|
||||
<module>spring-boot-mvc-birt</module>
|
||||
<module>spring-boot-mvc-jersey</module>
|
||||
<module>spring-boot-nashorn</module>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
## Spring Boot MVC
|
||||
|
||||
This module contains articles about Spring Web MVC in Spring Boot projects.
|
||||
|
||||
### Relevant Articles:
|
|
@ -0,0 +1,2 @@
|
|||
endpoint.foo=true
|
||||
endpoint.regex=.*
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-boot-mvc-5</artifactId>
|
||||
<name>spring-boot-mvc-5</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>Module For Spring Boot MVC Web</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring-boot-modules</groupId>
|
||||
<artifactId>spring-boot-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<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>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-configuration</groupId>
|
||||
<artifactId>commons-configuration</artifactId>
|
||||
<version>1.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>${start-class}</mainClass>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<spring.fox.version>3.0.0</spring.fox.version>
|
||||
<start-class>com.baeldung.springboot.swagger.ArticleApplication</start-class>
|
||||
<spring-cloud.version>2021.0.5</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,47 @@
|
|||
package com.baeldung.dynamicendpoints;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import com.baeldung.dynamicendpoints.config.ReloadableProperties;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableWebMvc
|
||||
public class DynamicEndpointApp {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DynamicEndpointApp.class, args);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "dynamic.endpoint.config.location", matchIfMissing = false)
|
||||
public PropertiesConfiguration propertiesConfiguration(
|
||||
@Value("${dynamic.endpoint.config.location}") String path,
|
||||
@Value("${spring.properties.refreshDelay}") long refreshDelay) throws Exception {
|
||||
String filePath = path.substring("file:".length());
|
||||
PropertiesConfiguration configuration = new PropertiesConfiguration(new File(filePath).getCanonicalPath());
|
||||
FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy();
|
||||
fileChangedReloadingStrategy.setRefreshDelay(refreshDelay);
|
||||
configuration.setReloadingStrategy(fileChangedReloadingStrategy);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(PropertiesConfiguration.class)
|
||||
@Primary
|
||||
public Properties properties(PropertiesConfiguration propertiesConfiguration) throws Exception {
|
||||
ReloadableProperties properties = new ReloadableProperties(propertiesConfiguration);
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.baeldung.dynamicendpoints.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RefreshScope
|
||||
public class EndpointRefreshConfigBean {
|
||||
|
||||
private boolean foo;
|
||||
|
||||
private String regex;
|
||||
|
||||
public EndpointRefreshConfigBean(@Value("${endpoint.foo}") boolean foo, @Value("${endpoint.regex}") String regex) {
|
||||
this.foo = foo;
|
||||
this.regex = regex;
|
||||
}
|
||||
|
||||
public boolean isFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
public void setFoo(boolean foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public String getRegex() {
|
||||
return regex;
|
||||
}
|
||||
|
||||
public void setRegex(String regex) {
|
||||
this.regex = regex;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.baeldung.dynamicendpoints.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class EnvironmentConfigBean {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public EnvironmentConfigBean(@Autowired Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public String getEndpointRegex() {
|
||||
return environment.getProperty("endpoint.regex");
|
||||
}
|
||||
|
||||
public boolean isFooEndpointEnabled() {
|
||||
return Boolean.parseBoolean(environment.getProperty("endpoint.foo"));
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.baeldung.dynamicendpoints.config;
|
||||
|
||||
public class PropertiesException extends RuntimeException {
|
||||
public PropertiesException() {
|
||||
}
|
||||
|
||||
public PropertiesException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.baeldung.dynamicendpoints.config;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
|
||||
public class ReloadableProperties extends Properties {
|
||||
private PropertiesConfiguration propertiesConfiguration;
|
||||
|
||||
public ReloadableProperties(PropertiesConfiguration propertiesConfiguration) throws IOException {
|
||||
super.load(new FileReader(propertiesConfiguration.getFile()));
|
||||
this.propertiesConfiguration = propertiesConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String key) {
|
||||
String val = propertiesConfiguration.getString(key);
|
||||
super.setProperty(key, val);
|
||||
return val;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.baeldung.dynamicendpoints.config;
|
||||
|
||||
import org.apache.commons.configuration.ConfigurationException;
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class ReloadablePropertySource extends PropertySource {
|
||||
|
||||
private final PropertiesConfiguration propertiesConfiguration;
|
||||
|
||||
public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) {
|
||||
super(name);
|
||||
this.propertiesConfiguration = propertiesConfiguration;
|
||||
}
|
||||
|
||||
public ReloadablePropertySource(String name, String path) throws ConfigurationException {
|
||||
super(StringUtils.hasText(name) ? path : name);
|
||||
try {
|
||||
this.propertiesConfiguration = new PropertiesConfiguration(path);
|
||||
this.propertiesConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy());
|
||||
} catch (PropertiesException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String s) {
|
||||
return propertiesConfiguration.getProperty(s);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.baeldung.dynamicendpoints.config;
|
||||
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
|
||||
@Configuration
|
||||
public class ReloadablePropertySourceConfig {
|
||||
|
||||
private ConfigurableEnvironment env;
|
||||
|
||||
public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "dynamic.endpoint.config.location", matchIfMissing = false)
|
||||
public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) {
|
||||
ReloadablePropertySource reloadablePropertySource = new ReloadablePropertySource("toggle-endpoints", properties);
|
||||
MutablePropertySources sources = env.getPropertySources();
|
||||
sources.addFirst(reloadablePropertySource);
|
||||
return reloadablePropertySource;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.baeldung.dynamicendpoints.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.baeldung.dynamicendpoints.filter.DynamicEndpointFilter;
|
||||
import com.baeldung.dynamicendpoints.config.EndpointRefreshConfigBean;
|
||||
import com.baeldung.dynamicendpoints.config.EnvironmentConfigBean;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/")
|
||||
public class AppController {
|
||||
|
||||
private EndpointRefreshConfigBean endpointRefreshConfigBean;
|
||||
private EnvironmentConfigBean environmentConfigBean;
|
||||
|
||||
@Autowired
|
||||
public AppController(EndpointRefreshConfigBean endpointRefreshConfigBean, EnvironmentConfigBean environmentConfigBean) {
|
||||
this.endpointRefreshConfigBean = endpointRefreshConfigBean;
|
||||
this.environmentConfigBean = environmentConfigBean;
|
||||
}
|
||||
|
||||
@GetMapping("/foo")
|
||||
public ResponseEntity<String> fooHandler() {
|
||||
if (endpointRefreshConfigBean.isFoo()) {
|
||||
return ResponseEntity.status(200)
|
||||
.body("foo");
|
||||
} else {
|
||||
return ResponseEntity.status(503)
|
||||
.body("endpoint is unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/bar1")
|
||||
public String bar1Handler() {
|
||||
return "bar1";
|
||||
}
|
||||
|
||||
@GetMapping("/bar2")
|
||||
public String bar2Handler() {
|
||||
return "bar2";
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(EnvironmentConfigBean.class)
|
||||
public FilterRegistrationBean<DynamicEndpointFilter> dynamicEndpointFilterFilterRegistrationBean(EnvironmentConfigBean environmentConfigBean) {
|
||||
FilterRegistrationBean<DynamicEndpointFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new DynamicEndpointFilter(environmentConfigBean.getEnvironment()));
|
||||
registrationBean.addUrlPatterns("*");
|
||||
return registrationBean;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.baeldung.dynamicendpoints.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
public class DynamicEndpointFilter extends OncePerRequestFilter {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public DynamicEndpointFilter(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String path = request.getRequestURI();
|
||||
String regex = this.environment.getProperty("endpoint.regex");
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(path);
|
||||
boolean matches = matcher.matches();
|
||||
|
||||
if (!matches) {
|
||||
response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service is unavailable");
|
||||
} else {
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
server.port=9090
|
||||
management.server.port=8081
|
||||
management.server.address=127.0.0.1
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
|
||||
logging.level.org.springframework=INFO
|
||||
|
||||
#Dynamic Endpoint
|
||||
spring.main.allow-bean-definition-overriding=true
|
||||
dynamic.endpoint.config.location=file:extra.properties
|
||||
spring.properties.refreshDelay=1
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
Loading…
Reference in New Issue