如何创建类似于@autowire 和@value 的自定义注解

How to create custom annotation similar to @autowire and @value

我们在大型 spring 应用程序中使用来自外部源的配置值。有没有办法创建自定义注释,以便我们可以 'wire' 这些配置值提供程序?

我们有一项服务可以根据当前环境 (dev/prod) 和当前频道 (web/mobile) 等变量提供配置值。 目前这使用静态代码,不使用 spring。 我搜索了一种使用 spring 注册自定义注释的方法以及该注释的工厂,如下所示:

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

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

我正在寻找一种方法来将一些 "myConfigAnnotationBeanFactory" 注册到 spring,spring 使用注释中的值进行调用。然后工厂为这个特定的配置键创建一个供应商 bean。

在 spring 中有这样的可能吗?使用@Autowire 和@Value 已经有两个注释做类似的事情,我只想用 spring.

注册第三种线机制

@ConfigurationProperties 和工厂 class 的组合提供给 @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;
    }        
}

有一个工厂 class 可以获取您的外部属性

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 中名称字段的初始值 "default"。

我设法找到了一个可行的解决方案: 首先,我为我的配置值创建了一个注释

@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 并添加逻辑以一步创建 bean 并添加注释数据,而不是我的两步方法。