SpringBoot外部化配置使用Plus版 (3)

@PropertySource是在refreshContext阶段,执行BeanDefinitionRegistryPostProcessor时处理的

// org.springframework.context.annotation.ConfigurationClassParser#processPropertySource private void processPropertySource(AnnotationAttributes propertySource) throws IOException { String name = propertySource.getString("name"); if (!StringUtils.hasLength(name)) { name = null; } String encoding = propertySource.getString("encoding"); if (!StringUtils.hasLength(encoding)) { encoding = null; } // 获取配置的文件路径 String[] locations = propertySource.getStringArray("value"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); // 指定的读取配置文件的工厂 Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory"); // 没有就用默认的 PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass)); // 循环加载 for (String location : locations) { try { // 会解析存在占位符的情况 String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); // 使用DefaultResourceLoader来加载资源 Resource resource = this.resourceLoader.getResource(resolvedLocation); // 创建PropertySource对象 addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) { // Placeholders not resolvable or resource not found when trying to open it if (ignoreResourceNotFound) { if (logger.isInfoEnabled()) { logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); } } else { throw ex; } } } }

因为执行时机的问题,有些配置不能使用@PropertySource,因为这个时候对有些配置来说,如果使用这种配置方式,黄花菜都凉了。同时这个注解要配合@Configuration注解一起使用才能生效,使用@Component是不行的。

处理@ConfigurationProperty的处理器是一个BeanPostProcessor,处理@Value的也是一个BeanPostProcessor,不过他俩的优先级并不一样,

// @ConfigurationProperty public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean { @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 1; } } // @Value public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware { private int order = Ordered.LOWEST_PRECEDENCE - 2; @Override public int getOrder() { return this.order; } }

从上面可以看出处理@ConfigurationProperty的BeanPostProcessor优先级很高,而@Value的BeanPostProcessor优先级很低。

使用@Value注入时,要求配置的key必须存在于Environment中的,否则会终止启动,而@ConfigurationProperties则不会。

@Value可以支持SpEL表达式,也支持占位符的方式。

自定义配置读取

org.springframework.boot.context.config.ConfigFileApplicationListener是一个监听器,同时也是一个EnvironmentPostProcessor,在有ApplicationEnvironmentPreparedEvent事件触发时,会去处理所有的EnvironmentPostProcessor的实现类,同时这些个实现也是使用SpringFactoriesLoader的方式来加载的。

对于配置文件的读取,就是使用的这种方式。

@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }

有了这个扩展点后,我们就能自己定义读取任何配置,从任何地方。

只要实现了EnvironmentPostProcessor接口,并且在META-INF/spring.factories中配置一下

org.springframework.boot.env.EnvironmentPostProcessor=com.example.configuration.ConfigurationFileLoader

附一个自己写的例子

public class ConfigurationFileLoader implements EnvironmentPostProcessor { private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "download"; private static final String DEFAULT_FILE_EXTENSION = ".yml"; @Override public void postProcessEnvironment (ConfigurableEnvironment environment, SpringApplication application) { List<String> list = Arrays.asList(StringUtils.trimArrayElements( StringUtils.commaDelimitedListToStringArray(DEFAULT_SEARCH_LOCATIONS))); Collections.reverse(list); Set<String> reversedLocationSet = new LinkedHashSet(list); ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean(); List<Properties> loadedProperties = new ArrayList<>(2); reversedLocationSet.forEach(location->{ Resource resource = defaultResourceLoader.getResource(location + DEFAULT_NAMES+DEFAULT_FILE_EXTENSION); if (resource == null || !resource.exists()) { return; } yamlPropertiesFactoryBean.setResources(resource); Properties properties = yamlPropertiesFactoryBean.getObject(); loadedProperties.add(properties); }); Properties filteredProperties = new Properties(); Set<Object> addedKeys = new LinkedHashSet<>(); for (Properties propertySource : loadedProperties) { for (Object key : propertySource.keySet()) { String stringKey = (String) key; if (addedKeys.add(key)) { filteredProperties.setProperty(stringKey, propertySource.getProperty(stringKey)); } } } PropertiesPropertySource propertySources = new PropertiesPropertySource(DEFAULT_NAMES, filteredProperties); environment.getPropertySources().addLast(propertySources); } }

基本上都是 参考ConfigFileApplicationListener写的 ,不过这里实现的功能,其实可以通过 @PropertySource来 解决,只是当时不知道。

使用@PropertySource的话,这么写 @PropertySource("file:./download.properties") 即可。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyydyg.html