Как использовать инъекцию с десериализацией XmlMapper

Я использую XmlMapper от com.fasterxml.jackson.dataformat.xml. Класс, который я сериализую, имеет член Autowired, который не сериализуется.

Я хочу иметь возможность десериализовать XML в экземпляр и иметь переменную-член с автоматическим подключением, заполненную Spring.

Есть ли способ сделать это?


person KenB    schedule 07.05.2019    source источник
comment
Вы видели мой ответ? Вам это как-то помогло?   -  person Michał Ziober    schedule 14.05.2019


Ответы (1)


ObjectMapper имеет setInjectableValues, который позволяет зарегистрировать некоторые внешние компоненты, которые мы хотим использовать во время serialisation/deserialisation. Например, класс DeserializationContext имеет findInjectableValue, который позволяет найти ранее зарегистрированный бин в контексте по имени. Ниже вы можете найти пример, который показывает общее представление о том, как это сделать. Во-первых, объявим инъекционный компонент, который мы хотим автоматически связать:

class InjectBean {

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

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

POJO, который мы хотим десериализовать из XML, может выглядеть следующим образом:

class Pojo {

    private String name;
    private InjectBean dependency;

    // getters, setters, toString
}

Теперь нам нужно реализовать пользовательский десериализатор, который будет вводить поле autowired:

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);
    }
}

Приведенный выше десериализатор можно использовать только для класса Pojo. Если вам нужно сделать то же самое для многих классов, вы можете извлечь метод setDependency в интерфейс и реализовать его в этом интерфейсе для каждого POJO, с которым вам нужно обращаться таким же образом. В приведенном выше десериализаторе вместо приведения к Pojo вы можете приводить к своему интерфейсу. Чтобы зарегистрировать наш пользовательский десериализатор, я буду использовать BeanDeserializerModifier, но вы можете сделать это и другим способом. Например, если у вас уже есть собственный десериализатор и вы используете аннотацию @JsonDeserialize, вам не нужно этого делать. Простое использование может выглядеть следующим образом:

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);
    }
}

Для полезной нагрузки ниже XML:

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

Отпечатки:

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

Смотрите также:

person Michał Ziober    schedule 07.05.2019