Spring: 当有多个解析器时如何解析一个属性?
Spring: How to Resolve a Property When There Are Multiple Resolvers?
我们正在使用 Spring 云框架构建多个微服务。其中一项服务依赖于一些遗留共享库,并为 bean 配置导入各种 XML 文件。我们面临的问题是,通过这些导入,引入了多个 属性 解析器,因此 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;
}
为了回答我自己的问题,我使用 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);
}
}
我们正在使用 Spring 云框架构建多个微服务。其中一项服务依赖于一些遗留共享库,并为 bean 配置导入各种 XML 文件。我们面临的问题是,通过这些导入,引入了多个 属性 解析器,因此 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;
}
为了回答我自己的问题,我使用 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);
}
}