Как получить доступ к переменной среды Spring Boot из конструктора Jackson @JsonCreator - PullRequest
0 голосов
/ 29 июня 2018

У меня есть неизменный класс модели, который может быть создан Джексоном с помощью аннотированного конструктора @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 ]

0 голосов
/ 29 июня 2018

Вы можете сделать это с помощью двух вспомогательных классов.

Первым является ContextHolder, который позволяет вам получить доступ к SpringCon ApplicationContext из внешнего компонента, управляемого Spring:

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

Обратите внимание, что в ContextHolder метод экземпляра используется для установки значения статического поля, которое обычно считается плохим проектом.

0 голосов
/ 29 июня 2018

Вы можете сделать это, связав свои свойства и используя @JacksonInject. Эта аннотация хорошо работает с объектным картографом. Я пропускаю часть о том, как связать пружину с Джексоном в коде, но приведу простой пример 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. ObjectMapper передаются свойства, которые соответствуют аннотации

Вторая часть самая важная.

Что касается работы с пружиной, подход будет иметь такой код в вашей конфигурации:

@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

Последнее редактирование: как сделать это только с одним аргументом конструктора, если Джексон сам обработает перезапись

- Артур

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...