代码之家  ›  专栏  ›  技术社区  ›  Jon Freedman

如何从jackson@jsonCreator构造函数访问Spring引导环境变量

  •  0
  • Jon Freedman  · 技术社区  · 6 年前

    我有一个不可变的模型类,Jackson可以使用 @JsonCreator 带注释的构造函数。其中一个属性是可选的,我希望具有基于中的变量的动态默认值。 application.yml 替换以下30的固定值。

    public class DataRequest {
        private final List<String> fields;
        private final int sessionTimeoutSecs;
    
        @JsonCreator
        public DataRequest(@JsonProperty("fields") final List<String> fields,
                           @JsonProperty("sessionTimeoutSecs") final Integer sessionTimeoutSecs) {
            this.fields = fields;
            this.sessionTimeoutSecs = MoreObjects.firstNonNull(sessionTimeoutSecs, 30);
        }
    }
    

    我尝试添加一个额外的构造函数参数 @Value("${default-session-timeout-secs}") final int defaultSessionTimeout 但结果是 MessageConversionException 试图映射时。

    2 回复  |  直到 6 年前
        1
  •  1
  •   Jon Freedman    6 年前

    您可以通过桥接您的属性并利用 @JacksonInject .此注释与对象映射器一起工作良好。我在代码中跳过了如何将Spring桥接到Jackson的部分,但请简单介绍一下 ObjectMapper 例子。请参见此代码:

    package de.pandaadb.jackson;
    
    import java.io.IOException;
    
    import com.fasterxml.jackson.annotation.JacksonInject;
    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.core.JsonParseException;
    import com.fasterxml.jackson.databind.InjectableValues;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class DataModel {
    
        private String test;
        private String optionalValue;
    
        @JsonCreator
        public DataModel(@JsonProperty("test") String test, @JacksonInject("opt") @JsonProperty(value= "opt", required=false)String opt) {
            this.optionalValue = opt;
            this.test = test;
        }
    
        @Override
        public String toString() {
            return "test=" + test + " optional=" + optionalValue;
        }
    
    
        public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
            ObjectMapper mapper = new ObjectMapper();
            InjectableValues.Std std = new InjectableValues.Std();
            std.addValue("opt", "alternative");
    
            String withOptional = "{\"test\" : \"hello\" ,    \"opt\" : \"optss\"}";
            String withoutOptional = "{\"test\" : \"hello\"}";
    
            mapper.setInjectableValues(std);
    
            System.out.println(mapper.readValue(withOptional, DataModel.class));
            System.out.println(mapper.readValue(withoutOptional, DataModel.class));
        }
    }
    

    此代码打印:

    test=hello optional=optss
    test=hello optional=alternative
    

    注意事项:

    1. 构造函数正在使用 JacksonInject 构造函数中的批注以及属性。这将首先注入配置的值,也可以用来自输入的JSON值覆盖它。

    2. 这个 对象映射器 正在馈送与我们的批注匹配的属性

    第二部分是最重要的部分。

    至于如何使用Spring,方法是在配置中包含此代码:

    @Configuration
    public class ObjectMapperConfiguration {
    
        ObjectMapper mapper(@Value("${my.test.string}") String test) {
            ObjectMapper mapper = new ObjectMapper();
            InjectableValues.Std std = new InjectableValues.Std();
            std.addValue("my.test.string", "test");
            mapper.setInjectableValues(std);
            return mapper;
        }
    }
    

    您也可以插入所有属性并将它们反馈给对象映射器,这样就不必用每个新属性来扩展它。

    希望有帮助,

    这里还有一些阅读材料: https://www.concretepage.com/jackson-api/jackson-jacksoninject-example#ObjectMapper

    最新编辑:如何只使用一个构造函数参数来完成此操作,让Jackson自己处理覆盖。

    --阿图尔

        2
  •  0
  •   Konrad Botor    6 年前

    您可以通过使用两个助手类来实现这一点。

    第一个是 ContextHolder ,它允许您从Spring管理的bean外部访问Spring的applicationContext:

    @Component
    public class ContextHolder implements ApplicationContextAware {
    
        public static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            ContextHolder.applicationContext = applicationContext;
        }
    }
    

    第二个是 DefaultTimeoutWrapper ,保留默认超时值:

    @Component
    public class DefaultTimeoutWrapper {
        @Value("${default-session-timeout-secs}")
        private int defaultTimeout;
    
        public int getDefaultTimeout() {
            return defaultTimeout;
        }
    }
    

    然后你需要改变你的 DataRequest 类如下使用它们:

    public class DataRequest {
        private final List<String> fields;
        private final int sessionTimeoutSecs;
    
        @JsonCreator
        public DataRequest(@JsonProperty("fields") final List<String> fields,
                           @JsonProperty("sessionTimeoutSecs") final Integer sessionTimeoutSecs) {
            DefaultTimeoutWrapper defaultTimeoutWrapper = ContextHolder.applicationContext.getBean(DefaultTimeoutWrapper.class);
            int defaultTimeout = defaultTimeoutWrapper.getDefaultTimeout();
    
            this.fields = fields;
            this.sessionTimeoutSecs = sessionTimeoutSecs != null ? sessionTimeoutSecs : defaultTimeout;
        }
    }
    

    注意,在 上下文持有者 实例法用于静态字段的设置,通常被认为是一种糟糕的设计。