Spring:如果有多个解析器,如何解析属性?

时间:2016-05-18 02:58:39

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

我们正在使用Spring Cloud框架构建多个微服务。其中一个服务依赖于某些遗留共享库,并导入各种XML文件以进行bean配置。我们面临的问题是,通过这些导入,会引入多个属性解析器,因此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;
}

1 个答案:

答案 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);
    }
}