我想我有一个相当复杂的配置结构,我无法开始工作。以下是配置类的重要部分:
@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。
答案 0 :(得分:1)
好吧,似乎我不知何故遇到了一个角落,Spring并没有做太多的事情。主要的问题是Spring似乎收集了可用的bean结构,包括它们的嵌套字段结构,然后才知道(或者至少使用)系统中存在的Converter
s。
我让@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能够做些什么来让它变得更容易,那就太好了......