【问题标题】:Spring: How to Resolve a Property When There Are Multiple Resolvers?Spring:当有多个解析器时如何解析一个属性?
【发布时间】:2016-05-22 15:15:59
【问题描述】:

我们正在使用 Spring Cloud 框架构建多个微服务。其中一项服务依赖于一些遗留共享库,并为 bean 配置导入各种 XML 文件。我们面临的问题是,通过这些导入,引入了多个属性解析器,因此AbstractBeanFactory 中的以下代码无法解析spring.application.name,因为该值以${spring.application.name:unknown} 的形式出现,第一个解析器无法解析并且因此将result 设置为unknownembeddedValueResolver 确实有一个解析器,它可以解析该属性,但由于该属性已被先前的解析器设置为默认值,所以它没有机会。这导致向 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;
}

【问题讨论】:

    标签: spring spring-mvc spring-cloud microservices netflix-eureka


    【解决方案1】:

    回答我自己的问题后,我使用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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-01-18
      • 1970-01-01
      • 2013-03-04
      • 1970-01-01
      • 2016-11-09
      • 2018-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多