用API获取property 的值在springboot 1.x版本中,通常从Environment对象读取
//判断是否包含键值
boolean containsProperty(String key);
//获取属性值,如果获取不到返回null
String getProperty(String key);
//获取属性值,如果获取不到返回缺省值
String getProperty(String key, String defaultValue);
//获取属性对象;其转换和Converter有关,会根据sourceType和targetType查找转换器
<T> T getProperty(String key, Class<T> targetType);
但是这样的方式功能有限,还需要自己进行字符串到对象的转换。
Binder
Springboot 2.x新引入的类,负责处理对象与多个ConfigurationPropertySource(属性)之间的绑定,比Environment类好用很多,可以非常方便地进行类型转换,以及提供回调方法介入绑定的各个阶段进行深度定制。
//绑定对象
MailPropertiesC propertiesC = Binder.get(environment) //首先要绑定配置器
//再将属性绑定到对象上
.bind( "binder.sample.test", Bindable.of(MailPropertiesC.class) ).get(); //再获取实例
//绑定Map
Map<String,Object> propMap = Binder.get(environment)
.bind( "fish.jdbc.datasource",Bindable.mapOf(String.class, Object.class) ).get();
//绑定List
List<String> list = Binder.get(environment)
.bind( "binder.sample.list",Bindable.listOf(String.class) ).get();
//转换以及默认值
String datestr = (String) Binder.get(environment)
.bind( "binder.sample.date",Bindable.of(String.class) )
//映射为大写
.map(String::toUpperCase)
//默认值
.orElse("not date string");
//绑定过程回调函数,高度定制
LocalDate str = Binder.get(environment)
.bind("binder.sample.date", Bindable.of(LocalDate.class), new BindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name,
Bindable<T> target, BindContext context) {
log.info("绑定开始{}",name);
return target;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
log.info("绑定成功{}",target.getValue());
return result;
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
log.info("绑定失败{}",name);
return "没有找到匹配的属性";
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
log.info("绑定结束{}",name);
}
}).get();
那么我们看看Binder 是怎么工作的。org.springframework.boot.context.properties.bind.Binder
首先看和Environment的绑定。
public static Binder get(Environment environment) {
return get(environment, null);
}
/**
* Create a new {@link Binder} instance from the specified environment.
* @param environment the environment source (must have attached
* {@link ConfigurationPropertySources})
* @param defaultBindHandler the default bind handler to use if none is specified when
* binding
* @return a {@link Binder} instance
* @since 2.2.0
*/
public static Binder get(Environment environment, BindHandler defaultBindHandler) {
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);
return new Binder(sources, placeholdersResolver, null, null, defaultBindHandler);
}
之后是构造函数, 这时候初始化了一个类型转换Service conversionService
, 它的实例 ApplicationConversionService.getSharedInstance();
public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver,
ConversionService conversionService, Consumer<PropertyEditorRegistry> propertyEditorInitializer,
BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) {
Assert.notNull(sources, "Sources must not be null");
this.sources = sources;
this.placeholdersResolver = (placeholdersResolver != null) ? placeholdersResolver : PlaceholdersResolver.NONE;
this.conversionService = (conversionService != null) ? conversionService
: ApplicationConversionService.getSharedInstance();
this.propertyEditorInitializer = propertyEditorInitializer;
this.defaultBindHandler = (defaultBindHandler != null) ? defaultBindHandler : BindHandler.DEFAULT;
if (constructorProvider == null) {
constructorProvider = BindConstructorProvider.DEFAULT;
}
ValueObjectBinder valueObjectBinder = new ValueObjectBinder(constructorProvider);
JavaBeanBinder javaBeanBinder = JavaBeanBinder.INSTANCE;
this.dataObjectBinders = Collections.unmodifiableList(Arrays.asList(valueObjectBinder, javaBeanBinder));
}
请注意, ApplicationConversionService.getSharedInstance(); 不是在这个时候才被初始化的。而是SpringApplication 在启动的时候configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
在本文后面会介绍如何加一个converter service。 在Context 下面在bind 方法中构造了Context 对象,
Context() {
this.converter = BindConverter.get(Binder.this.conversionService, Binder.this.propertyEditorInitializer);
}
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(target, "Target must not be null");
handler = (handler != null) ? handler : this.defaultBindHandler;
Context context = new Context();
return bind(name, target, handler, context, false, create);
}
BindConverter 是重要的类型转换接口,它里面封装了一个CompositeConversionService 是TypeConverterConversionService,ApplicationConversionService 的组合。
private BindConverter(ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
Assert.notNull(conversionService, "ConversionService must not be null");
List<ConversionService> conversionServices = getConversionServices(conversionService,
propertyEditorInitializer);
this.conversionService = new CompositeConversionService(conversionServices);
}
private List<ConversionService> getConversionServices(ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
List<ConversionService> services = new ArrayList<>();
services.add(new TypeConverterConversionService(propertyEditorInitializer));
services.add(conversionService);
if (!(conversionService instanceof ApplicationConversionService)) {
services.add(ApplicationConversionService.getSharedInstance());
}
return services;
}
之后就寻找PropertyResource, 然后进行转换了。
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
boolean allowRecursiveBinding, boolean create) {
try {
Bindable<T> replacementTarget = handler.onStart(name, target, context);
if (replacementTarget == null) {
return handleBindResult(name, target, handler, context, null, create);
}
target = replacementTarget;
Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
return handleBindResult(name, target, handler, context, bound, create);
}
catch (Exception ex) {
return handleBindError(name, target, handler, context, ex);
}
}
private <T> T handleBindResult(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
Context context, Object result, boolean create) throws Exception {
if (result != null) {
result = handler.onSuccess(name, target, context, result);
result = context.getConverter().convert(result, target);
}
if (result == null && create) {
result = create(target, context);
result = handler.onCreate(name, target, context, result);
result = context.getConverter().convert(result, target);
Assert.state(result != null, () -> "Unable to create instance for " + target.getType());
}
handler.onFinish(name, target, context, result);
return context.getConverter().convert(result, target);
}
从上面代码可以看出,一个bind 基本由BindHandler来执行, 得出result由BindConverter 来转换类型。
Add a Customization ConverterService
如何在解析Property 时候来支持我们一个新的类型,比如给一个字符串,自动构建一个URL。从上文看到,我们可以从这段代码入手
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
我们应可以对environment里的conversionService,做个代理就可以实现增加一个conversion service了。
public class URLConfigurableConversionService implements ConfigurableConversionService {
private final ConfigurableConversionService m_delegate;
public RaptorIOConfigurableConversionService(ConfigurableConversionService delegate) {
m_delegate = delegate;
}
@Override
public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
m_delegate.addConverter(sourceType, targetType, converter);
}
@Override
public void addConverter(Converter<?, ?> converter) {
m_delegate.addConverter(converter);
}
@Override
public void addConverter(GenericConverter converter) {
m_delegate.addConverter(converter);
}
@Override
public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
m_delegate.addConverterFactory(converterFactory);
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
return m_delegate.canConvert(sourceType, targetType);
}
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
return m_delegate.canConvert(sourceType, targetType);
}
@Override
@SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) {
//only try to convert to URL in case of file existence case
if (targetType.equals(URL.class) && source instanceof String) {
try {
File file = new File((String) source); //NOSONAR
if (file.exists()) {
return (T) file.toURI().toURL();
}
} catch (Exception e) { //NOSONAR
//ignore it
}
}
return m_delegate.convert(source, targetType);
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return m_delegate.convert(source, sourceType, targetType);
}
@Override
public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
m_delegate.removeConvertible(sourceType, targetType);
}
}
实现一个自己的conversion service ,用environment 的conversion service作为代理, 在onApplicationEvent 时设置进去。
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
try {
ConfigurableEnvironment env = event.getEnvironment()
//setup raptorio file to URL conversion service
//to support "URL configFileUrl = getProperty(String key, URL.class)"
URLConfigurableConversionService conversionService = new URLConfigurableConversionService(env.getConversionService());
env.setConversionService(conversionService);
} catch (Exception e) {
logger.error("Failed to init environment variables.", e);
}
}