Last Update: 24.05.2017. By Jens in Spring Boot
Spring Boot provides a good set of starters for the popular open source frameworks used in modern Spring applications. However, it is not limited to that, and we are not at the mercy of the Spring Boot gods. We can create our own starters.
Maybe you have an internal library at work or an open source one which is commonly used throughout your application landscape. It might be useful to create a Spring Boot starter for it if you are going to use it in multiple Spring Boot application.
In this article, we’ll take a closer look and actually implement a first custom starter.
A Spring Boot starter consists of two modules:
Let’s look at the mechanism called autoconfiguration. It sounds more complex than it actually is.
On startup Spring Boot scans the classpath for all files named spring.factories located in a META-INF directory and will process it. These files contain the single key org.springframework.boot.autoconfigure.EnableAutoConfiguration= with the value set to a list of regular @Configurtion classes.
It will check each @Configurtion if it should be included and eventually use it.
You can add conditions to it when Spring Boot should consider and use it; like the @ConditionalOnClass which adds a condition that your @Configurtion shall only be included if the specified class is present in the classpath.
For this example, I am using my request-logging module which I extracted from the sample code of my first Spring Boot Book, and we turn that into a Spring Boot starter.
The module consists of a ServletFilter which will use the value in a specific HTTP header and the set it in the MDC (Mapped Diagnostic Context) of logback. If the header is not present or is empty, we generate an UUID to identify the request.
You can find the starter module on GitHub.
Spring Boot recommends a naming schema for the modules in the form
Our module will be named request-logging-spring-boot-autoconfigure and contain two classes - a@Confiration_ and a property class to expose some properties which can be configured in the application.properties.
Let’s start with the pom.
<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>
<parent>
<groupId>de.codeboje</groupId>
<artifactId>request-logging-spring-boot-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>request-logging-spring-boot-autoconfigure</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>de.codeboje</groupId>
<artifactId>request-logging</artifactId>
<version>${request-logging.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
In the example, I use a Maven multimodule project. The parent pom just contains the compiler source settings and defines the version of Spring Boot we are using. You can find it in the source code.
The first two dependencies are for using Spring Boot and the auto configuration classes.
spring-boot-configuration-processor is a tool that creates a meta data file from our @ConfigurationProperties so that IDEs like STS support auto-completion in the property files.
RequestLoggingProperties hold to variables, one for the header name (requestHeaderId) and one for variable name (logIdentifier) in logback patterns.
We also use a prefix for our properties. They are accessible as
Let’s implement it.
@ConfigurationProperties(prefix = "codeboje.requestlogging")
public class RequestLoggingProperties {
/**
* Name of the HTTP Header containing the request Id. defaults to "X-REQUEST-ID"
*/
private String requestHeaderId;
/**
* Name of the logback variable containing the request Id value; defaults to "requestId"
*/
private String logIdentifier;
//standard setter and getter
}
Next, we define our RequestLoggingAutoConfiguration. We enable properties with @EnableConfigurationProperties and add two conditions:
Let’s look at the code.
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(RequestContextLoggingFilter.class)
@EnableConfigurationProperties(RequestLoggingProperties.class)
public class RequestLoggingAutoConfiguration {
@Autowired
private RequestLoggingProperties requestLoggingProperties;
@Bean
@Order(1)
@ConditionalOnMissingBean
public RequestContextLoggingFilter requestContextLoggingFilter() {
return new RequestContextLoggingFilter(requestLoggingProperties.getRequestHeaderId(),
requestLoggingProperties.getLogIdentifier());
}
}
If our configuration is included, it will initialize our filter bean, if the @ConditionalOnMissingBean is valid. Only when no bean of our time is already present in the application context, our bean gets initialized.
In it, we create the instance of RequestContextLoggingFilter and pass the properties along. Null values are handled in the filter at preset with defaults.
As with the autoconfigure module, Spring Boot also recommends a naming schema
for the starter in the form
We name ours request-logging-spring-boot-starter.
<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>
<parent>
<groupId>de.codeboje</groupId>
<artifactId>request-logging-spring-boot-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>request-logging-spring-boot-starter</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>de.codeboje</groupId>
<artifactId>request-logging-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>de.codeboje</groupId>
<artifactId>request-logging</artifactId>
<version>${request-logging.version}</version>
</dependency>
</dependencies>
</project>
The starter only includes all active dependencies for making this starter working. Otherwise, it is a codeless module.
Clone the repo from my book and replace in the comment-store the dependency of the _logging_module with our freshly created starter.
Second, adjust the MDC variable in application.properties with
codeboje.requestlogging.logIdentifier=SBBRequestUUID
Start the application, fire up Postman or alike and make a GET to http://localhost:8080/list/product4711 using admin/mypassword.
On the console, you should see a log output with a UUID it. Try setting the X-REQUEST-ID header to another value, and you’ll see that in the output.
At first, Spring Boot starters and auto configuration feel like magic fairy dust. But when you take a closer look it is a well thought out mechanism for rapidly making your library working under Spring Boot.