【问题标题】:How to inject dependency into Jackson Custom deserializer如何将依赖项注入杰克逊自定义反序列化器
【发布时间】:2019-02-20 01:44:25
【问题描述】:

我想为某些字符串类型的字段启用自定义杰克逊反序列化器。反序列化器还需要注入一个基于 guice 的依赖 bean。示例代码如下:

public class CustomDeserializer extends StdDeserializer<String> {

    private SomeDependecy dependency;

    public StringDeserializer() {
        this(null);
    }

    public StringDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return dependency.perform(p.getValueAsString());
    }
}

我无法注册基于类类型的模块,因为它是通用的(String.class、复杂数据类型(但不是每个都需要自定义反序列化器))。有没有办法在不使用静态方法的情况下实现上述目标?

PS:我确实搜索过网络,但如果不使用 statics 就找不到更清洁的解决方案。使用一些静态方法获取上下文和 bean 的所有建议。

【问题讨论】:

  • 我觉得这个问题有两个部分。首先,如何注入依赖。可以通过使用ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addDeserializer(String.class, new CustomDeserializer()); mapper.registerModule(module); 注册这个反序列化器来解决这个问题但是,有选择地将此反序列化器应用于某些字符串字段,这是一个不同的问题。你如何定义这个反序列化器应该应用于你的类中的哪些字符串字段?
  • 加密的字符串将在反序列化器将应用到的字符串字段列表中失败。
  • JSON反序列化后能否进行解密。您可以创建一个可以简单地使用 Object mapper 解析并表示加密数据的中间模型,然后调用一些不同的 API 来解密加密模型。在某些情况下,当您希望针对 JSON 解析异常或依赖项失败异常以不同方式处理故障时,这也可能是有益的
  • 也有适用于复杂对象的用例。因此,我试图以某种方式在反序列化中注入解密逻辑。否则,我将不得不写两层 Transformation JSONStringWithEncryptedBlobs -> JSONStringWithPlainData -> JavaPojo
  • 似乎您应该在要使用此反序列化程序的特定位置围绕 String 使用包装器类型。这将允许您显式地绑定到该包装器类型。 module.addDeserializer(StringWrapper.class, new CustomDeserializer(dependency))

标签: java jackson guice json-deserialization


【解决方案1】:

看起来还有另一种方法(感谢我的一位同事)在 objectMapper 实例上使用 injectableValues,然后通过 DeserializationContext ctxt 获取依赖项。以下是代码。

ObjectMapper guice 模块。

public class MerchantConverterModule extends AbstractModule {

    @Override
    protected void configure() {

    }

    @Provides
    @Singleton
    public ObjectMapper objectMapper() {

        ObjectMapper objectMapper = new ObjectMapper();

        /**
         * Add dependency object to object mapper.
         */
        objectMapper.setInjectableValues(new InjectableValues
            .Std()
            .addValue("DependencyName", dependency));

        return objectMapper;
    }


}

自定义反序列化器的代码

public class CustomDeserializer extends StdDeserializer<String> {

    private SomeDependecy dependency;

    public StringDeserializer() {
        this(null);
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return getDependency(ctxt).perform(p.getValueAsString());
    }

    private SomeDependency getDependency(DeserializationContext ctxt) {
        SomeDependency dependency = (SomeDependency) ctxt
                .findInjectableValue("DependencyName", null, null);

        return dependency;
    }
}

findInjectableValue 方法是 final 方法,因此您可能需要调整单元测试代码以模拟 final。

注意: 缺点是对象映射器和反序列化器之间存在紧密耦合。

【讨论】:

    【解决方案2】:

    看看ContextualDeserializer 接口。来自文档:

    JsonDeserializers 可以实现的附加接口以获得一个 可用于创建上下文的回调(依赖于上下文) 用于处理支持的属性的解串器实例 类型。这对于可以通过以下方式配置的解串器很有用 注释,或者应该有不同的行为取决于 正在反序列化什么样的属性。

    假设你有简单的解密接口和实现结构。

    interface Dependency {
    
        String decrypt(String value);
    }
    
    class SomeDependency implements Dependency {
    
        public SomeDependency() {
            System.out.println("Create new SomeDependency!");
        }
    
        @Override
        public String decrypt(String value) {
            return value.replace('a', 'A');
        }
    }
    
    class DecryptModule extends AbstractModule {
    
        @Override
        protected void configure() {
            bind(Dependency.class).to(SomeDependency.class);
        }
    }
    

    您的自定义反序列化器可能如下所示:

    class DecryptDeserializer extends StdDeserializer<String> implements ContextualDeserializer {
    
        private Dependency dependency;
    
        public DecryptDeserializer() {
            super(String.class);
        }
    
        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return dependency.decrypt(p.getValueAsString());
        }
    
        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
            Injector injector = Guice.createInjector(new DecryptModule());
            DecryptDeserializer deserializer = new DecryptDeserializer();
            deserializer.dependency = injector.getInstance(Dependency.class);
    
            return deserializer;
        }
    }
    

    createContextual 方法用于创建新的反序列化器实例。你有很多选择如何创建它。您甚至可以将此解决方案与Static Injection 混合使用。

    【讨论】:

    • 感谢您的建议。我将注入作为 DecryptDeserializer 默认构造函数的一部分进行。
    【解决方案3】:

    我没怎么用过Guice,但我猜它和Spring有点相似。因此,我将在此处发布此答案,以防您可以使用与我相同的原则,并且使用 Spring 的人会阅读此内容。

    首先我将我的 Jackson 模块注释为 Spring @Component:

    @Component
    public class FlowModule extends SimpleModule {
    
    @Autowired private SupplierItemDeserializer supplierItemDeserializer;
    

    这意味着我可以将任何我想要的@Autowired 放入我的模块中。正如你所看到的,我也对我的反序列化器做了同样的事情:

    @Component
    public class SupplierItemDeserializer extends JsonDeserializer<SupplierItem> {
    
    @Autowired
    private SupplierService supplierService;
    

    然后我创建了一个服务来处理与 JSON 之间的转换:

    @Service
    public class IOService {
    
    private ObjectMapper mapper;
    
    @Autowired
    private FlowModule flowModule;
    
    
    @PostConstruct
    public void initialize() {
        mapper = new ObjectMapper();
        mapper.registerModule(flowModule);
        mapper.registerModule(new JavaTimeModule());
    }
    

    该服务包含一个带有我的模块的 ObjectMapper,该模块具有所有正确的接线。

    【讨论】:

      猜你喜欢
      • 2015-02-06
      • 1970-01-01
      • 1970-01-01
      • 2014-01-31
      • 1970-01-01
      • 1970-01-01
      • 2017-01-30
      • 1970-01-01
      • 2013-08-11
      相关资源
      最近更新 更多