Don't fork me!

Travis CI status: Build Status

1. Introduction

spring-boot magic…​

Read github pages reference documentation

generated by generator-jvm yeoman generator (java-spring-boot)

2. Implementation

To create starter we need minimal projects structure:

  • service with functionality we wanna expose / integrate: hello-service

  • module which will be automatically configure that service as far starter was added as a dependency: hello-service-autoconfigure

  • starter module, containing everything needed (hello-service + auto-config + other dependencies): spring-boot-starter-hello

we will be using and testing out starter in project: spring-boot-starter-hello-tests

2.1. hello-service

First, create service with functionality you wanna to share with the world

mkdir hello-service
touch hello-service/pom.xml
# ...

That module contains HelloService we wanna expose:

HelloService interface:
/**
 * Super complex greeting service!
 */
public interface HelloService {

  /**
   * Some javadoc...
   * @param whom who, we salute?
   * @return greeting message
   */
  String sayHello(final String whom);
}
HelloServiceImpl interface:
@RequiredArgsConstructor
public class HelloServiceImpl implements HelloService {

  final String prefix;
  final String suffix;

  @Override
  public String sayHello(String whom) {
    return format("%s %s%s", prefix, whom, suffix);
  }
}

2.2. hello-service-autoconfigure

Next, crete auto-configuration module for hello-service

mkdir hello-service-autoconfigure
touch hello-service-autoconfigure/pom.xml
# ...

That module will depends on hello-service module and spring-boot auto-configuration dependencies

file hello-service-autoconfigure/pom.xml:
  <dependencies>
    <!-- compile -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>

    <dependency>
      <optional>true</optional>
      <groupId>com.github.daggerok</groupId>
      <artifactId>hello-service</artifactId>
    </dependency>

    <!-- optional -->
    <dependency>
      <optional>true</optional>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>

    <!-- test -->
    <dependency>
      <scope>test</scope>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
  </dependencies>

Here we are creating auto-configuration which is basically will be picked up if HelloService class in classpath

file ./hello-service-autoconfigure/src/main/java/com/github/daggerok/hello/config/HelloServiceAutoConfiguration.java:
/**
 * Apply auto configuration only if {@link HelloService} class is in classpath.
 */
@Configuration
@RequiredArgsConstructor
@ConditionalOnClass(HelloService.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public HelloService helloService(final HelloProperties properties) {
    final Hello prop = properties.getHello();
    return new HelloServiceImpl(prop.getPrefix(), prop.getSuffix());
  }
}

To make it happens, we need provide spring.factories file, which spring-boot will identify and create needed auto-configurations for us if starter in classpath according to conditions

file ./hello-service-autoconfigure/src/main/java/com/github/daggerok/hello/config/HelloServiceAutoConfiguration.java:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.daggerok.hello.config.HelloServiceAutoConfiguration

2.3. spring-boot-starter-hello

Now we are ready to go create starter itself

mkdir spring-boot-starter-hello
touch spring-boot-starter-hello/pom.xml
# ...

That starter will define in dependencies everything needed

file spring-boot-starter-hello/pom.xml:
  <artifactId>spring-boot-starter-hello</artifactId>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>com.github.daggerok</groupId>
      <artifactId>hello-service</artifactId>
    </dependency>

    <dependency>
      <groupId>com.github.daggerok</groupId>
      <artifactId>hello-service-autoconfigure</artifactId>
    </dependency>
  </dependencies>
that module also has auto-configuration HelloStarterAutoConfiguration.java:
Unresolved directive in index.adoc - include::../spring-boot-starter-hello/src/main/java/com/github/daggerok/starter/HelloStarterAutoConfiguration.java[tags=content]
and spring.factories file:
Unresolved directive in index.adoc - include::../spring-boot-starter-hello/src/main/resources/META-INF/spring.factories[]
it’s very important test your auto configuration if it’s properly works:
Unresolved directive in index.adoc - include::../spring-boot-starter-hello/src/test/java/com/github/daggerok/starter/HelloStarterAutoConfigurationTests.java[tags=content]
Here we are testing that HelloService bean was properly instantiated and found in application context.

3. Testing

To test starter, all you need to do is:

1. create module for it:
mkdir spring-boot-starter-hello-tests
touch spring-boot-starter-hello-tests/pom.xml
# ...
2. add to your pom.xml started:
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>com.github.daggerok</groupId>
      <artifactId>spring-boot-starter-hello</artifactId>
    </dependency>
  </dependencies>
3. and use it like any other spring-boot starters:
@Log4j2
@SpringBootApplication
public class HelloStarterTestApplication {
  public static void main(String[] args) {
    final ConfigurableApplicationContext context = SpringApplication.run(HelloStarterTestApplication.class, args);
    final HelloService helloService = context.getBean(HelloService.class);
    log.info(() -> helloService.sayHello("ololo-trololo"));
  }
}