问题
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