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.
META-INF/spring.factories
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:

Table 1. Conditional annotations
Annotation Description

@ConditionalOnClass / @ConditionalOnMissingClass

Matches when specified classes are (or not) on classpath.

@ConditionalOnBean / @ConditionalOnMissingBean

Matches when specified bean is present (absent).

@ConditionalOnWebApplication / @ConditionalOnNotWebApplication

Matches when the application uses WebApplicationContext or not.

@ConditionalOnJava

Matches on Java version the application is running.

@ConditionalOnExpression

Matches on specified SpEL expression.

@ConditionalOnJndi

Matches if one of provided names can be looked up in JNDI.

@ConditionalOnProperty

Matches if a property with specified value can be found in Spring Environment.

@ConditionalOnResource

Matches when specified resource can be found.

Table 2. Configuration order annotations
Annotation Description

@AutoConfigureBefore

Marks annotated auto-configuration class to be resolved before other auto-configuration classes.

@AutoConfigureAfter

Marks annotated auto-configuration class to be resolved after other auto-configuration classes.

@AutoConfigureOrder

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.

To know the exact order of classes execution you can also put a breakpoint in EnableAutoConfigurationImportSelector in a place where AutoConfigurationSorter is used.

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.

— Spring Boot documentation

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:

SwaggerProperties.java
@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:

IDE autocompletion

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).

If you ever feel trapped into the corner, do not hesitate to dive deep into Spring Boot’s codebase. It is very well written and documented. When you get along with it, nothing will seem magical anymore. Don’t also forget about Stack Overflow or Spring Boot Gitter channel. There is a chance that somebody had same problem you are having. If not, don’t be afraid to ask - people there are more than happy to help you.

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.

Neo-Starters are available at Neoteric’s GitHub. However, they are still under heavy development and subject to breaking changes.

Each starter covers individual aspects of application. I will cover their details (whys and hows) in upcoming blog posts…​

Comments