简体   繁体   English

如何从Jackson @JsonCreator构造函数访问Spring Boot环境变量

[英]How can I access a Spring Boot environment variable from a Jackson @JsonCreator constructor

I have an immutable model class which can be instantiated by Jackson using the @JsonCreator annotated constructor. 我有一个不变的模型类,可以由Jackson使用@JsonCreator注释的构造函数实例化。 One of the properties is optional and I want to have a dynamic default value based on a variable in application.yml to replace the fixed value of 30 below. 这些属性之一是可选的,我想基于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);
    }
}

I tried adding an additional constructor parameter @Value("${default-session-timeout-secs}") final int defaultSessionTimeout but that results in a MessageConversionException when attempting to map. 我尝试添加一个附加的构造函数参数@Value("${default-session-timeout-secs}") final int defaultSessionTimeout但是在尝试映射时会导致MessageConversionException

You can do this by bridging your properties and making use of @JacksonInject . 您可以通过桥接属性并使用@JacksonInject This annotation works well with the object-mapper. 此注释与对象映射器配合使用良好。 I am skipping the part on how to bridge spring to jackson in code, but give you a plain ObjectMapper example. 我跳过了有关如何在代码中将spring桥接到jackson的部分,但是给了您一个简单的ObjectMapper示例。 See this code: 参见以下代码:

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

This code prints: 此代码打印:

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

Few notes: 一些注意事项:

  1. The constructor is using the JacksonInject annotation in the constructor in addition to a property. 除了属性之外,构造函数还使用构造函数中的JacksonInject批注。 This will inject the configured value first, optionally overwriting it with the json value coming from the input. 这将首先注入配置的值,可选地使用来自输入的json值覆盖它。

  2. The ObjectMapper is being fed properties that match the annotation we have 正在向ObjectMapper提供与我们具有的注释匹配的属性

The second part is the most important. 第二部分是最重要的。

As for making this work with spring, the approach would be to have this code in your configuration: 至于使用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;
    }
}

You can alternatively also inject ALL properties and feed them back to the object mapper, as to not having to extend this with every new property. 另外,您也可以注入ALL属性,并将其反馈给对象映射器,以免不必对每个新属性进行扩展。

I hope that helps, 希望对您有所帮助,

Some more reading material here: https://www.concretepage.com/jackson-api/jackson-jacksoninject-example#ObjectMapper 一些更多的阅读材料在这里: https : //www.concretepage.com/jackson-api/jackson-jacksoninject-example#ObjectMapper

Latest edit: How to do this with only 1 constructor argument, having jackson handle the overwrite itself 最新编辑:如何仅使用1个构造函数参数,让jackson自己处理覆盖

-- Artur -阿图尔

You can do this by using two helper classes. 您可以通过使用两个帮助程序类来实现。

The first is ContextHolder , which allows you to access Spring's ApplicationContext from outside Spring-managed bean: 第一个是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;
    }
}

The second is DefaultTimeoutWrapper , which holds default timeout value: 第二个是DefaultTimeoutWrapper ,其中包含默认超时值:

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

    public int getDefaultTimeout() {
        return defaultTimeout;
    }
}

Then you need to change your DataRequest class to use them like this: 然后,您需要更改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;
    }
}

Note that in ContextHolder instance method is used to set value of a static field, which is generally considered to be bad design. 请注意,在ContextHolder实例方法用于设置静态字段的值,通常认为这是不好的设计。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在我的Spring Boot应用程序中从AWS访问环境变量 - How to access Environment Variable from AWS in my spring boot application 如何在Spring Boot中注册并使用jackson AfterburnerModule? - How can I register and use the jackson AfterburnerModule in Spring Boot? 在POJO中未调用带有Jackson @JsonCreator注释的构造函数? - Constructor annotated with Jackson @JsonCreator not being called in POJO? 如何从 Spring Boot 中的实体访问存储库? - How can I access the repository from the entity in Spring Boot? 如何从Constructor访问@Value注释变量? - How can I access to @Value annotated variable from Constructor? 如何使用 Jackson ObjectMapper 反序列化 Spring 的 ResponseEntity(可能使用 @JsonCreator 和 Jackson mixins) - How to deserialize Spring's ResponseEntity with Jackson ObjectMapper (probably with using @JsonCreator and Jackson mixins) 如何从 Spring Boot 获取操作系统环境变量? - How to get OS environment variable from Spring Boot? 如何在Spring Boot中将YAML属性与构造函数注入配合使用? - How can I use YAML properties with constructor injection in Spring Boot? Spring不使用带注释的构造函数(@JsonCreator)进行实例化 - Spring does not use annotated constructor (@JsonCreator) for instantiation 如何在 Spring Boot / Gradle / IntelliJ 环境中设置 Java 环境变量? - How do I set up a Java Environment Variable in a Spring Boot / Gradle / IntelliJ Environment?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM