在 Spring 配置文件里,我们往往通过字面值为 Bean 各种类型的属性提供设置值:不管是 double 类型还是 int 类型,在配置文件中都应字符串类型的字面值。BeanWrapper 填充 Bean 属性时如何将这个字面值转换为对应的 double 或 int 等内部类型呢 ?我们可以隐约地感觉到一定有一个转换器在其中起作用,这个转换器就是属性编辑器。
“属性编辑器”这个名字可能 会让人误以为是一个带用户界面的输入器,其实属性编辑器不一定非得有用户界面,任何实现 java.beans.PropertyEditor
接口的类都是属性编辑器。属性编辑器的主要功能就是 将外部的设置值转换为 JVM 内部的对应类型 ,所以属性编辑器其实就是一个类型转换器。
转回刚才说到的问题:BeanWrapper 填充属性时是如何进行属性编辑的?刚好上节 Spring IoC之BeanWrapper 讲述了关于 BeanWrapper 设置属性的过程,在最后设置属性值的时候,在 AbstractNestablePropertyAccessor 类中有这么一段代码:
valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); 复制代码
该段代码就是获取 xml 文件中属性的设置值,继续往下看。
private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException { Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate"); PropertyChangeEvent pce; try { return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td); } ....... } 复制代码
在 convertIfNecessary()
方法中执行时会跳转到 TypeConverterDelegate 类中的 convertIfNecessary()
方法,其中会涉及到 PropertyEditor 接口的实现类。关于 TypeConverterDelegate 类在 BeanWrapper 的构造方法中有提及到,那个时候就已经构建了关于当前 bean 对象的属性编辑器。关于 TypeConverterDelegate 类想要了解更多可以参看: SpringMVC类型转换器、属性编辑器PropertiesEditor源码分析CustomDateEditor源码分析TypeConverterDelegate源码分析
PropertyEditor 是属性编辑器的接口,它规定了将外部设置值转换为内部 JavaBean 属性值的转换接口方法。PropertyEditor 主要的接口方法说明如下:
Object getValue() void setValue(Object newValue) String getAsText() void setAsText(String text) String[] getTags() String getJavaInitializationString() void paintValue(Graphics gfx,Rectangle box) Component getCustomEditor()
可以看出 PropertyEditor 接口方法是内部属性值和外部设置值的沟通桥梁。此外,我们可以很容易地发现该接口的很多方法是专为 IDE 中的可视化属性编辑器提供的:如 getTags()
、 getJavaInitializationString()
、 paintValue()
等。
简单的 PropertyEditor 可能仅支持 getAsText 和 setAsText 方法,而无需支持(例如)paintValue 或 getCustomEditor。更复杂的类型可能无法支持 getAsText 和 setAsText,但将支持 paintValue 和 getCustomEditor。
Java 为 PropertyEditor 提供了一个方便类:PropertyEditorSupport,该类实现了 PropertyEditor 接口并提供默认实现,一般情况下,用户可以通过扩展这个方便类设计自己的属性编辑器。
在 Spring IoC之BeanWrapper 一文中有提到关于 BeanWrapper 的使用场景,我们看一下当时构建 BeanWrapper 对象的方法。
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) { BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); Constructor<?> constructorToUse = null; ConstructorResolver.ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; ...... Assert.state(argsToUse != null, "Unresolved constructor arguments"); bw.setBeanInstance(this.instantiate(beanName, mbd, constructorToUse, argsToUse)); return bw; } 复制代码
最开始执行了 initBeanWrapper()
方法,该方法用来配置一些必要数据,继续往下看。该方法具体实现在 AbstractBeanFactory 类中,定义如下:
protected void initBeanWrapper(BeanWrapper bw) { bw.setConversionService(this.getConversionService()); this.registerCustomEditors(bw); } protected void registerCustomEditors(PropertyEditorRegistry registry) { PropertyEditorRegistrySupport registrySupport = registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport)registry : null; if (registrySupport != null) { registrySupport.useConfigValueEditors(); } if (!this.propertyEditorRegistrars.isEmpty()) { Iterator var3 = this.propertyEditorRegistrars.iterator(); while(var3.hasNext()) { PropertyEditorRegistrar registrar = (PropertyEditorRegistrar)var3.next(); try { registrar.registerCustomEditors(registry); } catch (BeanCreationException var9) { Throwable rootCause = var9.getMostSpecificCause(); if (rootCause instanceof BeanCurrentlyInCreationException) { BeanCreationException bce = (BeanCreationException)rootCause; String bceBeanName = bce.getBeanName(); if (bceBeanName != null && this.isCurrentlyInCreation(bceBeanName)) { if (this.logger.isDebugEnabled()) { this.logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() + "] failed because it tried to obtain currently created bean '" + var9.getBeanName() + "': " + var9.getMessage()); } this.onSuppressedException(var9); continue; } } throw var9; } } } if (!this.customEditors.isEmpty()) { this.customEditors.forEach((requiredType, editorClass) -> { registry.registerCustomEditor(requiredType, (PropertyEditor)BeanUtils.instantiateClass(editorClass)); }); } } 复制代码
该方法的功能是把容器中默认的 PropertyEditor 和注册到容器中的自定义 PropertyEditor 复制到 BeanWrapper中,来辅助构造子注入时的值转换操作以及为后面其它属性的注入值转换做准备,为什么要把这些 PropertyEditor复制到各个BeanWrapper中?因为 PropertyEditor 是单例模式,非线程安全的接口,每个 BeanWrapper 复制一份可以消除高并发下的状态同步开销。
关于那些内置 PropertyEditor 在哪里添加进去的,这里需要关注一下 PropertyEditorRegistrySupport 类,在该类中有一个 createDefaultEditors()
方法,它会将一些必要的 PropertyEditor 提前加进去。
private void createDefaultEditors() { this.defaultEditors = new HashMap(64); 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()); 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)); this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor()); this.defaultEditors.put(char[].class, new CharArrayPropertyEditor()); this.defaultEditors.put(Character.TYPE, new CharacterEditor(false)); this.defaultEditors.put(Character.class, new CharacterEditor(true)); this.defaultEditors.put(Boolean.TYPE, new CustomBooleanEditor(false)); this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); this.defaultEditors.put(Byte.TYPE, new CustomNumberEditor(Byte.class, false)); this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true)); this.defaultEditors.put(Short.TYPE, new CustomNumberEditor(Short.class, false)); this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true)); this.defaultEditors.put(Integer.TYPE, new CustomNumberEditor(Integer.class, false)); this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true)); this.defaultEditors.put(Long.TYPE, new CustomNumberEditor(Long.class, false)); this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true)); this.defaultEditors.put(Float.TYPE, new CustomNumberEditor(Float.class, false)); this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true)); this.defaultEditors.put(Double.TYPE, 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)); 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); } } 复制代码
此外还内置了 一些 Resource 相关的 PropertyEditor,代码在 ResourceEditorRegistrar 类的registerCustomEditors 方法中:
PropertyEditorRegistrar registrar = (PropertyEditorRegistrar)var3.next(); registrar.registerCustomEditors(registry); 复制代码
具体实现在 ResourceEditorRegistrar 类中:
public void registerCustomEditors(PropertyEditorRegistry registry) { ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver); this.doRegisterEditor(registry, Resource.class, baseEditor); this.doRegisterEditor(registry, ContextResource.class, baseEditor); this.doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor)); this.doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor)); this.doRegisterEditor(registry, File.class, new FileEditor(baseEditor)); this.doRegisterEditor(registry, Path.class, new PathEditor(baseEditor)); this.doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor)); this.doRegisterEditor(registry, URL.class, new URLEditor(baseEditor)); ClassLoader classLoader = this.resourceLoader.getClassLoader(); this.doRegisterEditor(registry, URI.class, new URIEditor(classLoader)); this.doRegisterEditor(registry, Class.class, new ClassEditor(classLoader)); this.doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader)); if (this.resourceLoader instanceof ResourcePatternResolver) { this.doRegisterEditor(registry, Resource[].class, new ResourceArrayPropertyEditor((ResourcePatternResolver)this.resourceLoader, this.propertyResolver)); } } 复制代码
ResourceEditorRegistrar 是框架内置的一个 PropertyEditor 注册器,它是一个 BFPP,在ClasspathXmlApplicationContext 启动时就会把它添加到容器中。
在内置的 PropertyEditor 中有这么一个类 URLEditor,实际使用代码如下:
@Test public void propertyEditorTest() throws Exception{ PropertyEditor propertyEditor = new URLEditor(); propertyEditor.setAsText("http://www.springframework.org"); Object value = propertyEditor.getValue(); assertTrue(value instanceof URL); URL url = (URL) value; assertEquals(url.toString(),propertyEditor.getAsText()); } 复制代码
简单来说就是字符串和其他对象的类型转换器,通过 setAsText ()
设置,再通过 getValue()
获取转换值。
当 Spring 内置的 PropertyEditor 无法满足我们的要求的时候,我们可以根据 Spring 提供的扩展机制来自定义 PropertyEditor,下面通过一个例子来介绍如何实现自定义的 PropertyEditor,这个 PropertyEditor 是一个时间相关的 Editor,它可以一个满足特定时间格式的字符串转换成日期对象。
CustomEditorConfigurer
在讲述案例前我们需要了解一下 org.springframework.beans.factory.config.CustomEditorConfigurer
类, 通常,在使用容器之前在容器中注册一个属性编辑器。 CustomEditorConfigurer
类被实现为内置的 bean工厂后处理器,供您在实例化任何 bean 之前注册自定义属性编辑器。
该类有两种使用方式:
1、从Spring 2.0开始,推荐的用法是使用自定义 PropertyEditorRegistrar
实现,这些实现又将给定的注册任何所需的编辑器实例 registry
。每个 PropertyEditorRegistrar 可以注册任意数量的自定义编辑器。
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <bean class="mypackage.MyCustomDateEditorRegistrar"/> <bean class="mypackage.MyObjectEditorRegistrar"/> </list> </property> </bean> 复制代码
2、通过 customEditors 属性注册 PropertyEditor 类, Spring 将为每次编辑尝试创建它们的新实例,例如:
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.util.Date" value="mypackage.MyCustomDateEditor"/> <entry key="mypackage.MyObject" value="mypackage.MyObjectEditor"/> </map> </property> </bean> 复制代码
注意:现在不鼓励通过 customEditors 属性注册 PropertyEditor Bean 实例,因为 PropertyEditor 是非线程安全的,因此对于每次编辑尝试,实例都必须同步。 这意味着在在高并发坏境下这里的同步会对应用的性能造成比较大的影响。所以这种方式是不推荐的,事实上这种方式已经被 Spring 打上 deprecated 标签了。如果您需要控制 PropertyEditor 的实例化过程,请使用 PropertyEditorRegistrar 进行注册。
Date属性编辑
首先定义一个 Boy 类,其中包含时间格式的属性:
public class Boy { private String name; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Boy{" + "name='" + name + '/'' + ", birthday=" + birthday + '}'; } } 复制代码
定义一个 PropertyEditor,框架中提供了一个 PropertyEditor 基类 PropertyEditorSupport,直接继承这个类可以省去一部分代码,代码如下:
public class DateEditor extends PropertyEditorSupport { private DateFormat dateFormat; public DateFormat getDateFormat() { return dateFormat; } public void setDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; } @Override public void setAsText(String s) throws IllegalArgumentException { try { Object value = dateFormat.parse(s); setValue(value); } catch (ParseException e) { e.printStackTrace(); } } @Override public String getAsText() { if (getValue() instanceof Date){ Date date = (Date) getValue(); return dateFormat.format(date); } return super.getAsText(); } } 复制代码
还需要定义一个 PropertyEditorRegistrar 实现类,它的职责是用来注册 PropertyEditor,而且可以注册任意数量的 PropertyEditor。
public class DateFormatEditor implements PropertyEditorRegistrar { private String dateFormat; public String getDateFormat() { return dateFormat; } public void setDateFormat(String dateFormat) { this.dateFormat = dateFormat; } @Override public void registerCustomEditors(PropertyEditorRegistry propertyEditorRegistry) { DateEditor dateEditor = new DateEditor(); dateEditor.setDateFormat(new SimpleDateFormat(dateFormat)); propertyEditorRegistry.registerCustomEditor(Date.class,dateEditor); } } 复制代码
在注册器的 registerCustomEditors 方法中把 DateEditor 注册到容器中并且和 Date 类型绑定,要确保 DateEditor 对象时临时创建的对象而不是一个全局对象,否则一样可能会引发性能问题。把日期格式通过dateFormat 字段注入,需要注意的是先将注入的字符串转换为 DateEditor 中定义的 DateFormat 类型。
接着就需要配置 XML 文件,定义如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="boy" class="com.msdn.bean.Boy"> <property name="name" value="hresh" /> <property name="birthday" value="1996年1月1日" /> </bean> <bean id="editorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <bean class="com.msdn.editor.DateFormatEditor"> <property name="dateFormat" value="yyyy年MM月dd日" /> </bean> </list> </property> </bean> </beans> 复制代码
这种方式下,每个 BeanWrapper 都会注册不同的 PropertyEditor,不会有高并发下的性能问题,而且用法也比较灵活,所以 Spring 的推荐是通过 propertyEditorRegistrars 注入来完成 PropertyEditor 的自定义。
测试代码如下:
@Test public void updateBean() throws MalformedURLException { ApplicationContext context = new ClassPathXmlApplicationContext("beans_editor.xml"); Boy boy = (Boy) context.getBean("boy"); System.out.println(boy); System.out.println(boy.getBirthday()); } 复制代码
执行结果为:
Boy{name='hresh', birthday=Mon Jan 01 00:00:00 CST 1996} Mon Jan 01 00:00:00 CST 1996 复制代码
看到注入的属性被转换为 Date 类型填充到 Boy 对象中,也得到正确的输出结果,但是想了一个问题: 如果我们要获取原值该怎么获取? 只有通过 DateEditor 类中的 getAsText()
方法才能获取到属性转换前的值,但是问题是获取不到 DateEditor 的实例对象。一番查看在 beanFactory 中看到一点有用信息,但是无法后续处理。
查了一些资料,发现通过 BeanWrapper 和 PropertyEditor 结合使用也能对属性进行转换,不过这已经不涉及到 XML 文件的使用。代码如下:
@Test public void testCustomEditorForSingleProperty(){ Boy boy = new Boy(); BeanWrapperImpl beanWrapper = new BeanWrapperImpl(boy); DateEditor dateEditor = new DateEditor(); dateEditor.setDateFormat(new SimpleDateFormat("yyyy年MM月dd日")); beanWrapper.registerCustomEditor(Date.class,"birthday",dateEditor); beanWrapper.setPropertyValue("name","hresh"); beanWrapper.setPropertyValue("birthday","1997年1月1日"); System.out.println(boy.getBirthday()); System.out.println(beanWrapper.getPropertyValue("birthday")); System.out.println(dateEditor.getAsText()); beanWrapper.setPropertyValue("name","hresh"); beanWrapper.setPropertyValue("birthday","1998年1月1日"); System.out.println(boy.getBirthday()); System.out.println(dateEditor.getAsText()); } 复制代码
执行结果为:
Wed Jan 01 00:00:00 CST 1997 Wed Jan 01 00:00:00 CST 1997 1997年01月01日 Thu Jan 01 00:00:00 CST 1998 1998年01月01日 复制代码
如果有大佬了解上述提到的问题,请不吝赐教。
自定义属性编辑器
在 Spring bean之间的关系 一文中讲述了 bean 之间的几种关系,其中介绍的案例中的 Person 类中有个 Car 对象属性,在依赖和引用关系中都可以实现,接下来我们再介绍一种实现方式。
为 Car 类提供一个自定义的属性编辑器,然后通过字面值为 Person 的 car 属性提供配置值。
现在,我们来为 Car 编写一个自定义的属性编辑器,其代码如下所示:
public class CustomCarEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { if(text == null || text.indexOf(",") == -1){ throw new IllegalArgumentException("设置的字符串格式不正确"); } String[] infos = text.split(","); Car car = new Car(); car.setBrand(infos[0]); car.setMaxSpeed(Integer.valueOf(infos[1])); car.setPrice(Double.valueOf(infos[2])); car.setColor(infos[3]); setValue(car); } } 复制代码
CustomCarEditor 很简单,它仅覆盖 PropertyEditorSupport 类的 setAsText(String text)
方法,该方法负责将配置文件以字符串提供的字面值转换为 Car 对象。字面值采用逗号分隔的格式同时为 brand、maxSpeed、price 和 color属性值提供设置值, setAsText()
方法解析这个字面值并生成对应的 Car 对象。由于我们并不需要将 Person 内部的 car 属性反显到属性编辑器中,因此不需要覆盖 getAsText()
方法。
定义 XML 文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--③该属性将使用②处的属性编辑器完成属性填充操作--> <bean id="person" class="com.msdn.bean.Person" > <property name="name" value="hresh" /> <property name="car" value="东风,299,5400,银色" /> <property name="desc" value="xxxxx" /> </bean> <!--①配置自动注册属性编辑器的CustomEditorConfigurer --> <bean id="editorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map><!--②-1属性编辑器对应的属性类型 2对应的属性编辑器Bean--> <entry key="com.msdn.bean.Car" value="com.msdn.editor.CustomCarEditor" /> </map> </property> </bean> </beans> 复制代码
在①处,我们定义了用于注册自定义属性编辑器的 CustomEditorConfigurer,Spring 容器将通过反射机制自动调用这个 Bean。CustomEditorConfigurer 通过一个 Map 属性定义需要自动注册的自定义属性编辑器。在②处,我们为 Car 类型指定了对应属性编辑器 CustomCarEditor,注意键是属性类型,而值是对应的属性编辑器Bean,而不是属性编辑器的类名。
最精彩的部分当然是③处的配置,我们原来通过一个元素标签配置好 car Bean,然后在 person 的中通过 ref 引用 car Bean,但是现在我们直接通过 value 为 car 属性提供配置。BeanWrapper 在设置person 的car属性时,它将检索自定义属性编辑器的注册表,当发现 Car 属性类型拥有对应的属性编辑器 CustomCarEditor 时,它就会利用 CustomCarEditor 将"东风,299,5400,银色"转换为 Car 对象。
测试代码如下:
@Test public void updateBean(){ ApplicationContext context = new ClassPathXmlApplicationContext("person_car.xml"); Person person = (Person) context.getBean("person"); System.out.println(person); } 复制代码
执行结果为:
调用Car类的无参构造函数 Person{name='hresh', 拥有一辆car=Car{maxSpeed=299, price=5400.0, brand='东风', color='银色'}} 复制代码
https://www.cnblogs.com/Tony-Mu/articles/2565527.html
https://blog.csdn.net/pentiumchen/article/details/44026575
https://www.jianshu.com/p/aeb97395d9c5