【问题标题】:@NestedConfigurationProperty and Converter Doesn't Work@NestedConfigurationProperty 和转换器不起作用
【发布时间】:2016-08-24 06:30:04
【问题描述】:

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

@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。

【问题讨论】:

    标签: configuration spring-boot


    【解决方案1】:

    好吧,似乎我遇到了一个 Spring 没有做太多事情的极端情况。主要问题是 Spring 似乎在知道(或至少使用)系统中存在的Converters 之前收集了可用的 bean 结构,包括它们的嵌套字段结构。

    我让@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 可以对此做点什么以使其更容易,那就太好了...

    【讨论】:

      猜你喜欢
      • 2020-04-03
      • 2015-08-15
      • 2018-12-12
      • 1970-01-01
      • 2011-01-11
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      • 2018-03-26
      相关资源
      最近更新 更多