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:
public class Config {
@Bean
public MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
return converter;
}
@Bean
public ObjectMapper objectMapper() {
return new JacksonObjectMapper();
}
}
If you are referencing this configuration through an Import, like the following, two instances of the JacksonObjectMapper to be created.
@ComponentScan("com.objectpartners.buesing")
@Import(Config.class)
public class ApplicationConfiguration {
}
Once when spring calls objectMapper() and once when spring calls converter(). Instead, method arguments should be used when one bean needs to reference another.
public class Config {
@Bean
public MappingJackson2HttpMessageConverter converter(final ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return converter;
}
@Bean
public ObjectMapper objectMapper() {
return new JacksonObjectMapper();
}
}
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.
public class Config {
@Bean
public MappingJackson2HttpMessageConverter converter(@Qualifier("jsonMapper") final ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return converter;
}
@Bean
public ObjectMapper jsonMapper() {
return new JacksonObjectMapper();
}
@Bean
public ObjectMapper xmlMapper() {
return new XmlMapper();
}
}
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.
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).
wow it even confuses the people doing the blog posts on it. Go Spring!