How to use injection with XmlMapper deserialization

狂风中的少年 提交于 2019-12-11 04:24:23

问题


I am using XmlMapper from com.fasterxml.jackson.dataformat.xml. The class, I am serializing, has an Autowired member that is not serialized.

I want to be able to deserialize the XML into an instance and have the autowired member variable populated by Spring.

Is there a way to do this?


回答1:


ObjectMapper has setInjectableValues method which allow to register some external beans which we want to use during serialisation/deserialisation. For example, DeserializationContext class has findInjectableValue method which allow to find previously registered bean in context by name. Below you can find example which shows general idea how to do that. First, declare an injectable bean which we want to autowire:

class InjectBean {

    private int key = ThreadLocalRandom.current().nextInt();

    @Override
    public String toString() {
        return "key => " + key;
    }
}

POJO class which we want to deserialise from XML could look like below:

class Pojo {

    private String name;
    private InjectBean dependency;

    // getters, setters, toString
}

Now, we need to implement custom deserialiser which will inject autowired field:

class PojoBeanDeserializer extends BeanDeserializer {

    public static final String DEPENDENCY_NAME = "injectBean";

    public PojoBeanDeserializer(BeanDeserializerBase src) {
        super(src);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        Object deserialize = super.deserialize(p, ctxt);
        InjectBean injectableValue = findInjectableValue(ctxt);

        Pojo pojo = (Pojo) deserialize;
        pojo.setDependency(injectableValue);

        return deserialize;
    }

    private InjectBean findInjectableValue(DeserializationContext context) throws JsonMappingException {
        return (InjectBean) context.findInjectableValue(DEPENDENCY_NAME, null, null);
    }
}

Above deserialiser could be used only for Pojo class. If you need to do the same for many classes you can extract setDependency method to an interface and implement it this interface by each POJO you need to handle in the same way. In above deserialiser instead of casting to Pojo you can cast to your interface. To register our custom deserialiser I will use BeanDeserializerModifier but you can do that in other way. For example if you already have custom deserialiser and you use @JsonDeserialize annotation you do not need to do that. Simple usage could look like below:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;

public class XmlMapperApp {

    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();

        InjectBean injectBean = autowire();
        InjectableValues.Std injectableValues = new InjectableValues.Std();
        injectableValues.addValue(PojoBeanDeserializer.DEPENDENCY_NAME, injectBean);

        SimpleModule injectModule = new SimpleModule();
        injectModule.setDeserializerModifier(new InjectBeanDeserializerModifier());

        XmlMapper xmlMapper = new XmlMapper();
        xmlMapper.registerModule(injectModule);
        xmlMapper.setInjectableValues(injectableValues);

        Pojo bean = xmlMapper.readValue(xmlFile, Pojo.class);

        System.out.println("After deserialization:");
        System.out.println(bean);
    }

    private static InjectBean autowire() {
        InjectBean bean = new InjectBean();

        System.out.println("Injectable bean from context: " + bean);

        return bean;
    }
}

class InjectBeanDeserializerModifier extends BeanDeserializerModifier {
    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        if (beanDesc.getType().getRawClass() == Pojo.class) {
            JsonDeserializer<?> jsonDeserializer = super.modifyDeserializer(config, beanDesc, deserializer);

            return new PojoBeanDeserializer((BeanDeserializer) jsonDeserializer);
        }

        return super.modifyDeserializer(config, beanDesc, deserializer);
    }
}

For below XML payload:

<Pojo>
    <name>Tom</name>
</Pojo>

Prints:

Injectable bean from context: key => 909636975
After deserialization:
Bean{name='Tom', dependency=key => 909636975}

See also:

  • How to inject dependency into Jackson Custom deserializer


来源:https://stackoverflow.com/questions/56015253/how-to-use-injection-with-xmlmapper-deserialization

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