前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring读源码系列番外篇04----类型转换--上---老旧的PropertyEditor

Spring读源码系列番外篇04----类型转换--上---老旧的PropertyEditor

作者头像
大忽悠爱学习
发布2022-05-10 16:33:26
6440
发布2022-05-10 16:33:26
举报
文章被收录于专栏:c++与qt学习
  • AbstractPropertyBindingResult#findEditor()为属性寻找合适PropertyEditor的时候,若ConversionService能支持就包装为ConvertingPropertyEditorAdapter供以使用,这是适配器模式的典型应用场景。

PropertyEditorRegistry

提问: Spring是如何注册、管理这些转换器,以及如何自定义转换器去实现私有转换协议的呢?

xxxRegistry用于管理(注册、修改、删除、查找)一类组件,当组件类型较多时使用注册中心统一管理是一种非常有效的手段。诚然,PropertyEditor就属于这种场景,管理它们的注册中心是PropertyEditorRegistry。

它是管理PropertyEditor的中心接口,负责注册、查找对应的PropertyEditor。

代码语言:javascript
复制
public interface PropertyEditorRegistry {

    // 注册一个转换器:该type类型【所有的属性】都将交给此转换器去转换(即使是个集合类型)
    // 效果等同于调用下方法:registerCustomEditor(type,null,editor);
	void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
	// 注册一个转换器:该type类型的【propertyPath】属性将交给此转换器
	// 此方法是重点,详解见下文
	void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);
	// 查找到一个合适的转换器
	PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath);	
}

此接口的继承树如下:

值得注意的是:虽然此接口看似实现者众多,但其实其它所有的实现关于PropertyEditor的管理部分都是委托给PropertyEditorRegistrySupport来管理,无一例外。

因此,本文只需关注PropertyEditorRegistrySupport足矣,这为后面的高级应用(如数据绑定、BeanWrapper等)打好坚实基础。


PropertyEditorRegistrySupport

它是PropertyEditorRegistry接口的实现,提供对default editors和custom editors的管理,最终主要为BeanWrapperImpl和DataBinder服务。

一般来说,Registry注册中心内部会使用多个Map来维护,代表注册表。此处也不例外:

代码语言:javascript
复制
// 装载【默认的】编辑器们,初始化的时候会注册好
private Map<Class<?>, PropertyEditor> defaultEditors;
// 如果想覆盖掉【默认行为】,可通过此Map覆盖(比如处理Charset类型你不想用默认的编辑器处理)
// 通过API:overrideDefaultEditor(...)放进此Map里
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;

// ======================注册自定义的编辑器======================
// 通过API:registerCustomEditor(...)放进此Map里(若没指定propertyPath)
private Map<Class<?>, PropertyEditor> customEditors;
// 通过API:registerCustomEditor(...)放进此Map里(若指定了propertyPath)
private Map<String, CustomEditorHolder> customEditorsForPath;

PropertyEditorRegistrySupport使用了4个 Map来维护不同来源的编辑器,作为查找的 “数据源”。

这4个Map可分为两大组,并且有如下规律:

  • 默认编辑器组:defaultEditors和overriddenDefaultEditors
    • overriddenDefaultEditors优先级 高于 defaultEditors
  • 自定义编辑器组:customEditors和customEditorsForPath
    • 它俩为互斥关系
代码语言:javascript
复制
private Map<Class<?>, PropertyEditor> customEditorCache;

从属性名上理解,它表示customEditors属性的缓存。那么问题来了:customEditors和customEditorCache的数据结构一毛一样(都是Map),谈何缓存呢?直接从customEditors里获取值不更香吗?


customEditorCache作用解释

customEditorCache用于缓存自定义的编辑器,辅以成员属性customEditors属性一起使用。具体(唯一)使用方式在私有方法:根据类型获取自定义编辑器PropertyEditorRegistrySupport#getCustomEditor

代码语言:javascript
复制
@Nullable
	private PropertyEditor getCustomEditor(@Nullable Class<?> requiredType) {
		if (requiredType == null || this.customEditors == null) {
			return null;
		}
		//尝试从自定义属性编辑器集合中取出requiredType对应的转换器
		PropertyEditor editor = this.customEditors.get(requiredType);
		//重点:若customEditors没有并不代表处理不了,因为还得考虑父子关系、接口关系
		if (editor == null) {
			// 去缓存里查询,是否存在父子类作为key的情况
			if (this.customEditorCache != null) {
				editor = this.customEditorCache.get(requiredType);
			}
			// 若缓存没命中,就得遍历customEditors了,时间复杂度为O(n)
			if (editor == null) {
				// Find editor for superclass or interface.
				for (Iterator<Class<?>> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) {            
				//判断父子关系---这个比较烦人--因此需要缓存来帮忙   
					Class<?> key = it.next();
					if (key.isAssignableFrom(requiredType)) {
						editor = this.customEditors.get(key);
						// Cache editor for search type, to avoid the overhead
						// of repeated assignable-from checks.
						if (this.customEditorCache == null) {
							this.customEditorCache = new HashMap<>();
						}
						//对存在父子关系的转换过程进行缓存
						this.customEditorCache.put(requiredType, editor);
					}
				}
			}
		}
		return editor;
	}

customEditorsForPath作用解释

customEditorsForPath的作用是能够实现更精准匹配,针对属性级别精准处理。此Map的值通过此API注册进来:

代码语言:javascript
复制
public void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);

可能你会想,有了customEditors为何还需要customEditorsForPath呢?这里就不得不说两者的最大区别了:

  • customEditors:粒度较粗,通用性强。key为类型,即该类型的转换全部交给此编辑器处理
    • 如:registerCustomEditor(UUID.class,newUUIDEditor()),那么此编辑器就能处理全天下所有的String <-> UUID 转换工作
  • customEditorsForPath:粒度细精确到属性(字段)级别,有点专车专座的意思
    • 如:registerCustomEditor(Person.class, “cat.uuid” , new UUIDEditor()),那么此编辑器就有且仅能处理Person.cat.uuid属性,其它的一概不管

有了这种区别,注册中心在findCustomEditor(requiredType,propertyPath)匹配的时候也是按照优先级顺序执行匹配的:

  • 若指定了propertyPath(不为null),就先去customEditorsForPath里找。否则就去customEditors里找
  • 若没有指定propertyPath(为null),就直接去customEditors里找
代码语言:javascript
复制
	public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
		Class<?> requiredTypeToUse = requiredType;
		if (propertyPath != null) {
			if (this.customEditorsForPath != null) {
				// Check property-specific editor first.
				PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
				if (editor == null) {
					List<String> strippedPaths = new ArrayList<>();
					addStrippedPropertyPaths(strippedPaths, "", propertyPath);
					for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
						String strippedPath = it.next();
						editor = getCustomEditor(strippedPath, requiredType);
					}
				}
				if (editor != null) {
					return editor;
				}
			}
			if (requiredType == null) {
				requiredTypeToUse = getPropertyType(propertyPath);
			}
		}
		// No property-specific editor -> check type-specific editor.
		return getCustomEditor(requiredTypeToUse);
	}
代码语言:javascript
复制
	private PropertyEditor getCustomEditor(String propertyName, @Nullable Class<?> requiredType) {
		CustomEditorHolder holder =
				(this.customEditorsForPath != null ? this.customEditorsForPath.get(propertyName) : null);
		return (holder != null ? holder.getPropertyEditor(requiredType) : null);
	}

customEditorsForPath相当于给你留了钩子,当你在某些特殊情况需要特殊照顾的时候,你可以借助它来搞定,十分的方便。

此方式有必要记住并且尝试,在实际开发中使用得还是比较多的。特别在你不想全局定义,且要确保向下兼容性的时候,使用抽象接口类型 + 此种方式缩小影响范围将十分有用

说明:propertyPath不仅支持Java Bean导航方式,还支持集合数组方式,如Person.cats0.uuid这样格式也是ok的


PropertyEditorRegistrySupport源码
代码语言:javascript
复制
//注册中心的实现类---负责管理,删除,增加PropertyEditor,和查找指定的转换器
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {

	/**
由 spring.xml.ignore 系统属性控制的布尔标志,指示 Spring 忽略 XML,即不初始化与 XML 相关的基础结构。
默认为false
	 */
	private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore");

//转换器服务类---负责具体的转换操作,和查找,增加,删除convert
	@Nullable
	private ConversionService conversionService;

//是否激活默认的属性编译器---如果不进行激活,那么当我们尝试去
//获取一个默认属性编辑器的时候,会返回null
	private boolean defaultEditorsActive = false;

//同上---如果不进行激活,当尝试去获取configValueEditor时,返回null
	private boolean configValueEditorsActive = false;

// ======================默认和覆盖默认的编辑器集合======================

//存放默认属性编辑器集合----会在初始化的时候注册好
	@Nullable
	private Map<Class<?>, PropertyEditor> defaultEditors;
//如果想覆盖掉【默认行为】,可通过此Map覆盖(比如处理Charset类型你不想用默认的编辑器处理)
	@Nullable
	private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;

// ======================自定义的编辑器集合======================

// 通过API:registerCustomEditor(...)放进此Map里(若没指定propertyPath)
	@Nullable
	private Map<Class<?>, PropertyEditor> customEditors;
// 通过API:registerCustomEditor(...)放进此Map里(若指定了propertyPath)
	@Nullable
	private Map<String, CustomEditorHolder> customEditorsForPath;

// ======================自定义编辑器缓存集合======================
//存放父子关系场景---下面大家看源码会明白
	@Nullable
	private Map<Class<?>, PropertyEditor> customEditorCache;


	/**
	 * Spring 3.0 使用ConversionService 替代 PropertyEditors.
	 */
	public void setConversionService(@Nullable ConversionService conversionService) {
		this.conversionService = conversionService;
	}


	@Nullable
	public ConversionService getConversionService() {
		return this.conversionService;
	}


	//---------------------------------------------------------------------
	// Management of default editors
	//---------------------------------------------------------------------

	/**
激活此注册表实例的默认编辑器,允许在需要时延迟注册默认编辑器。
	 */
	protected void registerDefaultEditors() {
		this.defaultEditorsActive = true;
	}

	/**
激活仅用于配置目的的配置值编辑器,例如 StringArrayPropertyEditor。
默认情况下,这些编辑器不会注册,因为它们通常不适合数据绑定目的。
当然,在任何情况下,您都可以通过 registerCustomEditor 单独注册它们。
	 */
	public void useConfigValueEditors() {
		this.configValueEditorsActive = true;
	}

	/**
使用给定的属性编辑器覆盖指定类型的默认编辑器。
请注意,这与注册自定义编辑器不同,因为该编辑器在语义上仍然是默认编辑器。 
ConversionService 将覆盖这样的默认编辑器,而自定义编辑器通常会覆盖 ConversionService
	 */
	public void overrideDefaultEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
		if (this.overriddenDefaultEditors == null) {
			this.overriddenDefaultEditors = new HashMap<>();
		}
		this.overriddenDefaultEditors.put(requiredType, propertyEditor);
	}

	/**
检索给定属性类型的默认编辑器(如果有)。如果默认编辑器处于活动状态,则延迟注册它们。
	 */
	@Nullable
	public PropertyEditor getDefaultEditor(Class<?> requiredType) {
	//如果默认编辑器不处于活动状态,直接返回null
		if (!this.defaultEditorsActive) {
			return null;
		}
		//先去看overriddenDefaultEditors集合
		if (this.overriddenDefaultEditors != null) {
		//如果覆盖默认属性编辑器集合不为空,直接返回
			PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
			if (editor != null) {
				return editor;
			}
		}
		//懒加载体现---第一次尝试去获取默认属性编辑器的时候
		if (this.defaultEditors == null) {
		//才会去注册默认的属性编辑器
			createDefaultEditors();
		}
		//尝试从默认属性编辑器中获取
		return this.defaultEditors.get(requiredType);
	}

	/**
	注册默认的属性编辑器
	 */
	private void createDefaultEditors() {
		this.defaultEditors = new HashMap<>(64);

		// Simple editors, without parameterization capabilities.
		// The JDK does not contain a default editor for any of these target types.
		this.defaultEditors.put(Charset.class, new CharsetEditor());
		this.defaultEditors.put(Class.class, new ClassEditor());
		this.defaultEditors.put(Class[].class, new ClassArrayEditor());
		this.defaultEditors.put(Currency.class, new CurrencyEditor());
		this.defaultEditors.put(File.class, new FileEditor());
		this.defaultEditors.put(InputStream.class, new InputStreamEditor());
		if (!shouldIgnoreXml) {
			this.defaultEditors.put(InputSource.class, new InputSourceEditor());
		}
		this.defaultEditors.put(Locale.class, new LocaleEditor());
		this.defaultEditors.put(Path.class, new PathEditor());
		this.defaultEditors.put(Pattern.class, new PatternEditor());
		this.defaultEditors.put(Properties.class, new PropertiesEditor());
		this.defaultEditors.put(Reader.class, new ReaderEditor());
		this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
		this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
		this.defaultEditors.put(URI.class, new URIEditor());
		this.defaultEditors.put(URL.class, new URLEditor());
		this.defaultEditors.put(UUID.class, new UUIDEditor());
		this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());

		// Default instances of collection editors.
		// Can be overridden by registering custom instances of those as custom editors.
		this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
		this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
		this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
		this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
		this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));

		// Default editors for primitive arrays.
		this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
		this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());

		// The JDK does not contain a default editor for char!
		this.defaultEditors.put(char.class, new CharacterEditor(false));
		this.defaultEditors.put(Character.class, new CharacterEditor(true));

		// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
		this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
		this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));

		// The JDK does not contain default editors for number wrapper types!
		// Override JDK primitive number editors with our own CustomNumberEditor.
		this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
		this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
		this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
		this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
		this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
		this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
		this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
		this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
		this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
		this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
		this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
		this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
		this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
		this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));

		// Only register config value editors if explicitly requested.
		//configValueEditorsActive处于激活状态时,才会去注册
		if (this.configValueEditorsActive) {
			StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
			this.defaultEditors.put(String[].class, sae);
			this.defaultEditors.put(short[].class, sae);
			this.defaultEditors.put(int[].class, sae);
			this.defaultEditors.put(long[].class, sae);
		}
	}

	/**
	 * Copy the default editors registered in this instance to the given target registry.
	 * @param target the target registry to copy to
	 */
	protected void copyDefaultEditorsTo(PropertyEditorRegistrySupport target) {
		target.defaultEditorsActive = this.defaultEditorsActive;
		target.configValueEditorsActive = this.configValueEditorsActive;
		target.defaultEditors = this.defaultEditors;
		target.overriddenDefaultEditors = this.overriddenDefaultEditors;
	}


	//---------------------------------------------------------------------
	// Management of custom editors
	//---------------------------------------------------------------------

	@Override
	public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
		registerCustomEditor(requiredType, null, propertyEditor);
	}

//注册自定义属性编辑器
	@Override
	public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) {
		if (requiredType == null && propertyPath == null) {
			throw new IllegalArgumentException("Either requiredType or propertyPath is required");
		}
		//如果指定了属性的精准匹配
		if (propertyPath != null) {
			//懒加载体现,第一次使用到的时候,才会去创建集合
			if (this.customEditorsForPath == null) {
				this.customEditorsForPath = new LinkedHashMap<>(16);
			}
			//放入customEditorsForPath集合
			this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType));
		}
		else {
		//懒加载体现,第一次使用到的时候,才会去创建集合
			if (this.customEditors == null) {
				this.customEditors = new LinkedHashMap<>(16);
			}
			this.customEditors.put(requiredType, propertyEditor);
			//需要清空缓存
			this.customEditorCache = null;
		}
	}
    
    //查找自定义属性编辑器
	@Override
	@Nullable
	public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
		Class<?> requiredTypeToUse = requiredType;
		//指定了精确匹配
		if (propertyPath != null) {
            //去customEditorsForPath集合中寻找属性编辑器
			if (this.customEditorsForPath != null) {
				// Check property-specific editor first.
				PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
				if (editor == null) {
					List<String> strippedPaths = new ArrayList<>();
					addStrippedPropertyPaths(strippedPaths, "", propertyPath);
					for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
						String strippedPath = it.next();
						editor = getCustomEditor(strippedPath, requiredType);
					}
				}
				if (editor != null) {
					return editor;
				}
			}
			//虽然指定了propertyPath ,但是没找到对应的属性编辑器
			//那么下一步就是去解析出当前propertyPath对应的requiredType
			if (requiredType == null) {
				requiredTypeToUse = getPropertyType(propertyPath);
			}
		}
		//通过requiredType去查找
		// No property-specific editor -> check type-specific editor.
		return getCustomEditor(requiredTypeToUse);
	}

	/**
确定此注册表是否包含指定 array/collection集合里面元素的自定义编辑器。
	 */
	public boolean hasCustomEditorForElement(@Nullable Class<?> elementType, @Nullable String propertyPath) {
	//如果传入了精确匹配的propertyPath
		if (propertyPath != null && this.customEditorsForPath != null) {
			for (Map.Entry<String, CustomEditorHolder> entry : this.customEditorsForPath.entrySet()) {
			//propertyPath一致并且存在可以将传入字符串转换为elementType的属性编辑器
				if (PropertyAccessorUtils.matchesProperty(entry.getKey(), propertyPath) &&
						entry.getValue().getPropertyEditor(elementType) != null) {
					return true;
				}
			}
		}
		// No property-specific editor -> check type-specific editor.
		return (elementType != null && this.customEditors != null && 
//去非自定义属性编辑器中勋在
this.customEditors.containsKey(elementType));
	}

	/**
	 * Determine the property type for the given property path.
	 * <p>Called by {@link #findCustomEditor} if no required type has been specified,
	 * to be able to find a type-specific editor even if just given a property path.
	 * <p>The default implementation always returns {@code null}.
	 * BeanWrapperImpl overrides this with the standard {@code getPropertyType}
	 * method as defined by the BeanWrapper interface.
	 * @param propertyPath the property path to determine the type for
	 * @return the type of the property, or {@code null} if not determinable
	 * @see BeanWrapper#getPropertyType(String)
	 */
	@Nullable
	protected Class<?> getPropertyType(String propertyPath) {
		return null;
	}

	/**
	 * 精确匹配
	 */
	@Nullable
	private PropertyEditor getCustomEditor(String propertyName, @Nullable Class<?> requiredType) {
		CustomEditorHolder holder =
		//先尝试从customEditorsForPath集合中寻找
				(this.customEditorsForPath != null ? this.customEditorsForPath.get(propertyName) : null);
		return (holder != null ? holder.getPropertyEditor(requiredType) : null);
	}

	/**
	 * 非精确匹配
	 */
	@Nullable
	private PropertyEditor getCustomEditor(@Nullable Class<?> requiredType) {
		if (requiredType == null || this.customEditors == null) {
			return null;
		}
		// Check directly registered editor for type.
		//先检查customEditors
		PropertyEditor editor = this.customEditors.get(requiredType);
		if (editor == null) {
		//如果没有,在检查customEditorCache,因为可能存在父子关系的情况
			// Check cached editor for type, registered for superclass or interface.
			if (this.customEditorCache != null) {
				editor = this.customEditorCache.get(requiredType);
			}
			//判断是否存在继承关系
			if (editor == null) {
				// Find editor for superclass or interface.
				//遍历customEditors里面每个转换器,看是否存在继承关系
				for (Iterator<Class<?>> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) {
					Class<?> key = it.next();
					if (key.isAssignableFrom(requiredType)) {
						editor = this.customEditors.get(key);
						// Cache editor for search type, to avoid the overhead
						// of repeated assignable-from checks.
						if (this.customEditorCache == null) {
							this.customEditorCache = new HashMap<>();
						}
						//加入缓存---从这里看出,该集合存放的是存在父子关系的情况
						this.customEditorCache.put(requiredType, editor);
					}
				}
			}
		}
		return editor;
	}

	/**
	 * Guess the property type of the specified property from the registered
	 * custom editors (provided that they were registered for a specific type).
	 * @param propertyName the name of the property
	 * @return the property type, or {@code null} if not determinable
	 */
	@Nullable
	protected Class<?> guessPropertyTypeFromEditors(String propertyName) {
		if (this.customEditorsForPath != null) {
			CustomEditorHolder editorHolder = this.customEditorsForPath.get(propertyName);
			if (editorHolder == null) {
				List<String> strippedPaths = new ArrayList<>();
				addStrippedPropertyPaths(strippedPaths, "", propertyName);
				for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editorHolder == null;) {
					String strippedName = it.next();
					editorHolder = this.customEditorsForPath.get(strippedName);
				}
			}
			if (editorHolder != null) {
				return editorHolder.getRegisteredType();
			}
		}
		return null;
	}

	/**
	 * Copy the custom editors registered in this instance to the given target registry.
	 * @param target the target registry to copy to
	 * @param nestedProperty the nested property path of the target registry, if any.
	 * If this is non-null, only editors registered for a path below this nested property
	 * will be copied. If this is null, all editors will be copied.
	 */
	protected void copyCustomEditorsTo(PropertyEditorRegistry target, @Nullable String nestedProperty) {
		String actualPropertyName =
				(nestedProperty != null ? PropertyAccessorUtils.getPropertyName(nestedProperty) : null);
		if (this.customEditors != null) {
			this.customEditors.forEach(target::registerCustomEditor);
		}
		if (this.customEditorsForPath != null) {
			this.customEditorsForPath.forEach((editorPath, editorHolder) -> {
				if (nestedProperty != null) {
					int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(editorPath);
					if (pos != -1) {
						String editorNestedProperty = editorPath.substring(0, pos);
						String editorNestedPath = editorPath.substring(pos + 1);
						if (editorNestedProperty.equals(nestedProperty) || editorNestedProperty.equals(actualPropertyName)) {
							target.registerCustomEditor(
									editorHolder.getRegisteredType(), editorNestedPath, editorHolder.getPropertyEditor());
						}
					}
				}
				else {
					target.registerCustomEditor(
							editorHolder.getRegisteredType(), editorPath, editorHolder.getPropertyEditor());
				}
			});
		}
	}


	/**
	 * Add property paths with all variations of stripped keys and/or indexes.
	 * Invokes itself recursively with nested paths.
	 * @param strippedPaths the result list to add to
	 * @param nestedPath the current nested path
	 * @param propertyPath the property path to check for keys/indexes to strip
	 */
	private void addStrippedPropertyPaths(List<String> strippedPaths, String nestedPath, String propertyPath) {
		int startIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR);
		if (startIndex != -1) {
			int endIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR);
			if (endIndex != -1) {
				String prefix = propertyPath.substring(0, startIndex);
				String key = propertyPath.substring(startIndex, endIndex + 1);
				String suffix = propertyPath.substring(endIndex + 1);
				// Strip the first key.
				strippedPaths.add(nestedPath + prefix + suffix);
				// Search for further keys to strip, with the first key stripped.
				addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
				// Search for further keys to strip, with the first key not stripped.
				addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
			}
		}
	}


	/**
	 * Holder for a registered custom editor with property name.
	 * Keeps the PropertyEditor itself plus the type it was registered for.
	 */
	private static final class CustomEditorHolder {

		private final PropertyEditor propertyEditor;

		@Nullable
		private final Class<?> registeredType;

		private CustomEditorHolder(PropertyEditor propertyEditor, @Nullable Class<?> registeredType) {
			this.propertyEditor = propertyEditor;
			this.registeredType = registeredType;
		}

		private PropertyEditor getPropertyEditor() {
			return this.propertyEditor;
		}

		@Nullable
		private Class<?> getRegisteredType() {
			return this.registeredType;
		}

		@Nullable
		private PropertyEditor getPropertyEditor(@Nullable Class<?> requiredType) {
			// Special case: If no required type specified, which usually only happens for
			// Collection elements, or required type is not assignable to registered type,
			// which usually only happens for generic properties of type Object -
			// then return PropertyEditor if not registered for Collection or array type.
			// (If not registered for Collection or array, it is assumed to be intended
			// for elements.)
			if (this.registeredType == null ||
					(requiredType != null &&
					(ClassUtils.isAssignable(this.registeredType, requiredType) ||
					ClassUtils.isAssignable(requiredType, this.registeredType))) ||
					(requiredType == null &&
					(!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) {
				return this.propertyEditor;
			}
			else {
				return null;
			}
		}
	}

}

PropertyEditorRegistrar

Registrar:登记员。它一般和xxxRegistry配合使用,其实内核还是Registry,只是运用了倒排思想屏蔽一些内部实现而已。

代码语言:javascript
复制
public interface PropertyEditorRegistrar {
	void registerCustomEditors(PropertyEditorRegistry registry);
}

PropertyEditorRegistrar接口在Spring体系内唯一实现为:ResourceEditorRegistra。


ResourceEditorRegistrar

从命名上就知道它和Resource资源有关,实际上也确实如此:主要负责将ResourceEditor注册到注册中心里面去,用于处理形如Resource、File、URI等这些资源类型。

你配置classpath:xxx.xml用来启动Spring容器的配置文件,String -> Resource转换就是它的功劳

代码语言:javascript
复制
public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
    //属性解析--一般传入Environment
	private final PropertyResolver propertyResolver;
    //spring资源加载---一般传入ApplicationContext
	private final ResourceLoader resourceLoader;

	public ResourceEditorRegistrar(ResourceLoader resourceLoader, PropertyResolver propertyResolver) {
		this.resourceLoader = resourceLoader;
		this.propertyResolver = propertyResolver;
	}


	@Override
	public void registerCustomEditors(PropertyEditorRegistry registry) {
	     //注册一些默认的关于资源转换相关的转换器
		ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
		doRegisterEditor(registry, Resource.class, baseEditor);
		doRegisterEditor(registry, ContextResource.class, baseEditor);
		doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
		doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
		doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
		doRegisterEditor(registry, Path.class, new PathEditor(baseEditor));
		doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
		doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));

		ClassLoader classLoader = this.resourceLoader.getClassLoader();
		doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
		doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
		doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));

		if (this.resourceLoader instanceof ResourcePatternResolver) {
			doRegisterEditor(registry, Resource[].class,
					new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
		}
	}

	/**
	 * Override default editor, if possible (since that's what we really mean to do here);
	 * otherwise register as a custom editor.
	 */
	private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {
		if (registry instanceof PropertyEditorRegistrySupport) {
			((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor);
		}
		else {
			registry.registerCustomEditor(requiredType, editor);
		}
	}

}

很明显,它的设计就是服务于ApplicationContext上下文,在Bean创建过程中辅助BeanWrapper实现资源加载、转换。

BeanFactory在初始化的准备过程中就将它实例化,从而具备资源处理能力:

代码语言:javascript
复制
AbstractApplicationContext:

	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		
		...
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
		...
	}

这也是PropertyEditorRegistrar在Spring Framework的唯一使用处,值的关注。

之前在研究spring初始化源码的时候,也一直卡在这里,不知道这一步为啥,现在也算是懂了


PropertyEditor自动发现机制

最后介绍一个使用中的奇淫小技巧:PropertyEditor自动发现机制。

一般来说,我们自定义一个PropertyEditor是为了实现自定义类型 <-> 字符串的自动转换,它一般需要有如下步骤:

  • 为自定义类型写好一个xxxPropertyEditor(实现PropertyEditor接口)
  • 将写好的编辑器注册到注册中心PropertyEditorRegistry

显然步骤1属个性化行为无法替代,但步骤2属于标准行为,重复劳动是可以标准化的。自动发现机制就是用来解决此问题,对自定义的编辑器制定了如下标准:

  • 实现了PropertyEditor接口,具有空构造器
  • 与自定义类型同包(在同一个package内),名称必须为:targetType.getName() + “Editor”

这样你就无需再手动注册到注册中心了(当然手动注册了也不碍事),Spring能够自动发现它,这在有大量自定义类型编辑器的需要的时候将很有用。

说明:此段核心逻辑在BeanUtils#findEditorByConvention()里,有兴趣者可看看

代码语言:javascript
复制
	@Nullable
	public static PropertyEditor findEditorByConvention(@Nullable Class<?> targetType) {
		if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
			return null;
		}
        //类加载器
		ClassLoader cl = targetType.getClassLoader();
		if (cl == null) {
			try {
				cl = ClassLoader.getSystemClassLoader();
				if (cl == null) {
					return null;
				}
			}
			catch (Throwable ex) {
				// e.g. AccessControlException on Google App Engine
				return null;
			}
		}

		String targetTypeName = targetType.getName();
		//editorName实际是属性编辑器的名字
		String editorName = targetTypeName + "Editor";
		try {
		//这里尝试用类加载器去加载该类
			Class<?> editorClass = cl.loadClass(editorName);
			if (editorClass != null) {
				if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
					unknownEditorTypes.add(targetType);
					return null;
				}
				return (PropertyEditor) instantiateClass(editorClass);
			}
			// Misbehaving ClassLoader returned null instead of ClassNotFoundException
			// - fall back to unknown editor type registration below
		}
		catch (ClassNotFoundException ex) {
			// Ignore - fall back to unknown editor type registration below
		}
		unknownEditorTypes.add(targetType);
		return null;
	}

值得注意的是:此机制属Spring遵循Java Bean规范而单独提供,在单独使用PropertyEditorRegistry时并未开启,而是在使用Spring产品级能力TypeConverter时有提供,这在后文将有体现,欢迎保持关注。


参考文章

YourBatMan类型转换专栏—写的非常好,建议关注一波

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring读源码系列番外篇04----类型转换--上
  • Spring类型转换器升级历史
    • 古老的PropertyEditor
      • 先进的Converter、GenericConverter
        • 先进的转换服务接口:ConversionService
          • 类型转换整合格式化器Formatter
            • 类型转换底层接口TypeConvert
              • Spring Boot使用增强
              • PropertyEditor—旧的类型转换
                • PropertyEditorSupport
                  • Spring为何基于它扩展?
                    • Spring内建扩展实现有哪些?
                      • 特殊实现
                        • PropertyEditorRegistry
                          • PropertyEditorRegistrySupport
                        • PropertyEditorRegistrar
                          • ResourceEditorRegistrar
                        • PropertyEditor自动发现机制
                        • 参考文章
                        相关产品与服务
                        微服务引擎 TSE
                        微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档