Get along with your Spring Boot Starter
At Neoteric we have finally decided to move forward with our technology stack. From custom-tailored mix-up of Jetty, Jersey, Guava and Jackson (it all started before Dropwizard came along), going through different versions of these (during our journey we migrated from Jersey 1.x to 2.x, Jackson 1.x to 2.x, Guava 3.0 to 4.0 etc.), we’ve reached to the Spring island and we are currently learning language of the autochthons.
We have picked Spring ecosystem as its most widely known Java solution, with great community support. But, most importantly, because we fell in love with Spring Boot and Spring Cloud capabilities.
Spring Boot magic
There is so much hype on Spring Boot lately and so many Spring Boot articles, presentations etc., that I want to focus only on essential parts - required to understand how to write your own starter.
The whole 'magic', as often expressed when explaining Spring Boot, mostly boils down to scanning application classpath for particular classes
available and providing preconfigured Beans
for them. Treat it as a convention over configuration concept applied to the application level.
I think that, best way to understand it is to learn by example. Hence, let’s summon and examine Spring Boot’s MongoAutoConfiguration
class.
@Configuration (1)
@ConditionalOnClass(MongoClient.class) (2)
@EnableConfigurationProperties(MongoProperties.class) (3)
@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDbFactory") (4)
public class MongoAutoConfiguration {
@Autowired (5)
private MongoProperties properties;
@Autowired(required = false)
private MongoClientOptions options;
@Autowired
private Environment environment;
private MongoClient mongo;
@PreDestroy
public void close() {
if (this.mongo != null) {
this.mongo.close();
}
}
@Bean
@ConditionalOnMissingBean (6)
public MongoClient mongo() throws UnknownHostException {
this.mongo = this.properties.createMongoClient(this.options, this.environment);
return this.mongo;
}
}
1 | Standard Spring @Configuration annotation. What it makes an auto-configuration class is an entry in
META-INF/spring.factories file. |
2 | One of the Conditional type annotations. This one means that the MongoAutoConfiguration class will be processed only
if MongoClient class is available on classpath in runtime. |
3 | Registers a properties class, so it can be used in the auto-configuration. More on this here. |
4 | Another condition, which has to be met, to start processing the class. |
5 | Auto-configuration class is a standard Spring configuration class, hence ability to inject beans is quite obvious. |
6 | If you put @ConditionalOnMissingBean directly on a bean declaration, bean type will be inferred. |
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
To sum up: MongoAutoConfiguration
will be performed when MongoClient
class is available at runtime
(happens when mongo-java-driver
dependency is added) and if no Bean
of type MongoDbFactory
is found.
The latter implies, that you can define Bean
of such type in your application and handle MongoDB scaffolding yourself.
In that case MongoAutoConfiguration
will be ignored by Spring Boot. In our example, MongoDB set up is moved into
property class itself: this.properties.createMongoClient(this.options, this.environment);
There are some more annotation elements that may be helpful designing your starter:
Annotation | Description |
---|---|
Matches when specified classes are (or not) on classpath. |
|
Matches when specified bean is present (absent). |
|
|
Matches when the application uses |
Matches on Java version the application is running. |
|
Matches on specified SpEL expression. |
|
Matches if one of provided names can be looked up in JNDI. |
|
Matches if a property with specified value can be found in Spring Environment. |
|
Matches when specified resource can be found. |
Annotation | Description |
---|---|
Marks annotated auto-configuration class to be resolved before other auto-configuration classes. |
|
Marks annotated auto-configuration class to be resolved after other auto-configuration classes. |
|
Allows auto-configuration classes to be resolved in a specific order without knowledge of each other. |
To help solve potential auto-configuration problems just run your Spring Boot application with a --debug
switch
(or -Ddebug
system property). It will output nice Auto Configuration Report to the console. It contains all found
auto-configuration classes, divided into 4 sections:
-
Positive matches
-
Negative matches
-
Exclusions
-
Unconditional classes
When spring-boot-starter-actuator
dependency is added to your project, there is an alternative way to get information about
auto-configuration classes by calling /autoconfig
endpoint.
Custom Starter guidelines
As Spring Boot documentation states, you should not begin your starter name with spring-boot-* Especially when, you are making some technology "Spring Bootable". Keep in mind that it may get official support in the future.
Spring Boot structures its Starters into two parts:
The autoconfigure module that contains the auto-configuration code.
The starter module that provides a dependency to the autoconfigure module as well as the library and any additional dependencies that are typically useful.
For custom Starters there is also a suggestion that both these modules can be combined into a single one named simply starter. That’s how we approach creating our Starters, and couldn’t come up with a good reason to split.
No matter, if you want to create a Starter to support a technology, or to encapsulate common behaviour among your
applications, always try to provide sensible defaults with an option of extensive configuration. To achieve that you can
provide configuration keys in your Starter. To do that, mark a simple POJO class with a @ConfigurationProperties
annotation, providing prefix for your properties (again, do not make same prefixes as Spring Boot is using:
spring, server, etc). Example property class:
@ConfigurationProperties("neostarter.swagger")
public class SwaggerProperties {
/**
* Enable swagger switch
*/
private boolean enabled = true;
/**
* Should generated swagger.json be formatted
*/
private boolean prettyPrint = true;
/**
* Provides the version of the application API
*/
private String version = "1";
/**
* The contact information for the exposed API.
*/
private String contact = "backend@neoteric.eu";
/**
* The transfer protocol for the operation. Values MUST be from the list: "http", "https", "ws", "wss".
*/
private String[] schemes = {"http"};
/**
* The title of the application.
*/
private String title;
... (more fields, getters and setters) ...
}
You can then use the configuration properties as a conditions for your auto-configuration (via @ConditionalOnProperty
)
or / and register it with @EnableConfigProperties
and inject into auto-configuration class. As said
here
you can use annotation processing to help IDE assist on your custom properties auto-completion:
To achieve that, add an optional dependency (in Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
and it will pick up your fields Javadoc set up and provide as an auto-completion description.
Another important thing Spring Boot provides is that you don’t have to worry about dependencies version management. Spring Boot picks dependencies (and its versions) that works well with each other. Additional value your Starter brings is that you have already specified libraries versions you use and the end-user don’t have to think about that anymore (and potential version conflicts).
Neo-Starters
At Neoteric we have worked out some practices and standards for our services. Hence, came up with an idea that we can bend
Spring Boot to our needs even more by creating our own set of starters. They would encapsulate these practices and enable
us to develop new applications faster and with even less boilerplate code needed. We called them neo-starters
.
Each starter covers individual aspects of application. I will cover their details (whys and hows) in upcoming blog posts…