diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java index 1ad876886ef..348e09e822a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java @@ -28,9 +28,11 @@ import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.ws.rs.ApplicationPath; import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.Provider; +import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.glassfish.jersey.CommonProperties; @@ -40,7 +42,6 @@ import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.servlet.ServletProperties; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -234,23 +235,57 @@ public class JerseyAutoConfiguration implements ServletContextAware { @Configuration static class JacksonResourceConfigCustomizer { + private static final String JAXB_ANNOTATION_INTROSPECTOR_CLASS_NAME = "com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector"; + @Bean - public ResourceConfigCustomizer resourceConfigCustomizer() { + public ResourceConfigCustomizer resourceConfigCustomizer( + final ObjectMapper objectMapper) { + addJaxbAnnotationIntrospectorIfPresent(objectMapper); return new ResourceConfigCustomizer() { @Override public void customize(ResourceConfig config) { config.register(JacksonFeature.class); - config.register(ObjectMapperContextResolver.class); + config.register(new ObjectMapperContextResolver(objectMapper), + ContextResolver.class); } }; } - @Provider - static class ObjectMapperContextResolver + private void addJaxbAnnotationIntrospectorIfPresent(ObjectMapper objectMapper) { + if (ClassUtils.isPresent(JAXB_ANNOTATION_INTROSPECTOR_CLASS_NAME, + getClass().getClassLoader())) { + new ObjectMapperCustomizer().addJaxbAnnotationIntrospector(objectMapper); + } + } + + private static final class ObjectMapperCustomizer { + + private void addJaxbAnnotationIntrospector(ObjectMapper objectMapper) { + JaxbAnnotationIntrospector jaxbAnnotationIntrospector = new JaxbAnnotationIntrospector( + objectMapper.getTypeFactory()); + objectMapper.setAnnotationIntrospectors( + createPair(objectMapper.getSerializationConfig(), + jaxbAnnotationIntrospector), + createPair(objectMapper.getDeserializationConfig(), + jaxbAnnotationIntrospector)); + } + + private AnnotationIntrospector createPair(MapperConfig config, + JaxbAnnotationIntrospector jaxbAnnotationIntrospector) { + return AnnotationIntrospector.pair(config.getAnnotationIntrospector(), + jaxbAnnotationIntrospector); + } + + } + + private static final class ObjectMapperContextResolver implements ContextResolver { - @Autowired - private ObjectMapper objectMapper; + private final ObjectMapper objectMapper; + + private ObjectMapperContextResolver(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } @Override public ObjectMapper getContext(Class type) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java index 9531a989760..2fa84566201 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java @@ -25,6 +25,7 @@ import java.lang.annotation.Target; import javax.ws.rs.ApplicationPath; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.xml.bind.annotation.XmlTransient; import org.glassfish.jersey.server.ResourceConfig; import org.junit.Test; @@ -52,9 +53,10 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link JerseyAutoConfiguration} with a ObjectMapper. * * @author EddĂș MelĂ©ndez + * @author Andy Wilkinson */ @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.jackson.serialization-inclusion:non-null") @DirtiesContext public class JerseyAutoConfigurationObjectMapperProviderTests { @@ -62,12 +64,12 @@ public class JerseyAutoConfigurationObjectMapperProviderTests { private TestRestTemplate restTemplate; @Test - public void contextLoads() { + public void responseIsSerializedUsingAutoConfiguredObjectMapper() { ResponseEntity response = this.restTemplate.getForEntity("/rest/message", String.class); assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); - assertThat("{\"subject\":\"Jersey\",\"body\":null}") - .isEqualTo(response.getBody()); + assertThat(response.getBody()) + .isEqualTo(String.format("{\"subject\":\"Jersey\"}")); } @MinimalWebConfiguration @@ -121,6 +123,11 @@ public class JerseyAutoConfigurationObjectMapperProviderTests { this.body = body; } + @XmlTransient + public String getFoo() { + return "foo"; + } + } @Target(ElementType.TYPE)