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.
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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;
}
}
|
If we inject these property values by using constructor injection and the Environment class, the source code of the UrlBuilder class looks as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
);
}
}
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public
final
class
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.protocolproperty. 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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 Bobjects.
After we have done this, our source code looks as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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:
1
2
3
4
5
6
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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
static
PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
return
new
PropertySourcesPlaceholderConfigurer();
}
}
|
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 aWebProperties 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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 theapplication.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 theUrlBuilder class by using constructor injection, its source code looks as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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.
P.S. You can get the example application of this blog post from Github.