我们正在使用Spring Cloud框架构建多个微服务。其中一个服务依赖于某些遗留共享库,并导入各种XML文件以进行bean配置。我们面临的问题是,通过这些导入,会引入多个属性解析器,因此AbstractBeanFactory中的以下代码无法解析spring.application.name
,因为该值以${spring.application.name:unknown}
形式出现,第一个解析器无法解析,因此将result
设置为unknown
。 embeddedValueResolver
确实有一个可以解析该属性的解析器,但由于该属性被前一个解析器设置为默认属性,因此它没有机会。这导致Eureka的服务注册因NPE而失败。
@Override
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}
答案 0 :(得分:0)
回答我自己的问题,我使用BeanDefinitionRegistryPostProcessor
解决了问题。相关的JIRA SPR-6428已被其他用户提交但已被关闭。
/**
* Removes {@link org.springframework.beans.factory.config.PropertyPlaceholderConfigurer} classes that come before
* {@link PropertySourcesPlaceholderConfigurer} and fail to resolve Spring Cloud properties, thus setting them to default.
* One such property is {@code spring.application.name} that gets set to 'unknown' thus causing registration with
* discovery service to fail. This class collects the {@code locations} from these offending
* {@code PropertyPlaceholderConfigurer} and later adds to the end of property sources available from
* {@link org.springframework.core.env.Environment}.
* <p>
* c.f. https://jira.spring.io/browse/SPR-6428
*
* @author Abhijit Sarkar
*/
@Component
@Slf4j
public class PropertyPlaceholderConfigurerPostProcessor implements BeanDefinitionRegistryPostProcessor {
private final Set<String> locations = new HashSet<>();
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
List<String> propertyPlaceholderConfigurers = Arrays.stream(beanDefinitionNames)
.filter(name -> name.contains("PropertyPlaceholderConfigurer"))
.collect(toList());
for (String name : propertyPlaceholderConfigurers) {
BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(name);
TypedStringValue location = (TypedStringValue) beanDefinition.getPropertyValues().get("location");
if (location != null) {
String value = location.getValue();
log.info("Found location: {}.", location);
/* Remove 'classpath:' prefix, if present. It later creates problem with reading the file. */
locations.add(removeClasspathPrefixIfPresent(value));
log.info("Removing bean definition: {}.", name);
beanDefinitionRegistry.removeBeanDefinition(name);
}
}
}
private String removeClasspathPrefixIfPresent(String location) {
int classpathPrefixIdx = location.lastIndexOf(':');
return classpathPrefixIdx > 0 ? location.substring(++classpathPrefixIdx) : location;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
PropertySourcesPlaceholderConfigurer configurer =
beanFactory.getBean(PropertySourcesPlaceholderConfigurer.class);
MutablePropertySources propertySources = getPropertySources(configurer);
locations.stream()
.map(locationToPropertySrc)
.forEach(propertySources::addLast);
}
private MutablePropertySources getPropertySources(PropertySourcesPlaceholderConfigurer configurer) {
/* I don't like this but PropertySourcesPlaceholderConfigurer has no getter for environment. */
Field envField = null;
try {
envField = PropertySourcesPlaceholderConfigurer.class.getDeclaredField("environment");
envField.setAccessible(true);
ConfigurableEnvironment env = (ConfigurableEnvironment) envField.get(configurer);
return env.getPropertySources();
} catch (ReflectiveOperationException e) {
throw new ApplicationContextException("Our little hack didn't work. Failed to read field: environment.", e);
}
}
Function<String, PropertySource> locationToPropertySrc = location -> {
ClassPathResource resource = new ClassPathResource(location);
try {
Properties props = PropertiesLoaderUtils.loadProperties(resource);
String filename = getFilename(location);
log.debug("Adding property source with name: {} and location: {}.", filename, location);
return new PropertiesPropertySource(filename, props);
} catch (IOException e) {
throw new ApplicationContextException(
String.format("Failed to read from location: %s.", location), e);
}
};
private String getFilename(String location) {
return location.substring(location.lastIndexOf('/') + 1);
}
}