spring-projects/spring-boot

Auto-configure Jersey with a JacksonJaxbXMLProvider when jackson-jaxrs-xml-provider is on the classpath

Opened this issue · 3 comments

Problem

Spring Boot has fantastic support for Jackson. When using Jackson for JSON serialization/deserialization--especially with spring-mvc--it's easy to configure Jackson through the spring.jackson.* properties (thanks autoconfigure!).

Where this solution begins to fall short is when trying to use Jersey instead of Spring MVC, with Jackson for XML marshalling/unmarshalling. Ideally, the framework should expose a configured XmlMapper bean, or provide an easy way to build an XmlMapper that is configured, which we can then pass to the Jackson JAXB provider. Unfortunately I can't find a way to make that work cleanly.

Here's what I have for configuration:

@Configuration
@Slf4j
public class JerseyResourceSpringConfig {

    @Bean
    public ResourceConfig jerseyResourceConfig() {
        final var resourceConfig = new ResourceConfig();
        // ...
        resourceConfig.register(JacksonJaxbXMLProvider.class);
        return resourceConfig;
    }
}

It took me a while to figure out the spring.jackson.* properties weren't being respected, it wasn't immediately obvious (maybe some documentation enhancements would help). Internally Jersey will build an XmlMapper with its own defaults but they don't respect spring.jackson.* properties, which is obvious now given that Jersey doesn't really integrate with Spring in this way.

Alternatively it's possible to do something like this instead:

@Configuration
@Slf4j
public class JerseyResourceSpringConfig {

    @Bean
    public ResourceConfig jerseyResourceConfig() {
        final var resourceConfig = new ResourceConfig();
        // ...
        final var mapper = new XmlMapper();
        // configure mapper
        resourceConfig.register(new JacksonJaxbXMLProvider(mapper, JacksonJaxbXMLProvider.DEFAULT_ANNOTATIONS));
        return resourceConfig;
    }
}

The tricky part is configuring this XmlMapper correctly from those spring.jackson.* properties. From what I've seen there's no elegant way to do this in this specific configuration. Taking a look at JacksonAutoConfiguration, an ObjectMapper bean is created and some other goodies, and Jackson2ObjectMapperBuilder supports building XmlMapper, but manually exposing an XmlMapper bean doesn't seem ideal.

Here's what worked for me:

    @Bean
    public ResourceConfig jerseyResourceConfig(final JacksonJaxbXMLProvider provider) {
        final var resourceConfig = new ResourceConfig();
        // ...
        resourceConfig.register(provider);
        return resourceConfig;
    }

    @Bean
    public JacksonJaxbXMLProvider provider(final Jackson2ObjectMapperBuilder builder) {
        final var mapper = (XmlMapper) builder.createXmlMapper(true)
                .findModulesViaServiceLoader(true)
                .build();
        return new JacksonJaxbXMLProvider(mapper, JacksonJaxbXMLProvider.DEFAULT_ANNOTATIONS);
    }

It's not clear to me why I need to find jackson modules through the service loader.. Wouldn't the builder be configured to do this? Can someone explain why this might be needed here?

Proposed Solution

One option is to update the existing JacksonAutoConfiguration to register a configured XmlMapper bean, perhaps if XmlMapper is on the classpath. It could leverage all of the existing autoconfiguration!

        @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
	static class JacksonObjectMapperConfiguration {

		@Bean
		@Primary
		@ConditionalOnMissingBean
		ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
			return builder.createXmlMapper(false).build();
		}

                @Bean
		@Primary
		@ConditionalOnMissingBean
		XmlMapper jacksonXmlMapper(Jackson2ObjectMapperBuilder builder) {
			return builder.createXmlMapper(true).build();
		}
	}

We've discussed this and we don't think we should introduce a new XmlMapper bean, as this would conflict with ObjectMapper (the first extends the latter).

Instead, we should react to the presence of the dedicated Jackson module for jax-rs xml support and configure a JacksonJaxbXMLProvider for the application.

@bclozel Ahh yes, good call, I didn't even occur to me that XmlMapper extends ObjectMapper. That would be a problem.

Sounds great!

Closing, for now at least, as Jersey support has been removed in 4.0