Spring Boot自定义配置属性源(PropertySource)
作者:曹书豪
配置覆盖优于profile
在生产实践中,配置覆盖是解决不同环境不同配置的常用方法。比如用生产服务器上的配置文件覆盖包内的文件,或者使用中心化的配置服务来覆盖默认的业务配置。
相比于profile机制(比如maven的profile、spring boot的profile-specific properties),即不同环境使用不同的配置文件,覆盖的方式更有优势。程序员在开发时不需要关心生产环境数据库的地址、账号等信息,一次构建即可在不同环境中运行,而profile机制需要将生产环境的配置写到项目资源文件中,而且要为不同环境使用不同的构建参数或者运行参数。
Spring提供了灵活的配置扩展能力,有多种方式将自定义的属性源,将集成进来,可以轻松地实现配置覆盖。
本文基于Spring Boot 1.4.8/Spring 4.3.12编写
使用@PropertySource注解实现自定义配置文件和配置覆盖
@ConfigurationProperties @Configuration public class DemoProperties { // properties with getter/setters }
@PropertySource(value = { "test.properties", "file:/etc/test.properties", }, ignoreResourceNotFound = true) @Configuration public class DemoAutoConfiguration { @Autowired private DemoProperties demoProperties; @PostConstruct public void init() { System.out.println(demoProperties); } }
Spring支持使用PropertySource注解引入自定义配置文件,其中"test.properties"将使Spring从classpath下加载该文件,"file:/etc/test.properties"将使Spring从文件系统加载/etc/test.properties文件,ignoreResourceNotFound = true使Spring忽略文件加载失败的异常,即配置文件是可选的。
同时,由于"file:/etc/test.properties"位于"test.properties"之后,这使得文件系统的配置文件可以覆盖classpath下的配置。
自定义属性源工厂
如果想要更加灵活的自定义属性源,比如实现从中心化的配置服务加载配置,可以通过实现PropertySourceFactory接口,并通过配置PropertySource注解的factory参数来实现。
@Configuration @PropertySource(value = ""/*placeholder*/, factory = CompositePropertySourceFactory.class) public class CompositeConfigAutoConfiguration { }
value字段用于指定配置源对应的资源文件,如果不需要使用资源文件,可以配置为任意值,参数值将会被传递到factory参数的createPropertySource方法。
如果ignoreResourceNotFound字段指定为true,那么factory抛出的异常将被忽略,否则将导致启动失败。有的时候,直接把启动失败暴露出来不失为一种好的做法。
PropertySourceFactory接口的定义如下:
/** * Strategy interface for creating resource-based {@link PropertySource} wrappers. * * @author Juergen Hoeller * @since 4.3 * @see DefaultPropertySourceFactory */ public interface PropertySourceFactory { /** * Create a {@link PropertySource} that wraps the given resource. * @param name the name of the property source * @param resource the resource (potentially encoded) to wrap * @return the new {@link PropertySource} (never {@code null}) * @throws IOException if resource resolution failed */ PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException; }
需要注意的是PropertySourceFactory的加载时机早于Spring Beans容器,因此实现上不能依赖于Spring的IOC。
PropertySourceFactory要求实现类返回PropertySource。PropertySource是Spring属性(或者说配置)功能的核心接口,有很多实现,比如:
- ResourcePropertySource 从Resource加载PropertySource
- PropertiesPropertySource 从properties文件加载PropertySource
- SystemEnvironmentPropertySource 从系统环境变量加载PropertySource
- MapPropertySource 包装一个Map为PropertySource(Adapter模块)
- CompositePropertySource 支持将若干PropertySource进行组合(Composite模式)
实际实现类远不如这些,具体的可以阅读Spring文档或源码。
在自定义属性源时比较常用的是MapPropertySource和CompositePropertySource。
MapPropertySource可以用于将自己加载的属性数据包装,参考其构造方法。
public MapPropertySource(String name, Map<String, Object> source) { super(name, source); }
后者可以通过组合装载多个属性源并自定义覆盖顺序。例如:
PropertySource<?> packageInsidePropertySource = packageInsidePropertySourceIterateLoader.loadPropertySource(compositePropertySource); compositePropertySource.addPropertySource(packageInsidePropertySource); PropertySource<?> outerFilePropertySource = outerFilePropertySourceIterateLoader.loadPropertySource(compositePropertySource); // 优先级高于前者 compositePropertySource.addFirstPropertySource(outerFilePropertySource);
addFirstPropertySource方法可以设置传入的PropertySource为最高优先级(在此CompositePropertySource内部),addPropertySource方法则相反,放在后面的优先级更低。
加载依赖jar包中所有同名配置文件
直接从classpath加载配置文件,要求文件必须存在于classpath中。考虑在WEB项目中,如果文件存在于某个依赖的jar包中,即位于WEB-INF/lib/xxx.jar中,此时基于classpath无法直接加载。此时可以使用Spring提供的PathMatchingResourcePatternResolver,按资源名称扫描所有jar包来实现目的。
private List<Resource> getPackageInsideResourcesByPattern(String resourceName) throws IOException { String resourcePathPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + HbootConfigConstants.CONFIGS + resourceName; ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); return Arrays.asList(resourcePatternResolver.getResources(resourcePathPattern)); }
然后就可以使用ResourcePropertySource从Resource构建PropertySource传给Spring。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。