Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Fixing My Own Spring Boot Starter Demo

DZone's Guide to

Fixing My Own Spring Boot Starter Demo

When creating Spring configurations, it's sometimes necessary to create static inner classes to prevent cycles and bean conflicts. This post describes the why and how.

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

For one year or so, I've been trying to show the developer community that there’s no magic involved in Spring Boot, just straightforward software engineering. This is achieved with blog posts and conference talks. At jDays, Stéphane Nicoll was nice enough to attend my talk and pointed out an issue in the code. I didn’t fix it then, and it came back to bite me last week during a Pivotal webinar. Since a lesson learned is only as useful as its audience, I’d like to share my mistake with the world, or at least with you, dear readers.

The context is that of a Spring Boot starter for the XStream library: XStream is a simple library to serialize objects to XML and back again.

  1. (De)serialization capabilities are implemented as instance methods of the XStream class.
  2. Customization of the (de)serialization process is implemented through converters registered in the XStream instance.

The goal of the starter is to ease the usage of the library. For example, it creates an XStream instance in the context if there’s none already:

@Configuration
public class XStreamAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(XStream.class)
    public XStream xStream() {
        return new XStream();
    }
}

Also, it will collect all custom converters from the context and register them in the existing instance:

@Configuration
public class XStreamAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(XStream.class)
    public XStream xStream() {
        return new XStream();
    }

    @Bean
    public Collection<Converter> converters(XStream xstream, Collection<Converter> converters) {
        converters.forEach(xstream::registerConverter);
        return converters;
    }
}

The previous snippet achieves the objective, as Spring obediently injects both xstream and converters dependency beans in the method. Yet, a problem happened during the webinar demo: can you spot it?

If you answered about an extra converters bean registered into the context, you are right. But the issue occurs if there’s another converters bean registered by the client code and it’s of different type, i.e. not a Collection. Hence, registration of converters must not happen in beans methods, but only in a @PostConstruct one.

  • Option 1
    The first option is to convert the @Bean to @PostConstruct.
    @Configuration
    public class XStreamAutoConfiguration {
    
      @Bean
      @ConditionalOnMissingBean(XStream.class)
      public XStream xStream() {
          return new XStream();
      }
    
      @PostConstruct
      public void register(XStream xstream, Collection<Converter> converters) {
          converters.forEach(xstream::registerConverter);
      }
    }
    Unfortunately, @PostConstruct doesn’t allow for methods to have arguments. The code doesn’t compile.
  • Option 2
    The alternative is to inject both beans into attributes of the configuration class, and use them in the @PostConstruct-annotated method.
    @Configuration
    public class XStreamAutoConfiguration {
    
      @Autowired
      private XStream xstream;
    
      @Autowired
      private Collection<Converter> converters;
    
      @Bean
      @ConditionalOnMissingBean(XStream.class)
      public XStream xStream() {
          return new XStream();
      }
    
      @PostConstruct
      public void register() {
          converters.forEach(xstream::registerConverter);
      }
    }   

This compiles fine, but Spring enters a cycle trying both to inject the XStream instance into the configuration and to create it as a bean at the same time.

  • Option 3
    The final (and only valid) option is to learn from the master and use another configuration class- a nested one. Looking at Spring Boot code source, it’s obviously a pattern. The final code looks like this:
    @Configuration
    public class XStreamAutoConfiguration {
    
      @Bean
      @ConditionalOnMissingBean(XStream.class)
      public XStream xStream() {
          return new XStream();
      }
    
      @Configuration
      public static class XStreamConverterAutoConfiguration {
    
          @Autowired
          private XStream xstream;
    
          @Autowired
          private Collection<Converter> converters;
    
          @PostConstruct
          public void registerConverters() {
              converters.forEach(converter -> xstream.registerConverter(converter));
          }
      }
    }

The fixed code is available on Github. Grab the webinar while it’s hot.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
spring boot ,spring auto configuration ,spring configuration ,spring boot starter ,java

Published at DZone with permission of Nicolas Frankel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}