由于我引入了spring-boot-starter-webflux依赖,经过了getOrCreateEnvironment方法后得到了一个ApplicationReactiveWebEnvironment
configureEnvironment方法按照environment.setConversionService -> configurePropertySources -> configureProfiles的顺序分别对属性源或配置文件进行细粒度控制
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
addConversionService是SpringApplication的成员字段,默认值为true。ConfigurableEnvironment的void setConversionService(ConfigurableConversionService conversionService)方法继承于ConfigurablePropertyResolver接口, 该接口是PropertyResolver类型都将实现的配置接口。提供用于访问和自定义将属性值从一种类型转换为另一种类型时使用的ConversionService的工具。PropertyResolver是用于针对任何底层源解析属性的接口。
public interface PropertyResolver {
/**
* 返回给定的属性键是否可用于解析,即给定键的值是否不为null。
*/
boolean containsProperty(String key);
/**
* 返回与给定键关联的属性值,如果无法解析键,则返回null。
* @param key 要解析的属性名称
*/
@Nullable
String getProperty(String key);
/**
* 返回与给定键关联的属性值,如果无法解析键,则返回defaultValue。
* @param key 要解析的属性名称
* @param defaultValue 如果没有找到值,则返回默认值
*/
String getProperty(String key, String defaultValue);
/**
* 返回与给定键关联的属性值,如果无法解析键,则返回null。
* @param key 要解析的属性名称
* @param targetType 属性值的预期类型
*/
@Nullable
<T> T getProperty(String key, Class<T> targetType);
/**
* 返回与给定键关联的属性值,如果无法解析键,则返回defaultValue 。
* @param key 要解析的属性名称
* @param targetType 属性值的预期类型
* @param defaultValue 如果没有找到值,则返回默认值
*/
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
/**
* 返回与给定键关联的属性值(从不为null)
* @throws IllegalStateException 如果无法解析给定的key
*/
String getRequiredProperty(String key) throws IllegalStateException;
/**
* 返回与给定键关联的属性值,转换为给定的 targetType(从不为null )。
* @throws IllegalStateException 如果无法解析给定的key
*/
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* 解析给定文本中的 ${...} 占位符,用getProperty解析的相应属性值替换它们。
* 没有默认值的不可解析占位符将被忽略并保持不变。
* @param text 要解析的字符串
* @return 解析的字符串(从不为null )
* @throws IllegalArgumentException 如果给定字符串为null
*/
String resolvePlaceholders(String text);
/**
* 解析给定文本中的 ${...} 占位符,用getProperty解析的相应属性值替换它们。
* 没有默认值的不可解析占位符将导致抛出 IllegalArgumentException。
* @throws IllegalArgumentException 如果给定字符串为null或任何占位符无法解析
*/
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
ConfigurablePropertyResolver的void setConversionService(ConfigurableConversionService conversionService)方法设置对属性执行类型转换时要使用的ConfigurableConversionService 。
AbstractEnvironment具体实现了setConversionService方法:
public void setConversionService(ConfigurableConversionService conversionService) {
this.propertyResolver.setConversionService(conversionService);
}
ApplicationConversionService是FormattingConversionService的一种特殊化,默认配置了适用于大多数 Spring Boot 应用程序的转换器和格式化程序。ApplicationConversionService专为直接实例化而设计,但也公开了静态addApplicationConverters和addApplicationFormatters(FormatterRegistry)实用程序方法,以便针对注册表实例临时使用。ApplicationConversionService构造方法如下:
public ApplicationConversionService() {
this(null);
}
public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
this(embeddedValueResolver, false);
}
private ApplicationConversionService(StringValueResolver embeddedValueResolver, boolean unmodifiable) {
if (embeddedValueResolver != null) {
setEmbeddedValueResolver(embeddedValueResolver);
}
configure(this);
this.unmodifiable = unmodifiable;
}
configure(this):
/**
* 使用适用于大多数SpringBoot应用程序的格式化程序和转换器配置给定的FormatterRegistry。
* @param registry 要添加到的转换器的注册表
* @throws ClassCastException 如果给定的 FormatterRegistry 无法转换为 ConversionService
*/
public static void configure(FormatterRegistry registry) {
DefaultConversionService.addDefaultConverters(registry);
DefaultFormattingConversionService.addDefaultFormatters(registry);
addApplicationFormatters(registry);
addApplicationConverters(registry);
}
DefaultConversionService提供了默认情况下使用适用于大多数环境的转换器或者格式化服务,其addDefaultConverters源码如下:
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
converterRegistry.addConverter(new ObjectToObjectConverter());
converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new FallbackObjectToStringConverter());
converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}
从类名称可以看出每个转换器的作用,比如StringToTimeZoneConverter可以将将String转换为TimeZone 的,其源码如下:
class StringToTimeZoneConverter implements Converter<String, TimeZone> {
@Override
public TimeZone convert(String source) {
return StringUtils.parseTimeZoneString(source);
}
}
有些转换器会实现通过实现ConditionalConverter接口来实现条件匹配:
public interface ConditionalConverter {
/**
* 是否应该选择当前正在考虑的从sourceType到targetType的转换?
* @param sourceType 转换源的字段的类型描述符
* @param targetType 转换目标的字段的类型描述符
* @return true 如果应该执行转换,则为 true,否则为 false
*/
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
DefaultFormattingConversionService是FormattingConversionService的一种特殊化,默认配置为适用于大多数应用程序的转换器和格式化程序。DefaultFormattingConversionService的addDefaultFormatters可添加适用于大多数环境的格式化程序:包括数字格式化程序、JSR-354 货币和货币格式化程序、JSR-310 日期时间和/或 Joda-Time 格式化程序,具体取决于类路径中相应 API 的存在。
public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
// Default handling of number values
formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Default handling of monetary values
if (jsr354Present) {
formatterRegistry.addFormatter(new CurrencyUnitFormatter());
formatterRegistry.addFormatter(new MonetaryAmountFormatter());
formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
}
// Default handling of date-time values
// just handling JSR-310 specific date and time types
new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);
if (jodaTimePresent) {
// handles Joda-specific types as well as Date, Calendar, Long
new org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
}
else {
// regular DateFormat-based Date, Calendar, Long converters
new DateFormatterRegistrar().registerFormatters(formatterRegistry);
}
}
addApplicationFormatters方法添加对大多数 Spring Boot 应用程序有用的格式化程序。
public static void addApplicationFormatters(FormatterRegistry registry) {
registry.addFormatter(new CharArrayFormatter());
registry.addFormatter(new InetAddressFormatter());
registry.addFormatter(new IsoOffsetFormatter());
}
以InetAddressFormatter为例,提供给定主机名返回IP地址的功能:
final class InetAddressFormatter implements Formatter<InetAddress> {
@Override
public String print(InetAddress object, Locale locale) {
return object.getHostAddress();
}
@Override
public InetAddress parse(String text, Locale locale) throws ParseException {
try {
return InetAddress.getByName(text);
}
catch (UnknownHostException ex) {
throw new IllegalStateException("Unknown host " + text, ex);
}
}
}
addApplicationConverters方法添加对大多数 Spring Boot 应用程序有用的转换器。
public static void addApplicationConverters(ConverterRegistry registry) {
addDelimitedStringConverters(registry);
registry.addConverter(new StringToDurationConverter());
registry.addConverter(new DurationToStringConverter());
registry.addConverter(new NumberToDurationConverter());
registry.addConverter(new DurationToNumberConverter());
registry.addConverter(new StringToPeriodConverter());
registry.addConverter(new PeriodToStringConverter());
registry.addConverter(new NumberToPeriodConverter());
registry.addConverter(new StringToDataSizeConverter());
registry.addConverter(new NumberToDataSizeConverter());
registry.addConverter(new StringToFileConverter());
registry.addConverter(new InputStreamSourceToByteArrayConverter());
registry.addConverterFactory(new LenientStringToEnumConverterFactory());
registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
if (registry instanceof ConversionService) {
addApplicationConverters(registry, (ConversionService) registry);
}
}