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-2</module>
|
||||||
<module>spring-boot-mvc-3</module>
|
<module>spring-boot-mvc-3</module>
|
||||||
<module>spring-boot-mvc-4</module>
|
<module>spring-boot-mvc-4</module>
|
||||||
|
<module>spring-boot-mvc-5</module>
|
||||||
<module>spring-boot-mvc-birt</module>
|
<module>spring-boot-mvc-birt</module>
|
||||||
<module>spring-boot-mvc-jersey</module>
|
<module>spring-boot-mvc-jersey</module>
|
||||||
<module>spring-boot-nashorn</module>
|
<module>spring-boot-nashorn</module>
|
||||||
|
5
spring-boot-modules/spring-boot-mvc-5/README.md
Normal file
5
spring-boot-modules/spring-boot-mvc-5/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
## Spring Boot MVC
|
||||||
|
|
||||||
|
This module contains articles about Spring Web MVC in Spring Boot projects.
|
||||||
|
|
||||||
|
### Relevant Articles:
|
2
spring-boot-modules/spring-boot-mvc-5/extra.properties
Normal file
2
spring-boot-modules/spring-boot-mvc-5/extra.properties
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
endpoint.foo=true
|
||||||
|
endpoint.regex=.*
|
73
spring-boot-modules/spring-boot-mvc-5/pom.xml
Normal file
73
spring-boot-modules/spring-boot-mvc-5/pom.xml
Normal file
@ -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…
x
Reference in New Issue
Block a user