How to configure Jackson ObjectMapper for Camel in Spring Boot

流过昼夜 提交于 2019-12-03 08:58:49

问题


I am trying to serialize and deserialize POJOs to and from JSON on Camel routes using Jackson. Some of these have Java 8 LocalDate fields, and I want them to be serialised as YYYY-MM-DD string, not as an array of integers.

We only use Java configuration for our Spring Boot application, so no XML Camel configuration.

I have successfully created an ObjectMapper that does what I want, which is being used by other parts of our system by adding this to our dependencies:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

and this to our application configuration:

@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
    return builder
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .build();
}

Example outgoing REST route:

@Component
public class MyRouteBuilder extends RouteBuilder {

    @Override
    public void configure() throws Exception {

        restConfiguration().component("servlet").contextPath("/mycontext")
                .port(8080).bindingMode(RestBindingMode.json);

        rest("/myendpoint)
                .get()
                .route()
                .to("bean:myService?method=myMethod()");
    }
}

Example incoming message route:

@Component
public class MyRouteBuilder extends RouteBuilder {

    @Autowired
    private MyBean myBean;

    @Override
    public void configure() {
        from(uri)
                .unmarshal().json(JsonLibrary.Jackson)
                .bean(myBean);
    }
}

However, by default Camel creates its own ObjectMapper instances so does not pick up on either the JSR310 serializers/deserializers that Jackson2ObjectMapperBuilder adds automatically, or the disabled WRITE_DATES_AS_TIMESTAMPS feature. I have read the Camel JSON documentation, but it does not show how to add a custom DataFormat using Spring configuration, or how to apply a global customisation for all types.

So how can I tell Camel to use my ObjectMapper, using only Spring Boot Java configuration?


回答1:


I have found a solution by stepping through the Camel code. So while it does what I want, it might not work with future versions of Camel since it appears to be undocumented and potentially unsupported.

All I do is add the following bean to my Spring config, in additional to my ObjectMapper bean in the question:

@Bean(name = "json-jackson")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public JacksonDataFormat jacksonDataFormat(ObjectMapper objectMapper) {
    return new JacksonDataFormat(objectMapper, HashMap.class);
}

The crucial points to note:

  • There is no constructor for JacksonDataFormat that takes an ObjectMapper without an unmarshal type. However, in the default constructor a HashMap.class is used when no unmarshal type is provided, so I use that. By some magic, this appears to then get used to unmarshal all POJO types. If you also need more specific data formats for other classes, you will need to set the ObjectMapper in them too.
  • Camel appears to search the bean registry for a bean called "json-jackson", so setting the Spring bean to use that name tricks Camel into not creating a new one and using mine instead.
  • The bean scope must be set to SCOPE_PROTOTYPE because the REST DSL expects to get a new instance of the DataFormat. See CAMEL-7880.



回答2:


Create the JacksonDataFormat in java code and enable/disable the features you want, and then use that instance in the Camel route.

 .unmarshal(myInstanceGoesHere).



回答3:


Using Spring and Camel 2.18.1, I was able to achieve the same by adding the following dependencies:

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.6.1</version>
</dependency>

and in a CamelContextConfiguration class, autowiring the JacksonDataFormat in order to configure the discovery of classpath modules and the configuration of the serialization options:

@Configuration
public class CamelContextConfig implements CamelContextConfiguration {

    @Autowired
    public JacksonDataFormat jacksonDataFormat;

    @Override
    public void beforeApplicationStart(CamelContext camelContext) {
    }

    @Override
    public void afterApplicationStart(CamelContext camelContext) {
        jacksonDataFormat
            .getObjectMapper()
            .findAndRegisterModules()
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }
}



回答4:


I managed to configure ObjectMapper for Camel quite conveniently using org.apache.camel:camel-jackson-starter:2.20.0

It exposes some of the useful ObjectMapper properties for configuration via Spring application properties. WRITE_DATES_AS_TIMESTAMPS for example can be set straight from application.yaml or application.properties file.

Look for JacksonDataFormatConfiguration class for more details.

I also needed to use some Mixins so I still needed to configure Camel to use a Spring's ObjectMapper. I ended up with this:

Configuration bean:

@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder builder) {
            builder.mixIn(Person.class, PersonMixin.class);
        }
    }
}

application.yaml:

camel:
  dataformat:
    json-jackson:
      disable-features: WRITE_DATES_AS_TIMESTAMPS
      object-mapper: jacksonObjectMapper

Where jacksonObjectMapper is the name of the ObjectMapper bean built by the configured Jackson2ObjectMapperBuilder




回答5:


So far only the suggestion of @david-edwards has worked for me. I first defined a data format bean with the id: "json-jackson"

<bean id="json-jackson" class="com.mydomain.JacksonDataFormatExt" />

Then the format class:

public class JacksonDataFormatExt extends JacksonDataFormat{

    public JacksonDataFormatExt(){
        super();
        setPrettyPrint(true);
        setEnableFeatures(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.name());
        SimpleModule s = new SimpleModule();
        s.addSerializer(CustomEnum.class, new CustomEnumSerializer());
        addModule(s);
    }
}

And the CustomEnumSerializer class:

public class CustomEnumSerializer extends JsonSerializer<CustomEnum> {

    @Override
    public void serialize(CustomEnumvalue, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        String stringValue = value.getNlsText();
        if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
            jgen.writeString(stringValue);
        } else {
            jgen.writeNull();
        }
    }
}



回答6:


If anyone else was wondering how to use the fix put in the ver. 2.17.. I got it working using this xml configuration:

 <camel:camelContext id="defaultCamelContext">
       .....
        <camel:dataFormats>
            <camel:json id="json" library="Jackson"  objectMapper="myObjectMapper"/>
        </camel:dataFormats>

 </camel:camelContext>

..where myObjectMapper is a name of a spring bean of type ObjectMapper




回答7:


If Camel gives you trouble there, I would revert to using beans directly:

  1. Simply create a small Json utility that can do marshalling and unmarshalling and autowire your preconfigured ObjectMapper into it.

  2. Harness Camels awesome Spring bean integration to call your utility and transform the Message in the route, e.g.:

         from(uri)
            .unmarshal().json(JsonLibrary.Jackson)
            .beanRef("jsonUtil", "unmarshal")
            .bean(myBean);
    



回答8:


I could not get any of the examples to work. A little disappointed that this is quite complicated from reading workarounds.

In my opinion camel should make it easy to use the Spring default object mapper by using the same Jackson bean that comes with the application.

I forgone the use of .json() and swapped it for a processor.

like the following, this used the objectMapper provided by Spring.

Route

from(CONSUME_TAG)
 .process("jsonProcessor")
 .to("direct:anotherRoute")
 .end();

Generic Processor notice how this Autowires the spring boot objectMapper bean.

@Component
public class JsonProcessor implements Processor {

    @Autowired
    ObjectMapper objectMapper;

    @Override
    public void process(Exchange exchange) throws Exception {
        exchange.getOut().setBody(objectMapper.writeValueAsString(exchange.getIn().getBody()));
    }

}


来源:https://stackoverflow.com/questions/33397359/how-to-configure-jackson-objectmapper-for-camel-in-spring-boot

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!