Overview

Boot your own infrastructure – Extending Spring Boot in five steps

2 Comments

Writing your own Spring Boot Starter can be a very reasonable thing to do – not every technology is covered by the provided starters, and maybe you have your own framework stuff you wanna boot automatically. We’ll take a look at the possible reasons later in this post, but now it’s time for the how, not the why. So, what are the main steps you need to take when writing your own starter?

  1. Choose your base from existing starters.
  2. Write your own configuration and let it be added to the ApplicationContext automatically.
  3. Make your starter configurable by using properties.
  4. Make your starter extensible by using overridable default implementations.
  5. Make your starter classpath- and resources-aware.

We wanted to have our own batch server with http endpoints responsible for starting and stopping jobs and doing monitoring stuff, and since until now there’s only a standalone runner for Spring Batch jobs (spring-boot-starter-batch), we decided to write our own Spring Boot Starter for it (spring-boot-starter-batch-web). It turned out to be very easy. I’ll walk through the five steps above with some examples from that starter, some code from other sources.

Choose your base from existing starters

Probably you don’t wanna start on a green field. You may – but in most cases it doesn’t make sense. We wanted to have a web application, so we added spring-boot-starter-web to our starter’s dependencies. You’ll get a standard Spring MVC configuration and the embedded Tomcat. Then we wanted to have batch capabilities, so we added spring-boot-starter-batch. Since we wanted a configurable, pooled DataSource, we also added spring-boot-starter-jdbc, and for monitoring we added spring-boot-starter-actuator. The dependencies section of our POM looks like this:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
    <version>${spring.boot.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>${spring.boot.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${spring.boot.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>${spring.boot.version}</version>
</dependency>

Write your own configuration and let it be added to the ApplicationContext automatically

Until now we have a lot of stuff pre-configured only by dragging in the other starters, but we don’t have our own configuration added yet. We wanted to do some stuff there – adding REST endpoints, searching for batch job configurations and adding them to the ApplicationContext, adding listeners to the jobs and so on. So we wrote the configuration, split it up in several configuration classes and had one main entree configuration class: BatchWebAutoConfiguration. We wanted it to be picked up by Spring Boot’s auto-configuration capabilities, and for that you have to add a spring.factories file under src/main/resources/META-INF with the following content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration= de.codecentric.batch.configuration.BatchWebAutoConfiguration.

Make your starter configurable by using properties

Spring Boot has a quite complete concept of reading and using properties (Reference documentation), and of course you can use those properties in your configuration as well. We, for example, added a ThreadPoolTaskExecutor to the configuration for starting jobs asynchronously. We wanted to make the number of threads in that pool configurable. It’s quite easy, you just use @Value to read the property. In our case we added the default of 5, so that the property can be left out without problems. If somebody wants a pool bigger than 5, he just adds the property to the application.properties file. Take a look at ConfigurationProperties for more sophisticated ways of dealing with properties.

@Value("${batch.max.pool.size:5}")
private int batchMaxPoolSize;;
 
@Bean
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setMaxPoolSize(batchMaxPoolSize);
    taskExecutor.afterPropertiesSet();
    return taskExecutor;
}

Make your starter extensible by using overridable default implementations

I’ll distinguish between business logic beans and configuration classes here. A simple way of allowing to inject own business logic is providing an interface, autowire an implementation of this interface, but don’t require it, and provide a default implementation if there’s no other.
In our starter we have a log file for each batch job run, and we wanted the file name to be configurable. It should be possible to use every information about the job to build up the file name, so we introduced an interface with one method returning a string that can use the JobExecution object to build it up.

public interface JobLogFileNameCreator {
    public String createJobLogFileName(JobExecution jobExecution);
}

Then in a component where we want to use the log file name, we create a default implementation and let it possibly overridden like this:

private JobLogFileNameCreator jobLogFileNameCreator = new DefaultJobLogFileNameCreator();
 
@Autowired(required=false)
public void setJobLogFileNameCreator(JobLogFileNameCreator jobLogFileNameCreator) {
    this.jobLogFileNameCreator = jobLogFileNameCreator;
}

You may also use the annotation @ConditionalOnMissingBean for a similar functionality, I would recommend it for interfaces that are not that simple and for configuration classes. When using the Spring Batch configuration capabilities, you may add a bean implementing the interface BatchConfigurer for configuring batch resources. We have our own implementation of BatchConfigurer (TaskExecutorBatchConfigurer), because we want to start batch jobs asynchronously. We added @ConditionalOnMissingBean(BatchConfigurer.class) to the class declaration, so that somebody using our starter still has the possibility to add his own BatchConfigurer implementation, and when he does, our implementation will silently step aside.

@ConditionalOnMissingBean(BatchConfigurer.class)
@Configuration
public class TaskExecutorBatchConfigurer implements BatchConfigurer {

Make your starter classpath- and resources-aware

If you want, you may even add configurations if some classes are on the class path or if some resources are available. We didn’t use it in our starter by now, so the following example is taken from Spring Boot’s BatchAutoConfiguration:

@ConditionalOnClass(name = "javax.persistence.EntityManagerFactory")
@ConditionalOnMissingBean(BatchConfigurer.class)
@Configuration
protected static class JpaBatchConfiguration {
    // The EntityManagerFactory may not be discoverable by type when this condition
    // is evaluated, so we need a well-known bean name. This is the one used by Spring
    // Boot in the JPA auto configuration.
    @Bean
    @ConditionalOnBean(name = "entityManagerFactory")
    public BatchConfigurer jpaBatchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
        return new BasicBatchConfigurer(dataSource, entityManagerFactory);
    }
 
    @Bean
    @ConditionalOnMissingBean(name = "entityManagerFactory")
    public BatchConfigurer basicBatchConfigurer(DataSource dataSource) {
        return new BasicBatchConfigurer(dataSource);
   }
}

Here they use the annotation @ConditionalOnClass(name = "javax.persistence.EntityManagerFactory") to check if someone added JPA to the classpath, and if that’s the case, the EntityManagerFactory, if it exists, is used in the batch configuration.

Stuck with Websphere in production and stages, but wanna use embedded Tomcat for fast development? Just check for some Websphere classes on the classpath, and if you find them, get your DataSources and transaction manager via JNDI from the application server. It’ll be transparent for the developer.

And hey, there are more @Conditional annotation, so be sure to check out @ConditionalOnExpression, @ConditionalOnMissingClass, @ConditionalOnResource, @ConditionalOnWebApplication, @ConditionalOnNotWebApplication.

Why should you build your own starter?

So, after the how let’s talk about the why now. I see four possible reasons:

  1. Missing support for a publicly available technology
  2. Missing support for a not publicly available / proprietary technology
  3. Specific configurations / defaults for a supported technology
  4. Adding common functionality to each application

Missing support for a publicly available technology

There are already a lot of Spring Boot starters and auto configurations, but of course it may happen that you have to deal with a technology that isn’t supported by now and that has a publicly available interface. When writing a starter for that technology you should consider contributing it to Spring Boot core.

Missing support for a not publicly available / proprietary technology

A lot of companies are using / developing their own proprietary technology / frameworks. With your own Spring Boot starter for this stuff you’re able to start it up in a modular way.

Specific configurations / defaults for a supported technology

You often use a certain technology in the same way in every application in your company. For example, if you have an LDAP server running, you are maybe using Spring Security to talk to it. The configuration for that can be hidden in an own Spring Boot starter so that everybody in the company can easily use it. There are a lot of examples for this type of reason, for example configurations for resources like DataSources, JMS stuff etc. It’s about enabling the companies’ programmers to work on the business stuff, not on configurations.

Adding common functionality to each application

This one has to be handled with care, because you don’t want to share too much code. We added http endpoints to our Spring Boot starter because we wanted the same http interface for each batch application, and we added a few more things that are valuable in every batch application.

Conclusion

We were quite surprised how easy it was to build your own Spring Boot Starter, and we see a lot of potential in it not only for the enterprise world. The Spring Boot eco system is growing fast, so there is a lot you can use out-of-the-box, but if it’s not, you can easily add it yourself. And it’s not only valuable for technical configurations, you may easily write your own server for your own type of business application.

Kommentare

Comment

Your email address will not be published. Required fields are marked *