Spring From the Trenches: Returning Runtime Configuration as JSON
If we need to figure out the runtime configuration of a Spring web application that is deployed to a remote server, we need to the read the properties file found from the remote server. This is cumbersome.
Luckily, there is a better way. This blog post describes how we can
- Write the runtime configuration to a log file when our web application is started.
- Return the runtime configuration as JSON.
Let’s get started.
If you use Spring Boot, you should use the Spring Boot Actuator. It provides additional features that helps you to monitor and manage your Spring Boot application.
If you haven’t read my blog post titled: Spring From the Trenches: Injecting Property Values Into Configuration Beans, you should read it before you continue reading this blog post. It provides additional information that helps you to understand this blog post.
Writing the Runtime Configuration to a Log File
We can write the runtime configuration to a log file by following these steps:
- Add a toString() method to the WebProperties class.
- Add a toString() method to the ApplicationProperties class.
- Write the runtime configuration to a log file when our web application is started.
Let’s find out how we can finish these steps.
First, we have to add a toString() method to the WebProperties class and implement this method by using the ToStringBuilder class.
After we have done this, the source code of the WebProperties class looks as follows:
import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public final class WebProperties { private final String protocol; private final String serverHost; private final int serverPort; @Autowired public WebProperties(@Value("${app.server.protocol}") String protocol, @Value("${app.server.host}") String serverHost, @Value("${app.server.port}") int serverPort) { checkThatProtocolIsValid(protocol); this.protocol = protocol; this.serverHost = serverHost; this.serverPort = serverPort; } private void checkThatProtocolIsValid(String protocol) { if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) { throw new IllegalArgumentException(String.format( "Protocol: %s is not allowed. Allowed protocols are: http and https.", protocol )); } } public String getProtocol() { return protocol; } public String getServerHost() { return serverHost; } public int getServerPort() { return serverPort; } @Override public String toString() { return new ToStringBuilder(this) .append("protocol", this.protocol) .append("serverHost", this.serverHost) .append("serverPort", this.serverPort) .toString(); } }
Second, we have to add a toString() method to the ApplicationProperties class and implement it by using the ToStringBuilder class.
After we have made these changes to the ApplicationProperties class, its source code looks as follows:
import org.apache.commons.lang3.builder.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public final class ApplicationProperties { private final String name; private final boolean productionModeEnabled; private final WebProperties webProperties; @Autowired public ApplicationProperties(@Value("${app.name}") String name, @Value("${app.production.mode.enabled:false}") boolean productionModeEnabled, WebProperties webProperties) { this.name = name; this.productionModeEnabled = productionModeEnabled; this.webProperties = webProperties; } public String getName() { return name; } public boolean isProductionModeEnabled() { return productionModeEnabled; } public WebProperties getWebProperties() { return webProperties; } @Override public String toString() { return new ToStringBuilder(this) .append("name", this.name) .append("productionModeEnabled", this.productionModeEnabled) .append("webProperties", this.webProperties) .toString(); } }
Third, we have to write the runtime configuration to the log file when the application is started. We can do this by following these steps:
- Add a static final Logger field to ApplicationProperties class and create a new Logger object by using the LoggerFactory class.
- Add a writeConfigurationToLog() method to the ApplicationProperties class and annotate it with the @PostConstruct annotation. This ensures that the method is invoked after the dependencies of the created bean object have been injected into it.
- Implement the writeConfigurationToLog() method by writing the configuration to a log file.
After we have made these changes to the ApplicationProperties class, its source code looks as follows:
import org.apache.commons.lang3.builder.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public final class ApplicationProperties { private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationProperties.class); private final String name; private final boolean productionModeEnabled; private final WebProperties webProperties; @Autowired public ApplicationProperties(@Value("${app.name}") String name, @Value("${app.production.mode.enabled:false}") boolean productionModeEnabled, WebProperties webProperties) { this.name = name; this.productionModeEnabled = productionModeEnabled; this.webProperties = webProperties; } public String getName() { return name; } public boolean isProductionModeEnabled() { return productionModeEnabled; } public WebProperties getWebProperties() { return webProperties; } @Override public String toString() { return new ToStringBuilder(this) .append("name", this.name) .append("productionModeEnabled", this.productionModeEnabled) .append("webProperties", this.webProperties) .toString(); } @PostConstruct public void writeConfigurationToLog() { LOGGER.info("Starting application by using configuration: {}", this); } }
When we start our web application, we should find the following information from its log file:
INFO - ApplicationProperties - Starting application by using configuration: net.petrikainulainen.spring.trenches.config.ApplicationProperties@254449bb[ name=Configuration Properties example, productionModeEnabled=false, webProperties=net.petrikainulainen.spring.trenches.config.WebProperties@4e642ee1[ protocol=http, serverHost=localhost, serverPort=8080 ] ]
That information is written into a single line, but I formatted it a bit because I wanted to make it easier to read.
It is not be a good idea to write sensitive information, such as the username of the database user or the password of the database user, to a log file.
We can now find the runtime configuration of our web application from its log file. This is an improvement over the current situation, but it makes our life easier only if we are already reading the log file.
Let’s find out how we can make our life even more easier by implementating a controller method that returns the runtime configuration as JSON.
Returning the Runtime Configuration as JSON
We can implement a controller method that returns the runtime configuration as JSON by following these steps:
- Create a controller class and annotate it with the @RestController annotation.
- Inject the ApplicationProperties bean into the created controller bean by using constructor injection.
- Create a controller method that processes GET requests send to url ‘/config’ and implement it by returning the ApplicationProperties object.
The source code of the PropertiesController class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController final class PropertiesController { private final ApplicationProperties applicationProperties; @Autowired PropertiesController(ApplicationProperties applicationProperties) { this.applicationProperties = applicationProperties; } @RequestMapping(value = "/config", method = RequestMethod.GET) ApplicationProperties getAppConfiguration() { return applicationProperties; } }
When we send a GET request to the url ‘/config’, our controller method returns the following JSON:
{ "name":"Configuration Properties example", "productionModeEnabled":false, "webProperties":{ "protocol":"http", "serverHost":"localhost", "serverPort":8080 } }
We shouldn’t allow everyone to access the configuration of our application. If this would be a real life application, we should ensure that only administrators can access this information.
Let’s move on and summarize what we learned from this blog post.
Summary
This blog post has taught us that:
- We can write the runtime configuration to a log file by overriding the toString() methods of the configuration bean classes and writing the property values of these beans to a log file after the property values have been injected into them.
- We can return the runtime configuration as JSON by creating a controller method that returns the “root” configuration bean object.
Reference: | Spring From the Trenches: Returning Runtime Configuration as JSON from our JCG partner Petri Kainulainen at the Petri Kainulainen blog. |