探祕 Spring 的 PropertyEditor

Spring 源碼系列
Spring 總體架構
編譯Spring5.2.0源碼
Spring-AliasRegistry 別名註冊
Spring 資源加載
Spring 容器初始化
Spring 獲取單例(一)
Spring 獲取單例(二)
Spring 獲取單例(三)
Spring 解決循環依賴
Spring FactoryBean 緩存
Spring Aware 介紹
Spring BeanPostProcessor 介紹
扯淡 Spring BeanDefinition
探祕 Spring 的 PropertyEditor

PropertyEditor & PropertyEditorSupport 介紹

java.beans.PropertyEditor 是 JDK 自帶的類,是提供給 AWT。作啥用呢、就是講用戶在圖形見面中輸入的字符串轉換位對應類型的值(對象)。相似於一個 convertor。java

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);  } 複製代碼

主要方法有四個web

  • void setValue(Object value); 設置屬性值
  • Object getValue(); 獲取屬性值
  • String getAsText(); 把屬性值轉換成 String
  • void setAsText(String text); 把 String 轉換成屬性值

而 Java 也爲咱們提供了一個默認的實現類 java.beans.PropertyEditorSupportspring

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; } 複製代碼

咱們只要重寫 setAsTextgetAsText 方法能夠實現 String 類型到特定類型的轉換了緩存

與 Spring 的關係

說了那麼久、這個跟 Spring 有什麼錘子關係嗎 ?架構

咱們想想、當你使用 xml 配置文件給某個屬性設定某個值的時候、咱們輸入的是否是一個字符串、可是咱們對應的這個屬性的類型卻不必定是字符串類型、這種場景之下、是否是跟 AWT 的場景是同樣的。因此 Spring 的屬性解釋都是繼承自 PropertyEditorSupport 而後重寫了 setAsTextgetAsTextapp

舉個例子編輯器

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 "";  }  }  } 複製代碼

方法也是挺簡單的就不囉嗦解釋了ide

舉個例子函數

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}post

相關組件介紹

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 尚未註冊進去、將會報錯

本文使用 mdnice 排版

相關文章
相關標籤/搜索