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);
@Nullable
PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
PropertyEditorRegistrySupport
PropertyEditorRegistry
的實現類。當我們嘗試去通過 Class 對象獲取對應的 PropertyEditor 的時候、它會為我們初始化一系列默認的 PropertyEditor
在 doCreateBean 的 populateBean 中會調用 getDefaultEditor 獲取對應的 PropertyEditor 進行值的類型轉換
// spring 默認提供的 propertyEditor
@Nullable
private Map<Class<?>, PropertyEditor> defaultEditors;
// 去覆蓋的 默認的 property editor
@Nullable
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;
// 自定義的一些 property editor
@Nullable
private Map<Class<?>, PropertyEditor> customEditors;
// 屬性的路徑/屬性名,CustomEditorHolder 包含的是 Class 和 PropertyEditor
@Nullable
private Map<String, CustomEditorHolder> customEditorsForPath;
// 如果注冊的父 class、那么子類的 class 找不到的時候、就會返回這個父的 class 並且講這個關系保存在
// 這個 map 中
@Nullable
private 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 還沒有注冊進去、將會報錯