Skip to content

Commit 6f98c63

Browse files
odrotbohmwilkinsona
authored andcommitted
Add auto-configuration for Jackson's JodaTime and JSR-310 modules
We now register the Jackson JodaTime module with Jackson ObjectMappers if it is on the classpath. We also register the JSR-310 module if it's on the classpath and the application is running Java 8 or better. Extracted the Jackson specific configuration previously residing in HttpMessageConvertersAutoConfiguration into a JacksonAutoConfiguration class. Added the Jackson JSR-310 module as a managed Boot dependency.
1 parent 30bef1e commit 6f98c63

File tree

6 files changed

+316
-158
lines changed

6 files changed

+316
-158
lines changed

spring-boot-autoconfigure/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
<artifactId>jackson-datatype-joda</artifactId>
3737
<optional>true</optional>
3838
</dependency>
39+
<dependency>
40+
<groupId>com.fasterxml.jackson.datatype</groupId>
41+
<artifactId>jackson-datatype-jsr310</artifactId>
42+
<optional>true</optional>
43+
</dependency>
3944
<dependency>
4045
<groupId>org.flywaydb</groupId>
4146
<artifactId>flyway-core</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jackson;
18+
19+
import java.util.Collection;
20+
21+
import javax.annotation.PostConstruct;
22+
23+
import org.springframework.beans.factory.BeanFactoryUtils;
24+
import org.springframework.beans.factory.ListableBeanFactory;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
30+
import org.springframework.boot.autoconfigure.web.HttpMapperProperties;
31+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
32+
import org.springframework.context.annotation.Bean;
33+
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.context.annotation.Primary;
35+
36+
import com.fasterxml.jackson.databind.Module;
37+
import com.fasterxml.jackson.databind.ObjectMapper;
38+
import com.fasterxml.jackson.databind.SerializationFeature;
39+
import com.fasterxml.jackson.datatype.joda.JodaModule;
40+
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
41+
42+
/**
43+
* Auto configuration for Jackson. The following auto-configuration will get applied:
44+
* <ul>
45+
* <li>an {@link ObjectMapper} in case none is already configured.</li>
46+
* <li>the {@link JodaModule} registered if it's on the classpath.</li>
47+
* <li>the {@link JSR310Module} registered if it's on the classpath and the application is
48+
* running on Java 8 or better.</li>
49+
* <li>auto-registration for all {@link Module} beans with all {@link ObjectMapper} beans
50+
* (including the defaulted ones).</li>
51+
* </ul>
52+
*
53+
* @author Oliver Gierke
54+
* @since 1.1.0
55+
*/
56+
@Configuration
57+
@ConditionalOnClass(ObjectMapper.class)
58+
@EnableConfigurationProperties(HttpMapperProperties.class)
59+
public class JacksonAutoConfiguration {
60+
61+
@Autowired
62+
private HttpMapperProperties properties = new HttpMapperProperties();
63+
64+
@Autowired
65+
private ListableBeanFactory beanFactory;
66+
67+
@Bean
68+
@ConditionalOnMissingBean
69+
@Primary
70+
public ObjectMapper jacksonObjectMapper() {
71+
72+
ObjectMapper objectMapper = new ObjectMapper();
73+
74+
if (this.properties.isJsonSortKeys()) {
75+
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
76+
}
77+
78+
return objectMapper;
79+
}
80+
81+
@PostConstruct
82+
public void init() {
83+
84+
Collection<ObjectMapper> mappers = BeanFactoryUtils
85+
.beansOfTypeIncludingAncestors(this.beanFactory, ObjectMapper.class)
86+
.values();
87+
Collection<Module> modules = BeanFactoryUtils.beansOfTypeIncludingAncestors(
88+
this.beanFactory, Module.class).values();
89+
90+
for (ObjectMapper mapper : mappers) {
91+
mapper.registerModules(modules);
92+
}
93+
}
94+
95+
@Configuration
96+
@ConditionalOnClass(JodaModule.class)
97+
static class JodaModuleAutoConfiguration {
98+
99+
@Bean
100+
@ConditionalOnMissingBean
101+
JodaModule jacksonJodaModule() {
102+
return new JodaModule();
103+
}
104+
}
105+
106+
@Configuration
107+
@ConditionalOnJava(JavaVersion.EIGHT)
108+
@ConditionalOnClass(JSR310Module.class)
109+
static class Jsr310ModuleAutoConfiguration {
110+
111+
@Bean
112+
@ConditionalOnMissingBean
113+
JSR310Module jacksonJsr310Module() {
114+
return new JSR310Module();
115+
}
116+
}
117+
}

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,34 @@
1717
package org.springframework.boot.autoconfigure.web;
1818

1919
import java.util.ArrayList;
20-
import java.util.Collection;
2120
import java.util.Collections;
2221
import java.util.List;
2322

24-
import javax.annotation.PostConstruct;
25-
26-
import org.springframework.beans.factory.BeanFactoryUtils;
27-
import org.springframework.beans.factory.ListableBeanFactory;
2823
import org.springframework.beans.factory.annotation.Autowired;
2924
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3025
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3126
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
3228
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3329
import org.springframework.context.annotation.Bean;
3430
import org.springframework.context.annotation.Configuration;
35-
import org.springframework.context.annotation.Primary;
31+
import org.springframework.context.annotation.Import;
3632
import org.springframework.http.converter.HttpMessageConverter;
3733
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
3834

39-
import com.fasterxml.jackson.databind.Module;
4035
import com.fasterxml.jackson.databind.ObjectMapper;
41-
import com.fasterxml.jackson.databind.SerializationFeature;
4236

4337
/**
4438
* {@link EnableAutoConfiguration Auto-configuration} for {@link HttpMessageConverter}s.
45-
*
39+
*
4640
* @author Dave Syer
4741
* @author Christian Dupuis
4842
* @author Piotr Maj
43+
* @author Oliver Gierke
4944
*/
5045
@Configuration
5146
@ConditionalOnClass(HttpMessageConverter.class)
47+
@Import(JacksonAutoConfiguration.class)
5248
public class HttpMessageConvertersAutoConfiguration {
5349

5450
@Autowired(required = false)
@@ -70,33 +66,6 @@ protected static class ObjectMappers {
7066
@Autowired
7167
private HttpMapperProperties properties = new HttpMapperProperties();
7268

73-
@Autowired
74-
private ListableBeanFactory beanFactory;
75-
76-
@PostConstruct
77-
public void init() {
78-
Collection<ObjectMapper> mappers = BeanFactoryUtils
79-
.beansOfTypeIncludingAncestors(this.beanFactory, ObjectMapper.class)
80-
.values();
81-
Collection<Module> modules = BeanFactoryUtils.beansOfTypeIncludingAncestors(
82-
this.beanFactory, Module.class).values();
83-
for (ObjectMapper mapper : mappers) {
84-
mapper.registerModules(modules);
85-
}
86-
}
87-
88-
@Bean
89-
@ConditionalOnMissingBean
90-
@Primary
91-
public ObjectMapper jacksonObjectMapper() {
92-
ObjectMapper objectMapper = new ObjectMapper();
93-
if (this.properties.isJsonSortKeys()) {
94-
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,
95-
true);
96-
}
97-
return objectMapper;
98-
}
99-
10069
@Bean
10170
@ConditionalOnMissingBean
10271
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
@@ -106,7 +75,5 @@ public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
10675
converter.setPrettyPrint(this.properties.isJsonPrettyPrint());
10776
return converter;
10877
}
109-
11078
}
111-
11279
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jackson;
18+
19+
import java.io.IOException;
20+
import java.util.Collection;
21+
22+
import org.hamcrest.Matchers;
23+
import org.joda.time.LocalDateTime;
24+
import org.junit.After;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
import org.mockito.Mockito;
28+
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
29+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.context.annotation.Primary;
33+
34+
import com.fasterxml.jackson.core.JsonGenerator;
35+
import com.fasterxml.jackson.core.JsonProcessingException;
36+
import com.fasterxml.jackson.databind.JsonSerializer;
37+
import com.fasterxml.jackson.databind.Module;
38+
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.SerializerProvider;
40+
import com.fasterxml.jackson.databind.module.SimpleModule;
41+
import com.fasterxml.jackson.datatype.joda.JodaModule;
42+
43+
import static org.hamcrest.Matchers.hasItem;
44+
import static org.hamcrest.Matchers.instanceOf;
45+
import static org.hamcrest.Matchers.is;
46+
import static org.junit.Assert.assertEquals;
47+
import static org.junit.Assert.assertThat;
48+
import static org.mockito.Matchers.argThat;
49+
import static org.mockito.Mockito.verify;
50+
51+
/**
52+
* Tests for {@link JacksonAutoConfiguration}.
53+
*
54+
* @author Dave Syer
55+
* @author Oliver Gierke
56+
*/
57+
public class JacksonAutoConfigurationTests {
58+
59+
AnnotationConfigApplicationContext context;
60+
61+
@Before
62+
public void setUp() {
63+
this.context = new AnnotationConfigApplicationContext();
64+
}
65+
66+
@After
67+
public void tearDown() {
68+
if (this.context != null) {
69+
this.context.close();
70+
}
71+
}
72+
73+
@Test
74+
public void registersJodaModuleAutomatically() {
75+
76+
this.context.register(JacksonAutoConfiguration.class);
77+
this.context.refresh();
78+
79+
Collection<Module> modules = this.context.getBeansOfType(Module.class).values();
80+
assertThat(modules, is(Matchers.<Module> iterableWithSize(1)));
81+
assertThat(modules.iterator().next(), is(instanceOf(JodaModule.class)));
82+
83+
ObjectMapper objectMapper = this.context.getBean(ObjectMapper.class);
84+
assertThat(objectMapper.canSerialize(LocalDateTime.class), is(true));
85+
}
86+
87+
@Test
88+
public void customJacksonModules() throws Exception {
89+
90+
this.context = new AnnotationConfigApplicationContext();
91+
this.context.register(ModulesConfig.class, JacksonAutoConfiguration.class);
92+
this.context.refresh();
93+
94+
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
95+
96+
@SuppressWarnings({ "unchecked", "unused" })
97+
ObjectMapper result = verify(mapper).registerModules(
98+
(Iterable<Module>) argThat(hasItem(this.context.getBean("jacksonModule",
99+
Module.class))));
100+
}
101+
102+
@Test
103+
public void doubleModuleRegistration() throws Exception {
104+
105+
this.context = new AnnotationConfigApplicationContext();
106+
this.context.register(DoubleModulesConfig.class,
107+
HttpMessageConvertersAutoConfiguration.class);
108+
this.context.refresh();
109+
110+
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
111+
assertEquals("{\"foo\":\"bar\"}", mapper.writeValueAsString(new Foo()));
112+
}
113+
114+
@Configuration
115+
protected static class ModulesConfig {
116+
117+
@Bean
118+
public Module jacksonModule() {
119+
return new SimpleModule();
120+
}
121+
122+
@Bean
123+
@Primary
124+
public ObjectMapper objectMapper() {
125+
return Mockito.mock(ObjectMapper.class);
126+
}
127+
}
128+
129+
@Configuration
130+
protected static class DoubleModulesConfig {
131+
132+
@Bean
133+
public Module jacksonModule() {
134+
SimpleModule module = new SimpleModule();
135+
module.addSerializer(Foo.class, new JsonSerializer<Foo>() {
136+
137+
@Override
138+
public void serialize(Foo value, JsonGenerator jgen,
139+
SerializerProvider provider) throws IOException,
140+
JsonProcessingException {
141+
jgen.writeStartObject();
142+
jgen.writeStringField("foo", "bar");
143+
jgen.writeEndObject();
144+
}
145+
});
146+
return module;
147+
}
148+
149+
@Bean
150+
@Primary
151+
public ObjectMapper objectMapper() {
152+
ObjectMapper mapper = new ObjectMapper();
153+
mapper.registerModule(jacksonModule());
154+
return mapper;
155+
}
156+
}
157+
158+
protected static class Foo {
159+
160+
private String name;
161+
162+
private Foo() {
163+
164+
}
165+
166+
static Foo create() {
167+
return new Foo();
168+
}
169+
170+
public String getName() {
171+
return this.name;
172+
}
173+
174+
public void setName(String name) {
175+
this.name = name;
176+
}
177+
178+
}
179+
}

0 commit comments

Comments
 (0)