Как создать пользовательскую аннотацию, похожую на @autowire и @value - PullRequest
0 голосов
/ 25 января 2019

Мы используем значения конфигурации из внешнего источника в большом приложении пружины.Есть ли способ создать пользовательскую аннотацию, чтобы мы могли «связать» этих провайдеров значений конфигурации?

У нас есть сервис, который предоставляет значения конфигурации на основе ряда переменных, таких как текущая среда (dev / prod) и текущий канал(веб / мобильный).В настоящее время это использует статический код и не использует пружину.Я искал способ зарегистрировать пользовательскую аннотацию с помощью Spring и фабрику для этой аннотации, например, так:

@MyConfigurationAnnotation(key="my.config.key", fallbackValue= "1")
private MyConfigValueProvider provider;

...
void someMethod(){
    int val = provider.get(currentEnvironment, Integer.class);
}

Я ищу способ зарегистрировать некоторую myConfigAnnotationBeanFactory для пружины, которую Spring вызывает с помощьюзначения из аннотации.Затем фабрика создает компонент поставщика для этого конкретного ключа конфигурации.

Возможно ли что-то подобное весной?С @Autowire и @Value уже есть две аннотации, которые делают что-то похожее, я просто хочу зарегистрировать третий тип проводного механизма с пружиной.

Ответы [ 2 ]

0 голосов
/ 29 января 2019

Мне удалось найти работоспособное решение: Сначала я создал аннотацию для моих значений конфигурации

@Retention(RUNTIME)
@Target({ FIELD })
@Autowired
public @interface ConfigurationValue {
  String name();

  String defaultValue();
}

Затем я добавил BeanPostProcessor / FactoryBean для своих значений конфигурации

public class ConfigValueBeanProcessor implements BeanPostProcessor, FactoryBean<ConfigSupplier> {

  @Autowired
  private EnvironmentConfiguration environmentConfiguration;

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean.getClass().isAnnotationPresent(MyConfigurableMarkerAnnotationOnClassLevel.class)) {
      List<Field> annotatedFields = FieldUtils.getFieldsListWithAnnotation(bean.getClass(), ConfigurationValue.class);
      for (Field field : annotatedFields) {
        try {
          processAnnotatedField(field, bean);
        } catch (IllegalAccessException e) {
        // do stuff
        }
      }
    }
    return bean;
  }

  private void processAnnotatedField(Field field, Object bean) throws IllegalAccessException {
    boolean accessible = field.isAccessible();
    field.setAccessible(true);
    Object o = field.get(bean);
    if (o instanceof ConfigSupplier) {
      ConfigurationValue annotation = field.getAnnotation(ConfigurationValue.class);
      ConfigSupplier configSupplier = (ConfigSupplier) o;
      ConfigSupplier patchedSupplier = configSupplier.withSettingsKeyAndDefault(
          annotation.name(), annotation.defaultValue());
      field.set(bean, patchedSupplier);
    }
    field.setAccessible(accessible);
  }

  @Override
  public ConfigSupplier getObject() throws Exception {
    return new ConfigSupplier(environmentConfiguration);
  }

  @Override
  public Class<?> getObjectType() {
    return ConfigSupplier.class;
  }
}

Что происходит, это: Spring автоматически связывает мой ConfigSupplier со всеми зависимостями. Постпроцессор позже исправляет этого поставщика в жизненном цикле со значением по умолчанию и с правильным ключом конфигурации.

Чувствую себя немного хакером, и я все еще ищу лучшие альтернативы, но это работает. Вероятно, наилучшим способом было бы переопределить org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties и добавить логику для создания компонента и добавить данные аннотации за один шаг вместо моего двухэтапного подхода.

0 голосов
/ 25 января 2019

Может ли комбинация @ConfigurationProperties и фабричного класса, предоставленного для @PropertySource, помочь достичь желаемого?

Например,

@Configuration
@PropertySource(value="some-value", name="my.config.key", factory=MyConfigFactory.class)
@ConfigurationProperties(prefix="example")
public class ExternalConfig {

    private String name = "default";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }        
}

С фабричным классом, который отправляется и выбираетваши внешние свойства

public class MyConfigFactory implements PropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        // The name will be what you set on the @PropertySource 'my.config.key'
        // The resource is from the value you set on the @PropertySource
        // you could get at that using resource.getResource().getFilename()
        Map<String, Object> properties = new HashMap<>();
        // set whatever properties needed in the map
        properties.put("example.name", name);
        return new MapPropertySource("my-external-properties", properties );
    }

}

Здесь я только что установил example.name="my.congig.key".Это заменит начальное значение «по умолчанию» для поля имени в ExternalConfig.

...