探秘 Spring 的 PropertyEditor
PropertyEditor & PropertyEditorSupport 介绍
java.beans.PropertyEditor
是 JDK 自带的类,是提供给 AWT。做啥用呢、就是讲用户在图形见面中输入的字符串转换位对应类型的值(对象)。类似于一个 convertor。
public interface PropertyEditor { void setValue(Object value); Object getValue(); boolean isPaintable(); String getJavaInitializationString(); String getAsText(); void setAsText(String text) throws java.lang.IllegalArgumentException; String[] getTags(); java.awt.Component getCustomEditor(); boolean supportsCustomEditor(); void addPropertyChangeListener(PropertyChangeListener listener); void removePropertyChangeListener(PropertyChangeListener listener);}
主要方法有四个
void setValue(Object value);
设置属性值Object getValue();
获取属性值String getAsText();
把属性值转换成 Stringvoid setAsText(String text);
把 String 转换成属性值
而 Java 也为我们提供了一个默认的实现类 java.beans.PropertyEditorSupport
private Object value;public void setValue(Object value) { this.value = value; firePropertyChange();}public Object getValue() { return value;}public void setAsText(String text) throws java.lang.IllegalArgumentException { if (value instanceof String) { setValue(text); return; } throw new java.lang.IllegalArgumentException(text);}public String getAsText() { return (this.value != null) ? this.value.toString() : null;}
我们只要重写 setAsText
和 getAsText
方法可以实现 String 类型到特定类型的转换了
与 Spring 的关系
说了那么久、这个跟 Spring 有什么锤子关系吗 ?
我们想一想、当你使用 xml 配置文件给某个属性设定某个值的时候(或者说使用 @Value 注解给定一个默认值的时候)、我们输入的是不是一个字符串、但是我们对应的这个属性的类型却不一定是字符串类型、这种场景之下、是不是跟 AWT 的场景是一样的。所以 Spring 的属性解释都是继承自 PropertyEditorSupport 然后重写了 setAsText
和 getAsText
举个例子
public class CustomBooleanEditor extends PropertyEditorSupport { public static final String VALUE_TRUE = "true"; public static final String VALUE_FALSE = "false"; public static final String VALUE_ON = "on"; public static final String VALUE_OFF = "off"; public static final String VALUE_YES = "yes"; public static final String VALUE_NO = "no"; public static final String VALUE_1 = "1"; public static final String VALUE_0 = "0"; // 为 true 的时候的字符串、默认为 null @Nullable private final String trueString; // 为 false 的时候的字符串、默认为 null @Nullable private final String falseString; // 是否允许为 null // 基本类型 boolean 的时候不允许空的字符串 // 引用类型 Boolean 的时候允许空的字符串 private final boolean allowEmpty; public CustomBooleanEditor(boolean allowEmpty) { this(null, null, allowEmpty); } public CustomBooleanEditor(@Nullable String trueString, @Nullable String falseString, boolean allowEmpty) { this.trueString = trueString; this.falseString = falseString; this.allowEmpty = allowEmpty; } @Override public void setAsText(@Nullable String text) throws IllegalArgumentException { String input = (text != null ? text.trim() : null); if (this.allowEmpty && !StringUtils.hasLength(input)) { // Treat empty String as null value. setValue(null); } else if (this.trueString != null && this.trueString.equalsIgnoreCase(input)) { setValue(Boolean.TRUE); } else if (this.falseString != null && this.falseString.equalsIgnoreCase(input)) { setValue(Boolean.FALSE); } else if (this.trueString == null && (VALUE_TRUE.equalsIgnoreCase(input) || VALUE_ON.equalsIgnoreCase(input) || VALUE_YES.equalsIgnoreCase(input) || VALUE_1.equals(input))) { setValue(Boolean.TRUE); } else if (this.falseString == null && (VALUE_FALSE.equalsIgnoreCase(input) || VALUE_OFF.equalsIgnoreCase(input) || VALUE_NO.equalsIgnoreCase(input) || VALUE_0.equals(input))) { setValue(Boolean.FALSE); } else { throw new IllegalArgumentException("Invalid boolean value [" + text + "]"); } } @Override public String getAsText() { if (Boolean.TRUE.equals(getValue())) { return (this.trueString != null ? this.trueString : VALUE_TRUE); } else if (Boolean.FALSE.equals(getValue())) { return (this.falseString != null ? this.falseString : VALUE_FALSE); } else { return ""; } }}
方法也是挺简单的就不啰嗦解释了
举个例子
public class Job { private boolean completed; private Boolean started; // get and set ...........}
<bean class="com.demo.property.editor.Job" id="job"> <property name="completed" value="on" /> <property name="started" value=""/></bean>
获取这个 bean 并打印 Job{completed=true, started=null}
相关组件介绍
PropertyEditorRegistry
一看名字就知道是一个注册的接口
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);@NullablePropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
PropertyEditorRegistrySupport
PropertyEditorRegistry
的实现类。当我们尝试去通过 Class 对象获取对应的 PropertyEditor 的时候、它会为我们初始化一系列默认的 PropertyEditor
在 doCreateBean 的 populateBean 中会调用 getDefaultEditor 获取对应的 PropertyEditor 进行值的类型转换
// spring 默认提供的 propertyEditor@Nullableprivate Map<Class<?>, PropertyEditor> defaultEditors;// 去覆盖的 默认的 property editor@Nullableprivate Map<Class<?>, PropertyEditor> overriddenDefaultEditors;// 自定义的一些 property editor@Nullableprivate Map<Class<?>, PropertyEditor> customEditors;// 属性的路径/属性名,CustomEditorHolder 包含的是 Class 和 PropertyEditor@Nullableprivate Map<String, CustomEditorHolder> customEditorsForPath;// 如果注册的父 class、那么子类的 class 找不到的时候、就会返回这个父的 class 并且讲这个关系保存在// 这个 map 中@Nullableprivate Map<Class<?>, PropertyEditor> customEditorCache;@Nullable public PropertyEditor getDefaultEditor(Class<?> requiredType) { if (!this.defaultEditorsActive) { return null; } 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()); 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. 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); } }
BeanWrapper
Spring 中用于封装 bean 的是 BeanWrapper 类型、而它又间接继承了 PropertyEditorRegistry。BeanWrapperImpl 是 BeanWrapper 的实现类、我们在系统中看到的大多数 PropertyEditorRegistry 都是 BeanWrapperImpl 的对象。BeanWrapperImpl 还继承了 PropertyEditorRegistrySupport 这个实现类
PropertyEditorRegistrar
property editor 的登记处
void registerCustomEditors(PropertyEditorRegistry registry);
ResourceEditorRegistrar
唯一的一个默认的实现类
public class ResourceEditorRegistrar implements PropertyEditorRegistrar { private final PropertyResolver propertyResolver; 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)); } } private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) { if (registry instanceof PropertyEditorRegistrySupport) { ((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor); } else { registry.registerCustomEditor(requiredType, editor); } }}
先说下这个类被使用到的地方吧、只有使用 ApplicationContext 的时候这个 Registrar 才会被使用到、上面的 PropertyEditor 才会去注册或者覆盖 PropertyEditorRegistry 默认的值
调用关系链为
ClassPathXmlApplicationContext 构造函数 -> refresh -> prepareBeanFactory() -> 创建 ResourceEditorRegistrar 增加到 Set 中
继而它会在 doCreateBean 的 createBeanInstance 中将 ResourceEditorRegistrar 的默认的 PropertyEditor 注册进去
例子
public class Job { private boolean completed; private Content content; // get and set method}
public class Content { private String details; private String type; private int priority; // get and set method}
<bean class="com.demo.property.editor.Job" id="job" lazy-init="true"> <property name="completed" value="off" /> <property name="content" value="关注我:紧急:100"/></bean>
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("property.editor/coderLi.xml");classPathXmlApplicationContext.getBeanFactory().addPropertyEditorRegistrar(registry -> { if (registry instanceof PropertyEditorRegistrySupport) { ((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(Content.class, new ContentPropertyEditor()); System.out.println("PropertyEditorRegistrySupport"); } else { registry.registerCustomEditor(Content.class, new ContentPropertyEditor()); }});Object job = classPathXmlApplicationContext.getBean("job");System.out.println(job);
实现相同效果的方法有很多、比如说 CustomEditorConfigurer、也可以实现 BeanFactoryPostProcessor 接口等等
如我上面的代码实现的话、注意一个点就是、这个 bean 必须是一个延迟实例化的、因为 ApplicationContext 默认是会将所有的非 lazy 的 bean 实例化、而这个时候我们的 PropertyEditor 还没有注册进去、将会报错
版权声明: 本文为 InfoQ 作者【CoderLi】的原创文章。
原文链接:【http://xie.infoq.cn/article/991186ca3a67b256a7de294ed】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
CoderLi
微信公众号:CoderLi ,专注于 Java 后端开发 2019.07.14 加入
还未添加个人简介
评论