#探祕Spring的PropertyEditor 今天無心之中一位網友諮詢一個問題讓我有了深刻了解一下Spring的PropertyEditor機制的機會,雖然以前也大概知道些,可是都是知道它是什麼,殊不知道在Spring整個機制中它是如何執行的。今天就趁着中午閒暇時間看了一下Spring這方面的源碼。因此這裏主要是經過分析Spring的源碼來了解PropertyEditor
。java
其實PropertyEditor
是JDK裏面的java.beans下面的類,是提供AWT進行渲染用的。Spring經過利用該接口,來實現Bean的屬性轉換器。咱們在Spring的xml或者其餘途徑配置的bean屬性都是字符串類型的值,可是對應到每一個具體的屬性是各類類型的,Spring經過各類PropertyEditor
來對各個屬性進行類型轉換,在Spring中的PropertyEditor
並非直接實現PropertyEditor
接口,是經過PropertyEditorSupport
類屏蔽了一些Spring不須要的方法好比paintValue
,從而對它們提供了默認的實現,因此Spring裏面的PropertyEditor
都是在PropertyEditorSupport
的基礎上實現的。那麼先看看Spring到底提供了哪些PropertyEditor
的實現,下面截取了經過IDE工具找到的實現類截圖: spring
這裏面基本涉及到了全部JDK提供的類型,知道spring提供哪些PropertyEditor
以後,還得須要這些PropertyEditor
是怎麼嵌入Spring的。app
##PropertyEditorRegistry 這個是Spring框架內部將PropertyEditor
嵌入Spring的方式,其中BeanWrapperImpl
就是一個例子。先看看BeanWrapperImpl
的類圖: 框架
能夠看到BeanWrapperImpl
其實就是PropertyEditorRegistry
的子類。其實PropertyEditorRegistry
是一個接口,作具體事情的是PropertyEditorRegistrySupport
。在PropertyEditorRegistrySupport
中存在一個方法createDefaultEditors
,這個方法就是初始化Spring中默認PropertyEditor
。下面看看哪些是Spring默認的PropertyEditor
: <!--lang:java--> private void createDefaultEditors() { this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);ide
// 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(Pattern.class, new PatternEditor()); this.defaultEditors.put(Properties.class, new PropertiesEditor()); 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()); // 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); } }
執行完這個那麼BeanWrapperImpl
就具有上面類型的轉換功能,可能上面可以轉換的類型還不能知足咱們的需求,那麼能夠經過另外一種方式將PropertyEditor注入到Spring中。工具
##PropertyEditorRegistrar 該接口只有一個方法void registerCustomEditors(PropertyEditorRegistry registry)
,實現該方法就能夠往傳入的registry添加自定義的PropertyEditor
,通常狀況下傳入的registry
是BeanWrapperImpl
的實體,因此就等於將自定義的PropertyEditor
注入到BeanWrapperImpl
裏面。如今的問題是怎麼將PropertyEditorRegistrar
注入到Spring中,那麼就能夠將自定的PropertyEditor
添加到Spring中。Spring提供了一個類專門用來將第三方的PropertyEditor
注入到它裏面,這個類就是CustomEditorConfigurer
,先看看這個類的類圖: 發現它是實現了BeanFactoryPostProcessor
接口,那麼將會在構造完BeanDefinition
以後調用方法postProcessBeanFactory
,只有整個方法是嵌入Spring的惟一途經,那麼看看這個方法裏面作了什麼事情:post
<!--lang:java--> public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar); } } if (this.customEditors != null) { for (Map.Entry<String, ?> entry : this.customEditors.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); Class requiredType = null; try { requiredType = ClassUtils.forName(key, this.beanClassLoader); if (value instanceof PropertyEditor) { if (logger.isWarnEnabled()) { logger.warn("Passing PropertyEditor instances into CustomEditorConfigurer is deprecated: " + "use PropertyEditorRegistrars or PropertyEditor class names instead. " + "Offending key [" + key + "; offending editor instance: " + value); } beanFactory.addPropertyEditorRegistrar( new SharedPropertyEditorRegistrar(requiredType, (PropertyEditor) value)); } else if (value instanceof Class) { beanFactory.registerCustomEditor(requiredType, (Class) value); } else if (value instanceof String) { Class editorClass = ClassUtils.forName((String) value, this.beanClassLoader); Assert.isAssignable(PropertyEditor.class, editorClass); beanFactory.registerCustomEditor(requiredType, editorClass); } else { throw new IllegalArgumentException("Mapped value [" + value + "] for custom editor key [" + key + "] is not of required type [" + PropertyEditor.class.getName() + "] or a corresponding Class or String value indicating a PropertyEditor implementation"); } } catch (ClassNotFoundException ex) { if (this.ignoreUnresolvableEditors) { logger.info("Skipping editor [" + value + "] for required type [" + key + "]: " + (requiredType != null ? "editor" : "required type") + " class not found."); } else { throw new FatalBeanException( (requiredType != null ? "Editor" : "Required type") + " class not found", ex); } } } } }
發現它都調用了beanFactory
的方法將配置的PropertyEditorRegistrar
和customEditors
注入到Spring的bean工廠裏面。咱們只要往spring容器中注入一個CustomEditorConfigurer
Bean,而後設置propertyEditorRegistrars
和customEditors
屬性就能夠將自定義的PropertyEditor
注入到Spring中了。固然你也能夠不用CustomEditorConfigurer
類,自定義一個類實現BeanFactoryPostProcessor
接口,而後按照CustomEditorConfigurer
相似的路徑,也能夠達到目的。下面給出一個自定義PropertyEditor
大體流程:ui
<!--lang:java--> public class CustomPropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { super.setAsText(text); } @Override public Object getValue() { return super.getValue(); } }
上面是定義了一個自定義PropertyEditor
,下面須要將這個PropertyEditor
注入到Spring裏面中:this
<!--lang:xml--> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="com.xx.foo.FooPojo" value="com.xx.foo.CustomPropertyEditor"/> </map> </property> </bean>
Spring都會調用setAsText
方法將該屬性的字面值設置進來,而後須要根據你規定的規範轉換成對應的屬性對象。code
下面再給出一個ClassEditor
的實現,你們能夠仿照這個來實現:
<!--lang:java--> public class ClassEditor extends PropertyEditorSupport { private final ClassLoader classLoader; /** * Create a default ClassEditor, using the thread context ClassLoader. */ public ClassEditor() { this(null); } /** * Create a default ClassEditor, using the given ClassLoader. * @param classLoader the ClassLoader to use * (or {@code null} for the thread context ClassLoader) */ public ClassEditor(ClassLoader classLoader) { this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); } @Override public void setAsText(String text) throws IllegalArgumentException { if (StringUtils.hasText(text)) { setValue(ClassUtils.resolveClassName(text.trim(), this.classLoader)); } else { setValue(null); } } @Override public String getAsText() { Class clazz = (Class) getValue(); if (clazz != null) { return ClassUtils.getQualifiedName(clazz); } else { return ""; } } }