@NestedConfigurationProperty和转换器不起作用

时间:2016-04-29 07:43:36

标签: configuration spring-boot

我想我有一个相当复杂的配置结构,我无法开始工作。以下是配置类的重要部分:

@ConfigurationProperties
public abstract class AbstractConfigHolder<T extends AbstractComponentConfig> {

}

@Component
public class ExportConfigHolder extends AbstractConfigHolder<GenericExportConfig> {

  @NestedConfigurationProperty
  private Map<String, GenericExportConfig> exports;

  // getters and setters for all fields

}

public class GenericExportConfig extends AbstractComponentConfig {

  @NestedConfigurationProperty
  private AbstractLocatedConfig target;

  // getters and setters for all fields

}

public abstract class AbstractLocatedConfig extends RemoteConfig {

  @NestedConfigurationProperty
  private ProxyConfig proxy;

  // getters and setters for all fields

}

public class ProxyConfig extends RemoteConfig {

  private Type type;

  // getters and setters for all fields

}

public class RemoteConfig {

  private String host;
  private int port;
  private String user;
  private String password;

  // getters and setters for all fields

}

这是属性文件:

exports.mmkb.name=MMKB
exports.mmkb.target=ftp
exports.mmkb.target.path=${user.home}/path/blah
# throws an exception:
exports.mmkb.target.proxy.host=super-host

转换内容是恕我直言应该涵盖所有内容并为Spring提供适当的bean:

@Configuration
public class ConversionSupport {

  @ConfigurationPropertiesBinding
  @Bean
  public Converter<String, AbstractLocatedConfig> locatedConfigConverter(ApplicationContext applicationContext) {
    return new Converter<String, AbstractLocatedConfig>() {

      private ProxyConfigs proxyConfigs;
      private ConnectionConfigs connectionConfigs;

      @Override
      public AbstractLocatedConfig convert(String targetType) {
        System.out.println("Converting " + targetType);
        initFields(applicationContext);
        switch (targetType.toLowerCase()) {
          case "ftp":
            return new FtpTargetConfig(proxyConfigs, connectionConfigs);
          // others...
        }
      }

      // This is necessary to avoid conflicts in bean dependencies
      private void initFields(ApplicationContext applicationContext) {
        if (proxyConfigs == null) {
          AbstractConfigHolder<?> configHolder = applicationContext.getBean(AbstractConfigHolder.class);
          proxyConfigs = configHolder.getProxy();
          connectionConfigs = configHolder.getConnection();
        }
      }

    };
  }

}

然而,我得到了这个:

Converting ftp
2016-04-29 09:33:23,900 WARN  [org.springframework.context.annotation.AnnotationConfigApplicationContext] [main] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exportConfigHolder': Could not bind properties to ExportConfigHolder (prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig@52066604]
2016-04-29 09:33:23,902 ERROR [org.springframework.boot.SpringApplication] [main] Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exportConfigHolder': Could not bind properties to ExportConfigHolder (prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig@52066604]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:339)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:289)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
    at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191)
    at at.a1.iap.epggw.exporter.Application.main(Application.java:23)
Caused by: org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig@52066604]
    at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:406)
    at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280)
    at org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanWrapper.setPropertyValue(RelaxedDataBinder.java:700)
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95)
    at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:834)
    at org.springframework.validation.DataBinder.doBind(DataBinder.java:730)
    at org.springframework.boot.bind.RelaxedDataBinder.doBind(RelaxedDataBinder.java:128)
    at org.springframework.validation.DataBinder.bind(DataBinder.java:715)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:269)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:241)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:334)
    ... 17 common frames omitted

我的意思是错误清楚地表明,到目前为止一切正常,有适当的对象,但不知何故,它无法进一步应用属性。我知道它既不是数组也不是List也不是Map,因为我希望它是POJO。

我可以在这做什么来完成这项工作?

这是Spring-boot 1.3.3 BTW。

1 个答案:

答案 0 :(得分:1)

好吧,似乎我不知何故遇到了一个角落,Spring并没有做太多的事情。主要的问题是Spring似乎收集了可用的bean结构,包括它们的嵌套字段结构,然后才知道(或者至少使用)系统中存在的Converters

我让@ConfigurationProperties的班级实施ApplicationContextAware和新方法

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    AnnotationConfigApplicationContext context = (AnnotationConfigApplicationContext) applicationContext;

    @SuppressWarnings("unchecked")
    Converter<String, AbstractLocatedConfig> locatedConfigSupport = context.getBean("locatedConfigConverter", Converter.class);

    :
  }

然后还在上下文环境中查找将触发转换过程的所有属性,手动调用转换并以此方式创建bean结构。

由于某种原因,Spring的以下生命周期内容导致并非所有属性都以bean结尾,这使我这样做:

@Configuration
public class SampleConfiguration {

  @Autowired
  private Environment environment;

  @Autowired
  private ClassWithTheConfigurationPropertiesAbove theBeanWithTheConfigurationPropertiesAbove;

  @PostConstruct
  void postConstruct() throws Exception {
    if (environment instanceof AbstractEnvironment) {
      MutablePropertySources sources = ((AbstractEnvironment) environment).getPropertySources();
      // This is a MUST since Spring calls the nested properties handler BEFORE
      // calling the conversion service on that field. Therefore, our converter
      // for AbstractLocatedConfigs is called too late the first time. A second
      // call will fill in the fields in the new objects and set the other ones
      // again, too.
      // See org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(String, Class<T>, boolean)
      // Note: in case Spring reorders this, the logic here won't be needed.
      setProperties(theBeanWithTheConfigurationPropertiesAbove, sources);
    } else {
      throw new IllegalArgumentException("The environment must be an " + AbstractEnvironment.class.getSimpleName());
    }
  }

  void setProperties(Object target, MutablePropertySources propertySources) {
    // org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget()
    // was the base for this. Go there for further logic if needed.
    RelaxedDataBinder dataBinder = new RelaxedDataBinder(target);
    dataBinder.bind(new MutablePropertyValues(getProperties(propertySources)));
  }

  public String getProperty(String propertyName) {
    return environment.getProperty(propertyName);
  }

  private Map<String, String> getProperties(MutablePropertySources propertySources) {
    Iterable<PropertySource<?>> iterable = () -> propertySources.iterator();
    return StreamSupport.stream(iterable.spliterator(), false)
        .map(propertySource -> {
          Object source = propertySource.getSource();
          if (source instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, String> sourceMap = (Map<String, String>) source;
            return sourceMap.keySet();
          } else if (propertySource instanceof SimpleCommandLinePropertySource) {
            return Arrays.asList(((SimpleCommandLinePropertySource) propertySource).getPropertyNames());
          } else if (propertySource instanceof RandomValuePropertySource) {
            return null;
          } else {
            throw new NotImplementedException("unknown property source " + propertySource.getClass().getName() + " or its source " + source.getClass().getName());
          }
        })
        .filter(Objects::nonNull)
        .flatMap(Collection::stream)
        .collect(Collectors.toMap(Function.identity(), this::getProperty));
  }

}

如果Spring能够做些什么来让它变得更容易,那就太好了......