loading...

Spring Boot Part 6: Auto Configuration and Starters

allenball profile image Allen D. Ball Originally published at blog.hcf.dev ・5 min read

Spring Boot Part 6: Auto Configuration and Starters

Introduction

Spring Boot allows for the creation of "starters:" Convenient dependency descriptors that often provide some specific but complex functionality. Examples from Spring Boot discussed in this series of articles include spring-boot-starter-web, spring-boot-starter-thymeleaf, and spring-boot-starter-actuator.

This article describes some reusable code targeted for development and testing (a REST controller @ /jig/bean/{name}.json and /jig/bean/{name}.xml) and the steps necessary to create a Spring Boot starter. It also describes a starter for the embedded MySQL server process described in "Spring Embedded MySQL Server".

Complete javadoc is provided.

Theory of Operation

As discussed in part 1, the spring-boot-starter-actuator may be configured. One of its sevices may be used to get the list (with attributes) of the configured beans. Sample partial output below:

$ curl -is -X GET http://localhost:5001/actuator/beans/
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Sun, 19 Jul 2020 20:22:10 GMT

{
  "contexts" : {
    "application" : {
      "beans" : {
        ...
        "discoveryService" : {
          "scope" : "singleton",
          "type" : "upnp.DiscoveryService",
          "resource" : "file [/Users/ball/upnp-media-server/target/classes/upnp/DiscoveryService.class]",
          "dependencies" : [ "mediaServer" ]
        },
        ...
      },
      "parentId" : null
    }
  }
}

The ball-spring-jig-starter will provide the following two REST APIs to retrieve a specific bean's value and demonstrate serialization to JSON or XML as appropriate. JSON partial output:

$ curl -is -X GET http://localhost:5000/jig/bean/discoveryService.json
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 19 Jul 2020 20:23:38 GMT

{
  "uuid:00000000-0000-1010-8000-0024BEF18BCC" : {
    "expiration" : 1595191949391,
    "ssdpmessage" : {
      "params" : { },
      "entity" : null,
      "locale" : null,
      "inetAddress" : "10.0.1.9",
      "st" : "uuid:00000000-0000-1010-8000-0024BEF18BCC",
      "location" : "http://10.0.1.9:52323/dmr.xml",
      "usn" : "uuid:00000000-0000-1010-8000-0024BEF18BCC",
      "protocolVersion" : {
        "protocol" : "HTTP",
        "major" : 1,
        "minor" : 1
      },
      ...
    },
    ...
  },
  ...
}

And corresponding XML output:

$ curl -is -X GET http://localhost:5000/jig/bean/discoveryService.xml
HTTP/1.1 200
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Sun, 19 Jul 2020 20:29:47 GMT

<DiscoveryService>
  <uuid:00000000-0000-1010-8000-0024BEF18BCC>
    <expiration>1595192286661</expiration>
    <ssdpmessage>
      <params/>
      <entity/>
      <locale/>
      <inetAddress>10.0.1.9</inetAddress>
      <st>uuid:00000000-0000-1010-8000-0024BEF18BCC</st>
      <location>http://10.0.1.9:52323/dmr.xml</location>
      <usn>uuid:00000000-0000-1010-8000-0024BEF18BCC</usn>
      <protocolVersion>
        <protocol>HTTP</protocol>
        <major>1</major>
        <minor>1</minor>
      </protocolVersion>
      ...
    </ssdpmessage>
  </uuid:00000000-0000-1010-8000-0024BEF18BCC>
  ...
</DiscoveryService>

The implementation is described in the next section.

Implementation

The steps to create the "jig" starter:

  1. Implement the REST controller

  2. Create a project and POM for the starter artifact

  3. Create the auto-configuration class(es)

  4. Link the auto-configuration class(es) into the starter's META-INF/spring.factories resource1

The BeanRestController implementation is shown below.

ball.spring.jig.BeanRestController
@RestController
@RequestMapping(value = { "/jig/bean/" })
@ResponseBody
@NoArgsConstructor @ToString @Log4j2
public class BeanRestController implements ApplicationContextAware {
    private ApplicationContext context = null;

    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }

    @RequestMapping(method = { GET }, value = { "{name}.json" }, produces = APPLICATION_JSON_VALUE)
    public Object json(@PathVariable String name) throws Exception {
        return context.getBean(name);
    }

    @RequestMapping(method = { GET }, value = { "{name}.xml" }, produces = APPLICATION_XML_VALUE)
    public Object xml(@PathVariable String name) throws Exception {
        return context.getBean(name);
    }

    @ExceptionHandler({ NoSuchBeanDefinitionException.class, NoSuchElementException.class })
    @ResponseStatus(value = NOT_FOUND, reason = "Resource not found")
    public void handleNOT_FOUND() { }
}

Its implementation is straightforward: A method each to look-up the requested bean and then serialize to JSON and XML.

In the project for the starter, add the AutoConfiguration class.

ball.spring.jig.autoconfigure.AutoConfiguration
@Configuration
@ConditionalOnClass({ BeanRestController.class })
@Import({ BeanRestController.class })
@NoArgsConstructor @ToString @Log4j2
public class AutoConfiguration {
}

It is critical that @Configuration classes are added through @Import annotations and not @ComponentScan: Neither the starter author nor the integrator will be able to predict what components will or will not be included in a scan.

It is a good practice to include a "conditional-on" annotation (e.g., @ConditionalOnClass) to test any required dependency software has been configured and/or is on the class path.

Finally, META-INF/spring.factories must be configured in the starter JAR to notify Spring Boot to add the AutoConfiguration.

META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration: ball.spring.jig.autoconfigure.AutoConfiguration

Creating a starter for the embedded MySQL process described in "Spring Embedded MySQL Server" is equally straightforward. Its AutoConfiguration class is shown below:

ball.spring.mysqld.autoconfigure.AutoConfiguration
@Configuration
@ConditionalOnClass({ MysqldConfiguration.class })
@Import({ EntityManagerFactoryComponent.class, MysqldConfiguration.class })
@NoArgsConstructor @ToString @Log4j2
public class AutoConfiguration {
}

With it corresponding META-INF/spring.factories:

META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration: ball.spring.mysqld.autoconfigure.AutoConfiguration

Summary

Creating a Spring Boot starter is straightfoward: Create a project/POM to host the starter's dependencies and auto-configuration class(es), add the annotated auto-configuration class(es), and configure the starter JAR's META-INF/spring.factories with the auto-configuration class(es). That starter's functionality can then be added to a Spring Boot application simply by including a single dependency in the application POM.


  1. Many Spring Boot components provide separate auto-configuration and starter artifacts to support all use cases. The author feels these example implementations do not benefit from separate auto-configuration artifacts. 

Discussion

pic
Editor guide