转自:https://www.cnblogs.com/throwable/p/9417827.html
前提
前面写过一篇关于Environment属性加载的源码分析和扩展,里面提到属性的占位符解析和类型转换是相对复杂的,这篇文章就是要分析和解读这两个复杂的问题。关于这两个问题,选用一个比较复杂的参数处理方法PropertySourcesPropertyResolver#getProperty
,解析占位符的时候依赖到PropertySourcesPropertyResolver#getPropertyAsRawString
:
1 protected String getPropertyAsRawString(String key) { 2 return getProperty(key, String.class, false); 3 } 4 5 protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { 6 if (this.propertySources != null) { 7 for (PropertySource<?> propertySource : this.propertySources) { 8 if (logger.isTraceEnabled()) { 9 logger.trace("Searching for key '" + key + "' in PropertySource '" + 10 propertySource.getName() + "'"); 11 } 12 Object value = propertySource.getProperty(key); 13 if (value != null) { 14 if (resolveNestedPlaceholders && value instanceof String) { 15 //解析带有占位符的属性 16 value = resolveNestedPlaceholders((String) value); 17 } 18 logKeyFound(key, propertySource, value); 19 //需要时转换属性的类型 20 return convertValueIfNecessary(value, targetValueType); 21 } 22 } 23 } 24 if (logger.isDebugEnabled()) { 25 logger.debug("Could not find key '" + key + "' in any property source"); 26 } 27 return null; 28 }
属性占位符解析
属性占位符的解析方法是PropertySourcesPropertyResolver的父类AbstractPropertyResolver#resolveNestedPlaceholders
:
1 protected String resolveNestedPlaceholders(String value) { 2 return (this.ignoreUnresolvableNestedPlaceholders ? 3 resolvePlaceholders(value) : resolveRequiredPlaceholders(value)); 4 }
ignoreUnresolvableNestedPlaceholders属性默认为false,可以通过AbstractEnvironment#setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders)
设置,当此属性被设置为true,解析属性占位符失败的时候(并且没有为占位符配置默认值)不会抛出异常,返回属性原样字符串,否则会抛出IllegalArgumentException。我们这里只需要分析AbstractPropertyResolver#resolveRequiredPlaceholders
:
1 //AbstractPropertyResolver中的属性: 2 //ignoreUnresolvableNestedPlaceholders=true情况下创建的PropertyPlaceholderHelper实例 3 @Nullable 4 private PropertyPlaceholderHelper nonStrictHelper; 5 6 //ignoreUnresolvableNestedPlaceholders=false情况下创建的PropertyPlaceholderHelper实例 7 @Nullable 8 private PropertyPlaceholderHelper strictHelper; 9 10 //是否忽略无法处理的属性占位符,这里是false,也就是遇到无法处理的属性占位符且没有默认值则抛出异常 11 private boolean ignoreUnresolvableNestedPlaceholders = false; 12 13 //属性占位符前缀,这里是"${" 14 private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; 15 16 //属性占位符后缀,这里是"}" 17 private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; 18 19 //属性占位符解析失败的时候配置默认值的分隔符,这里是":" 20 @Nullable 21 private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; 22 23 24 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { 25 if (this.strictHelper == null) { 26 this.strictHelper = createPlaceholderHelper(false); 27 } 28 return doResolvePlaceholders(text, this.strictHelper); 29 } 30 31 //创建一个新的PropertyPlaceholderHelper实例,这里ignoreUnresolvablePlaceholders为false 32 private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) { 33 return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders); 34 } 35 36 //这里最终的解析工作委托到PropertyPlaceholderHelper#replacePlaceholders完成 37 private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { 38 return helper.replacePlaceholders(text, this::getPropertyAsRawString); 39 }
最终只需要分析PropertyPlaceholderHelper#replacePlaceholders
,这里需要重点注意:
- 注意到这里的第一个参数text就是属性值的源字符串,例如我们需要处理的属性为myProperties: ${server.port}-${spring.application.name},这里的text就是${server.port}-${spring.application.name}。
- replacePlaceholders方法的第二个参数placeholderResolver,这里比较巧妙,这里的方法引用this::getPropertyAsRawString相当于下面的代码:
1 //PlaceholderResolver是一个函数式接口 2 @FunctionalInterface 3 public interface PlaceholderResolver { 4 @Nullable 5 String resolvePlaceholder(String placeholderName); 6 } 7 //this::getPropertyAsRawString相当于下面的代码 8 return new PlaceholderResolver(){ 9 10 @Override 11 String resolvePlaceholder(String placeholderName){ 12 //这里调用到的是PropertySourcesPropertyResolver#getPropertyAsRawString,有点绕 13 return getPropertyAsRawString(placeholderName); 14 } 15 }
接着看PropertyPlaceholderHelper#replacePlaceholders
的源码:
1 //基础属性 2 //占位符前缀,默认是"${" 3 private final String placeholderPrefix; 4 //占位符后缀,默认是"}" 5 private final String placeholderSuffix; 6 //简单的占位符前缀,默认是"{",主要用于处理嵌套的占位符如${xxxxx.{yyyyy}} 7 private final String simplePrefix; 8 9 //默认值分隔符号,默认是":" 10 @Nullable 11 private final String valueSeparator; 12 //替换属性占位符 13 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { 14 Assert.notNull(value, "'value' must not be null"); 15 return parseStringValue(value, placeholderResolver, new HashSet<>()); 16 } 17 18 //递归解析带占位符的属性为字符串 19 protected String parseStringValue( 20 String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { 21 StringBuilder result = new StringBuilder(value); 22 int startIndex = value.indexOf(this.placeholderPrefix); 23 while (startIndex != -1) { 24 //搜索第一个占位符后缀的索引 25 int endIndex = findPlaceholderEndIndex(result, startIndex); 26 if (endIndex != -1) { 27 //提取第一个占位符中的原始字符串,如${server.port}->server.port 28 String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); 29 String originalPlaceholder = placeholder; 30 //判重 31 if (!visitedPlaceholders.add(originalPlaceholder)) { 32 throw new IllegalArgumentException( 33 "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); 34 } 35 // Recursive invocation, parsing placeholders contained in the placeholder key. 36 // 递归调用,实际上就是解析嵌套的占位符,因为提取的原始字符串有可能还有一层或者多层占位符 37 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); 38 // Now obtain the value for the fully resolved key... 39 // 递归调用完毕后,可以确定得到的字符串一定是不带占位符,这个时候调用getPropertyAsRawString获取key对应的字符串值 40 String propVal = placeholderResolver.resolvePlaceholder(placeholder); 41 // 如果字符串值为null,则进行默认值的解析,因为默认值有可能也使用了占位符,如${server.port:${server.port-2:8080}} 42 if (propVal == null && this.valueSeparator != null) { 43 int separatorIndex = placeholder.indexOf(this.valueSeparator); 44 if (separatorIndex != -1) { 45 String actualPlaceholder = placeholder.substring(0, separatorIndex); 46 // 提取默认值的字符串 47 String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); 48 // 这里是把默认值的表达式做一次解析,解析到null,则直接赋值为defaultValue 49 propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); 50 if (propVal == null) { 51 propVal = defaultValue; 52 } 53 } 54 } 55 // 上一步解析出来的值不为null,但是它有可能是一个带占位符的值,所以后面对值进行递归解析 56 if (propVal != null) { 57 // Recursive invocation, parsing placeholders contained in the 58 // previously resolved placeholder value. 59 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); 60 // 这一步很重要,替换掉第一个被解析完毕的占位符属性,例如${server.port}-${spring.application.name} -> 9090--${spring.application.name} 61 result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); 62 if (logger.isTraceEnabled()) { 63 logger.trace("Resolved placeholder '" + placeholder + "'"); 64 } 65 // 重置startIndex为下一个需要解析的占位符前缀的索引,可能为-1,说明解析结束 66 startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); 67 } 68 else if (this.ignoreUnresolvablePlaceholders) { 69 // 如果propVal为null并且ignoreUnresolvablePlaceholders设置为true,直接返回当前的占位符之间的原始字符串尾的索引,也就是跳过解析 70 // Proceed with unprocessed value. 71 startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); 72 } 73 else { 74 // 如果propVal为null并且ignoreUnresolvablePlaceholders设置为false,抛出异常 75 throw new IllegalArgumentException("Could not resolve placeholder '" + 76 placeholder + "'" + " in value \"" + value + "\""); 77 } 78 // 递归结束移除判重集合中的元素 79 visitedPlaceholders.remove(originalPlaceholder); 80 } 81 else { 82 // endIndex = -1说明解析结束 83 startIndex = -1; 84 } 85 } 86 return result.toString(); 87 } 88 89 //基于传入的起始索引,搜索第一个占位符后缀的索引,兼容嵌套的占位符 90 private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { 91 //这里index实际上就是实际需要解析的属性的第一个字符,如${server.port},这里index指向s 92 int index = startIndex + this.placeholderPrefix.length(); 93 int withinNestedPlaceholder = 0; 94 while (index < buf.length()) { 95 //index指向"}",说明有可能到达占位符尾部或者嵌套占位符尾部 96 if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { 97 //存在嵌套占位符,则返回字符串中占位符后缀的索引值 98 if (withinNestedPlaceholder > 0) { 99 withinNestedPlaceholder--; 100 index = index + this.placeholderSuffix.length(); 101 } 102 else { 103 //不存在嵌套占位符,直接返回占位符尾部索引 104 return index; 105 } 106 } 107 //index指向"{",记录嵌套占位符个数withinNestedPlaceholder加1,index更新为嵌套属性的第一个字符的索引 108 else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { 109 withinNestedPlaceholder++; 110 index = index + this.simplePrefix.length(); 111 } 112 else { 113 //index不是"{"或者"}",则进行自增 114 index++; 115 } 116 } 117 //这里说明解析索引已经超出了原字符串 118 return -1; 119 } 120 121 //StringUtils#substringMatch,此方法会检查原始字符串str的index位置开始是否和子字符串substring完全匹配 122 public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { 123 if (index + substring.length() > str.length()) { 124 return false; 125 } 126 for (int i = 0; i < substring.length(); i++) { 127 if (str.charAt(index + i) != substring.charAt(i)) { 128 return false; 129 } 130 } 131 return true; 132 }
上面的过程相对比较复杂,因为用到了递归,我们举个实际的例子说明一下整个解析过程,例如我们使用了四个属性项,我们的目标是获取server.desc的值:
application.name=spring server.port=9090 spring.application.name=${application.name} server.desc=${server.port-${spring.application.name}}:${description:"hello"}
属性类型转换
在上一步解析属性占位符完毕之后,得到的是属性字符串值,可以把字符串转换为指定的类型,此功能由AbstractPropertyResolver#convertValueIfNecessary
完成:
1 protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) { 2 if (targetType == null) { 3 return (T) value; 4 } 5 ConversionService conversionServiceToUse = this.conversionService; 6 if (conversionServiceToUse == null) { 7 // Avoid initialization of shared DefaultConversionService if 8 // no standard type conversion is needed in the first place... 9 // 这里一般只有字符串类型才会命中 10 if (ClassUtils.isAssignableValue(targetType, value)) { 11 return (T) value; 12 } 13 conversionServiceToUse = DefaultConversionService.getSharedInstance(); 14 } 15 return conversionServiceToUse.convert(value, targetType); 16 }
实际上转换的逻辑是委托到DefaultConversionService的父类方法GenericConversionService#convert
:
1 public <T> T convert(@Nullable Object source, Class<T> targetType) { 2 Assert.notNull(targetType, "Target type to convert to cannot be null"); 3 return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); 4 } 5 6 public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { 7 Assert.notNull(targetType, "Target type to convert to cannot be null"); 8 if (sourceType == null) { 9 Assert.isTrue(source == null, "Source must be [null] if source type == [null]"); 10 return handleResult(null, targetType, convertNullSource(null, targetType)); 11 } 12 if (source != null && !sourceType.getObjectType().isInstance(source)) { 13 throw new IllegalArgumentException("Source to convert from must be an instance of [" + 14 sourceType + "]; instead it was a [" + source.getClass().getName() + "]"); 15 } 16 // 从缓存中获取GenericConverter实例,其实这一步相对复杂,匹配两个类型的时候,会解析整个类的层次进行对比 17 GenericConverter converter = getConverter(sourceType, targetType); 18 if (converter != null) { 19 // 实际上就是调用转换方法 20 Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType); 21 // 断言最终结果和指定类型是否匹配并且返回 22 return handleResult(sourceType, targetType, result); 23 } 24 return handleConverterNotFound(source, sourceType, targetType); 25 }
上面所有的可用的GenericConverter的实例可以在DefaultConversionService的addDefaultConverters
中看到,默认添加的转换器实例已经超过20个,有些情况下如果无法满足需求可以添加自定义的转换器,实现GenericConverter接口添加进去即可。
小结
SpringBoot在抽象整个类型转换器方面做的比较好,在SpringMVC应用中,采用的是org.springframework.boot.autoconfigure.web.format.WebConversionService,兼容了Converter、Formatter、ConversionService等转换器类型并且对外提供一套统一的转换方法。
标签:类型转换,index,return,String,value,占位,null,SpringBoot From: https://www.cnblogs.com/fnlingnzb-learner/p/16800621.html