Spring from the Trenches: Injecting Property Values Into Configuration Beans
Spring Framework has a good support for injecting property values found from properties files into bean or @Configuration classes. However, if we inject individual property values into these classes, we will face some problems.
This blog post identifies these problems and describes how we can solve them.
Let’s get started.
If you use Spring Boot, you should use its typesafe configuration properties. You can get more information about this from the following web pages:
- Section 23.7 Typesafe Configuration Properties of Spring Boot Reference Manual
- The Javadoc of the @EnableConfigurationProperties annotation
- The Javadoc of the @ConfigurationProperties annotation
- Using @ConfigurationProperties in Spring Boot
It’s Simple but not Problem Free
If we inject individual property values into our bean classes, we will face the following problems:
1. Injecting Multiple Property Values Is Cumbersome
If we inject individual property values by using the @Value annotation or get the property values by using an Environment object, injecting multiple property values is cumbersome.
Let’s assume that we have to inject some property values to a UrlBuilder object. This object needs three property values:
- The server’s host (app.server.host)
- The port that is listened by the server (app.server.port)
- The used protocol (app.server.protocol)
These property values are used when the UrlBuilder object builds url addresses that are used to access different functions of our web application.
If we inject these property values by using constructor injection and the @Value annotation, the source code of the UrlBuilder class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class UrlBuilder { private final String host; private final String port; private final String protocol; @Autowired public UrlBuilder(@Value("${app.server.protocol}") String protocol, @Value("${app.server.host}") String serverHost, @Value("${app.server.port}") int serverPort) { this.protocol = protocol.toLowercase(); this.serverHost = serverHost; this.serverPort = serverPort; } }
Additional Reading:
If we inject these property values by using constructor injection and the Environment class, the source code of the UrlBuilder class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @Component public class UrlBuilder { private final String host; private final String port; private final String protocol; @Autowired public UrlBuilder(Environment env) { this.protocol = env.getRequiredProperty("app.server.protocol").toLowercase(); this.serverHost = env.getRequiredProperty("app.server.host"); this.serverPort = env.getRequiredProperty("app.server.port", Integer.class); } }
Additional Reading:
I admit that this doesn’t look so bad. However, when the number of required property values grows and/or our class has other dependencies as well, injecting all of them is cumbersome.
2. We Have to Specify the Property Names More Than Once (or Remember to Use Constants)
If we inject individual property values directly into the beans that need them, and more than one bean (A and B) need the same property value, the first thing that comes to our mind is to specify the property names in both bean classes:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class A { private final String protocol; @Autowired public A(@Value("${app.server.protocol}") String protocol) { this.protocol = protocol.toLowercase(); } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { private final String protocol; @Autowired public B(@Value("${app.server.protocol}") String protocol) { this.protocol = protocol.toLowercase(); } }
This is a problem because
- Because we are humans, we make typos. This is not a huge problem because we will notice it when we start our application. Nevertheless, it slows us down.
- It makes maintenance harder. If we change the name of a property, we have to make this change to the every class that use it.
We can fix this problem by moving the property names to a constant class. If we do this, our source code looks as follows:
public final PropertyNames { private PropertyNames() {} public static final String PROTOCOL = "${app.server.protocol}"; } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class A { private final String protocol; @Autowired public A(@Value(PropertyNames.PROTOCOL) String protocol) { this.protocol = protocol.toLowercase(); } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { private final String protocol; @Autowired public B(@Value(PropertyNames.PROTOCOL) String protocol) { this.protocol = protocol.toLowercase(); } }
This fixes the maintenance problem but only if all developers remember to use it. We can of course enforce this by using code reviews, but this is one more thing that the reviewer must remember to check.
3. Adding Validation Logic Becomes a Problem
Let’s assume that we have two classes (A and B) which need the value of the app.server.protocol property. If we inject this property value directly into the A and B beans, and we want to ensure that the value of that property is ‘http’ or ‘https’, we have to either
- Add the validation logic to both bean classes.
- Add the validation logic to a utility class and use it when we need to validate that the correct protocol is given.
If we add the validation logic to both bean classes, the source code of these classes looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class A { private final String protocol; @Autowired public A(@Value("${app.server.protocol}") String protocol) { checkThatProtocolIsValid(protocol); this.protocol = protocol.toLowercase(); } 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 )); } } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { private final String protocol; @Autowired public B(@Value("${app.server.protocol}") String protocol) { checkThatProtocolIsValid(protocol); this.protocol = protocol.toLowercase(); } 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 )); } } }
This is a maintenance problem because A and B classes contain copy-paste code. We can improve the situation a bit by moving the validation logic to a utility class and using it when we create new A and B objects.
After we have done this, our source code looks as follows:
public final class ProtocolValidator { private ProtocolValidator() {} public static 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 )); } } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class A { private final String protocol; @Autowired public A(@Value("${app.server.protocol}") String protocol) { ProtocolValidator.checkThatProtocolIsValid(protocol); this.protocol = protocol.toLowercase(); } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { private final String protocol; @Autowired public B(@Value("${app.server.protocol}") String protocol) { ProtocolValidator.checkThatProtocolIsValid(protocol); this.protocol = protocol.toLowercase(); } }
The problem is that we still have to remember to invoke this utility method. We can of course enforce this by using code reviews, but once again, this is one more thing that the reviewer must remember to check.
4. We Cannot Write Good Documentation
We cannot write good documentation that describes the configuration of our application because we have to add this documentation to the actual properties files, use a wiki, or write a *gasp* Word document.
Everyone of these options causes problems because we cannot use them at the same time we are writing code that requires property values found from our properties files. If we need to read our documentation, we have to open “an external document” and this causes a context switch that can be very expensive.
Let’s move on and find out how we can solve these problems.
Injecting Property Values Into Configuration Beans
We can solve the problems mentioned earlier by injecting the property values into configuration beans. Let’s start by creating a simple properties file for our example application.
Creating the Properties File
The first thing that we have to do is to create a properties file. The properties file of our example application is called application.properties, and it looks as follows:
app.name=Configuration Properties example app.production.mode.enabled=false app.server.port=8080 app.server.protocol=http app.server.host=localhost
Let’s move on and configure the application context of our example application.
Configuring the Application Context
The application context configuration class of our example application has two goals:
- Enable Spring MVC and import its default configuration.
- Ensure that the property values found from the application.properties file are read and can be injected into Spring beans.
We can fulfil its second second goal by following these steps:
- Configure the Spring container to scan all packages that contain bean classes.
- Ensure that the property values found from the application.properties file are read and added to the Spring Environment.
- Ensure that the ${…} placeholders found from the @Value annotations are replaced with property values found from the current Spring Environment and its PropertySources.
The source code of the WebAppContext class looks as follows:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @ComponentScan({ "net.petrikainulainen.spring.trenches.config", "net.petrikainulainen.spring.trenches.web" }) @EnableWebMvc @PropertySource("classpath:application.properties") public class WebAppContext { /** * Ensures that placeholders are replaced with property values */ @Bean PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } }
Additional Reading:
Our next step is to create the configuration bean classes and inject the property values found from our properties file into them. Let’s find out how we can do that.
Creating the Configuration Bean Classes
Let’s create two configuration bean classes that are described in the following:
- The WebProperties class contains the property values that configures the used protocol, the server’s host, and the port that is listened by the server.
- The ApplicationProperties class contains the property values that configures the name of the application and identifies if the production mode is enabled. It also has a reference to a WebProperties object.
First, we have to create the WebProperties class. We can do this by following these steps:
- Create the WebProperties class an annotate it with the @Component annotation.
- Add final protocol, serverHost, and serverPort fields to the created class.
- Inject the property values into these fields by using constructor injection, and ensure that the value of the protocol field must be either ‘http’ or ‘https’ (ignore the case).
- Add getters that are used the obtain the actual property values.
The source code of the WebProperties class looks as follows:
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.toLowercase(); 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; } }
Second, We have to implement the ApplicationProperties class. We can do this by following these steps:
- Create the ApplicationProperties class and annotate it with the @Component annotation.
- Add final name, productionModeEnabled, and webProperties fields to the created class.
- Inject the property values and the WebProperties bean into the ApplicationProperties bean by using constructor injection.
- Add getters that are used to obtain the field values.
The source code of the ApplicationProperties class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @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; } }
Let’s move on and find out what are the benefits of this solution.
How Does This Help Us?
We have now created the bean classes that contain the property values found from the application.properties file. This solution might seem like an over engineering, but it has the following advantages over the traditional and simple way:
1. We Can Inject Only One Bean Instead of Multiple Property Values
If we inject the property values into a configuration bean, and then inject this configuration bean into the UrlBuilder class by using constructor injection, its source code looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class UrlBuilder { private final WebProperties properties; @Autowired public UrlBuilder(WebProperties properties) { this.properties = properties; } }
As we can see, this makes our code cleaner (especially if we use constructor injection).
2. We Have to Specify the Property Names Only Once
If we inject the property values into the configuration beans, we have to specify the property names only in one place. This means that
- Our code follows the separation of concerns principle. The property names are found from the configuration beans, and the other beans that require this information don’t know where it comes from. They just use it.
- Our code follows the don’t repeat yourself principle. Because the property names are specified only in one place (in the configuration beans), our code is easier to maintain.
Also, (IMO) our code looks a lot cleaner too:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class A { private final String protocol; @Autowired public A(WebProperties properties) { this.protocol = properties.getProtocol(); } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { private final String protocol; @Autowired public B(WebProperties properties) { this.protocol = properties.getProtocol(); } }
3. We Have to Write Validation Logic Only Once
If we inject property values into the configuration beans, we can add the validation logic to the configuration beans, and the other beans don’t have to know about it. This approach has three benefits:
- Our code follows the separation of concerns principle because the validation logic is found from the configuration beans (where it belongs). The other beans don’t have to know about it.
- Our code follows the don’t repeat yourself principle because the validation logic is found from one place.
- We don’t have to remember to call the validation logic when we create new bean objects because we can enforce validation rules when the configuration beans are created.
Also, our source code looks a lot cleaner too:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class A { private final String protocol; @Autowired public A(WebProperties properties) { this.protocol = properties.getProtocol(); } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { private final String protocol; @Autowired public B(WebProperties properties) { this.protocol = properties.getProtocol(); } }
4. We Can Access the Documentation from Our IDE
We can document the configuration of our application by adding Javadoc comments to our configuration beans. After we have done this, we can access this documentation from our IDE when we are writing code that needs these property values. We don’t need to open another file or read a wiki page. We can simply continue writing code and avoid the cost of context switching.
Let’s move on and summarize what we learned from this blog post.
Summary
This blog post has taught us that injecting property values into configuration beans:
- Helps us to follow the separation of concerns principle. The things that concern configuration properties and the validation of the property values are encapsulated inside our configuration beans. This means that the beans that use these configuration beans don’t know where the property values come from or how they are validated.
- Helps us to follow the don’t repeat yourself principle because 1) We have to specify the property names only once and 2) We can add the validation logic to the configuration beans.
- Makes our documentation easier to access.
- Makes our code easier to write, read, and maintain.
However, it doesn’t help us to figure out the runtime configuration of our application. If we need this information, we have to read the properties file found from our server. This is cumbersome.
We will solve this problem in my next blog post.
Reference: | Spring from the Trenches: Injecting Property Values Into Configuration Beans from our JCG partner Petri Kainulainen at the Petri Kainulainen blog. |