Avoiding a common spring annotation configuration mistake

With annotated based configurations, I have seen this common mistake which will cause multiple instances of a singleton being created. When you have two beans and one bean needs the other, it is easy to construct the two beans as follows:

  1. public class Config {
  2.   @Bean
  3.   public MappingJackson2HttpMessageConverter converter() {
  4.     MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
  5.     converter.setObjectMapper(objectMapper());
  6.     return converter;
  7.   }
  8.  
  9.   @Bean
  10.   public ObjectMapper objectMapper() {
  11.     return new JacksonObjectMapper();
  12.   }
  13. }

If you are referencing this configuration through an Import, like the following, two instances of the JacksonObjectMapper to be created.

  1. @ComponentScan("com.objectpartners.buesing")
  2. @Import(Config.class)
  3. public class ApplicationConfiguration {
  4. }

Once when spring calls objectMapper() and once when spring calls converter(). Instead, method arguments should be used when one bean needs to reference another.

  1. public class Config {
  2.   @Bean
  3.   public MappingJackson2HttpMessageConverter converter(final ObjectMapper objectMapper) {
  4.     MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
  5.     converter.setObjectMapper(objectMapper);
  6.     return converter;
  7.   }
  8.  
  9.   @Bean
  10.   public ObjectMapper objectMapper() {
  11.     return new JacksonObjectMapper();
  12.   }
  13. }

Spring will construct objectMapper and provide that single instance when it calls converter to create the other bean. Furthermore, if there are two ObjectMapper instances, you can qualify which ObjectMapper is needed.

  1. public class Config {
  2.   @Bean
  3.   public MappingJackson2HttpMessageConverter converter(@Qualifier("jsonMapper") final ObjectMapper objectMapper) {
  4.     MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
  5.     converter.setObjectMapper(objectMapper);
  6.     return converter;
  7.   }
  8.  
  9.   @Bean
  10.   public ObjectMapper jsonMapper() {
  11.     return new JacksonObjectMapper();
  12.   }
  13.  
  14.   @Bean
  15.   public ObjectMapper xmlMapper() {
  16.     return new XmlMapper();
  17.   }
  18. }

Most of this is avoid when beans are auto-scanned and references are managed through @Autowire or @Resource. But when that is not possible, never call a @Bean method directly.

Update:
Now if all of your configurations are annotated with @Config spring will create only a single instance of jsonMapper. This is something I didn’t notice until after I wrote this; thanks to Jeff Sheets and Aaron Hanson for helping me uncover this subtle difference. It is good to know how spring works, how it creates beans, and the differences between how configuration is done.

One thought on “Avoiding a common spring annotation configuration mistake

  1. Neil Buesing says:

    Looks like my observations and issues with this unique to how I was creating my spring configurations and profiles (and doing it dynamically). In my coding examples (above) I just used the @Configuration trying to simplify the examples. However, that nuance creates a big different.

    In the code below, the following is printed

    Creating jsonMapper
    Creating xmlMapper
    Creating jsonMapper
    Running.

    If I add @Configuration to the MappingConfiguration, I just get

    Creating jsonMapper
    Creating xmlMapper
    Running.

    public class Bootstrap {
    public static void main(final String args[]) {
    final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MappingConfiguration.class);
    context.refresh();
    System.out.println(“Running.”);
    }
    }

    package com.objectpartners.buesing.config;

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

    public class MappingConfiguration {

    @Bean
    public MappingJackson2HttpMessageConverter converter(@Qualifier(“jsonMapper”) final ObjectMapper objectMapper) {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(jsonMapper());
    return converter;
    }

    @Bean
    public ObjectMapper jsonMapper() {
    System.out.println(“Creating jsonMapper”);
    return new ObjectMapper();
    }

    @Bean
    public ObjectMapper xmlMapper() {
    System.out.println(“Creating xmlMapper”);
    return new XmlMapper();
    }
    }

    My example is more of nested configuration where I include them, like the following and I will get two instances, however, if I don’t import MappingConfiguration and use the @Component on MappingConfiguration, I will only get one

    @ComponentScan(“com.objectpartners.buesing.config”)
    @Import(MappingConfiguration.class)
    public class ApplicationConfiguration {
    }

    package com.objectpartners.buesing.config;

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

    public class MappingConfiguration {

    @Bean
    public MappingJackson2HttpMessageConverter converter(@Qualifier(“jsonMapper”) final ObjectMapper objectMapper) {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(jsonMapper());
    return converter;
    }

    @Bean
    public ObjectMapper jsonMapper() {
    System.out.println(“Creating jsonMapper”);
    return new ObjectMapper();
    }

    @Bean
    public ObjectMapper xmlMapper() {
    System.out.println(“Creating xmlMapper”);
    return new XmlMapper();
    }
    }

    So while it is possible to use the method with @Configuration the solution to pass the bean in with the method parameter seems to always work (as expected).

Leave a Reply

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

*

*