GVKun编程网logo

spring源码分析-PropertyEditor(spring源码分析和总结简书)

11

在本文中,我们将带你了解spring源码分析-PropertyEditor在这篇文章中,我们将为您详细介绍spring源码分析-PropertyEditor的方方面面,并解答spring源码分析和总结

在本文中,我们将带你了解spring源码分析-PropertyEditor在这篇文章中,我们将为您详细介绍spring源码分析-PropertyEditor的方方面面,并解答spring源码分析和总结简书常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的2. Spring早期类型转换,基于PropertyEditor实现、4.盘点springmvc的常用接口之PropertyEditor、com.intellij.uiDesigner.propertyInspector.editors.PrimitiveTypeEditor的实例源码、com.intellij.uiDesigner.propertyInspector.PropertyEditor的实例源码

本文目录一览:

spring源码分析-PropertyEditor(spring源码分析和总结简书)

spring源码分析-PropertyEditor(spring源码分析和总结简书)

     在spring源码中,在很多地方都会出现PropertyEditor,那么属性编辑器用来干什么的呢?属性编辑器主要应用在以下两个方面:

  • 使用PropertyEditors设置Bean属性。当你在XML文件中声明的bean的属性类型为java.lang.String时,Spring将在容器初始化该bean时使用ClassEditor将String解析成Class对象(如果setter方法需要一个Class参数的话)。这么说有点难以理解,请看下面这个代码片段:
<bean>
    <property name="clazz" 
              value="com.github.thinwonton.spring.source.analysis.propertyeditor.Order"/>
</bean>

public class ClassHolder {
    private Class<?> clazz;

    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }
}
  • 在Spring MVC架构中使用各种PropertyEditors来解析HTTP请求中的参数。

  Spring提供了许多内建的PropertyEditors可以简化我们的工作,它们都位于org.springframework.beans.PropertyEditors包内。它们中的大多数已经默认在BeanWrapperImpl的实现类中注册好了。作为可配置的选项,我们也可以注册自己的属性编辑器实现去覆盖那些默认编辑器。

    由于PropertyEditor是spring源码分析的基础,于是决定研究一下PropertyEditor。在探索的过程中,发现了下面这篇分析PropertyEditor比较好的文章,就不想再造轮子了。后面还会写一篇关于怎么 在spring中使用自定义PropertyEditor的文章(https://my.oschina.net/thinwonton/blog/1492107),用于补充下面的内容。

=============================================================

    在spring配置文件或配置类里,通过字符串为Bean各种属性提供设置值:不管是double类型还是int类型,在配置文件中都对应字符串类型。BeanWrapper填充Bean属性时如何将这个字面值转换为对应的double或int等内部类型呢?我们可以隐约地感觉到一定有一个转换器在其中起作用,这个转换器就是属性编辑器。

 

1、PropertyEditor

    java.beans.PropertyEditor是属性编辑器的接口,它规定了将外部设置值转换为内部JavaBean属性值的转换接口方法。PropertyEditor主要的接口方法说明如下: 

  • Object getValue():返回属性的当前值。基本类型被封装成对应的封装类实例; 
  •  void setValue(Object newValue):设置属性的值,基本类型以封装类传入; 
  • String getAsText():将属性对象用一个字符串表示,以便外部的属性编辑器能以可视化的方式显示。缺省返回null,表示该属性不能以字符串表示; 
  •  void setAsText(String text):用一个字符串去更新属性的内部值,这个字符串一般从外部属性编辑器传入; 
  •  String[] getTags():返回表示有效属性值的字符串数组(如boolean属性对应的有效Tag为true和false),以便属性编辑器能以下拉框的方式显示出来。缺省返回null,表示属性没有匹配的字符值有限集合; 
  •  String getJavaInitializationString():为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值。

    可以看出PropertyEditor接口方法是内部属性值和外部设置值的沟通桥梁。此外,我们可以很容易地发现该接口的很多方法是专为IDE中的可视化属性编辑器提供的:如getTags()、getJavaInitializationString()以及另外一些我们未此介绍的接口方法。

 

PropertyEditorSupport

    Java为PropertyEditor提供了一个方便类:PropertyEditorSupport,该类实现了PropertyEditor接口并提供默认实现,一般情况下,用户可以通过扩展这个方便类设计自己的属性编辑器。

 

spring中的属性编辑器

    Spring大部分默认属性编辑器都直接扩展于java.beans.PropertyEditorSupport类,用户也可以通过扩展PropertyEditorSupport实现自己的属性编辑器。比起用于IDE环境的属性编辑器来说,Spring环境下使用的属性编辑器的功能非常单一:仅需要将配置文件中字面值转换为属性类型的对象即可,并不需要提供UI界面,因此仅需要简单覆盖PropertyEditorSupport的setAsText()方法就可以了。 

举个栗子:

实现 UUIDEditor 
 

public class UUIDEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (StringUtils.hasText(text)) {
            setValue(UUID.fromString(text));
        }
        else {
            setValue(null);
        }
    }

    @Override
    public String getAsText() {
        UUID value = (UUID) getValue();
        return (value != null ? value.toString() : "");
    }

}
public static void main(String[] args) throws ClassNotFoundException, IOException {

        //UUID对象转化为字符串
        UUIDEditor editor=new UUIDEditor();
        editor.setValue(UUID.randomUUID());
        System.out.println(editor.getAsText());//c2878055-fb49-4559-a7db-c60fc3ebee79

        //字符串转化为UUID
        UUIDEditor editor_2=new UUIDEditor();
        editor_2.setAsText("2-1-1-2-3");
        System.out.println(editor_2.getAsText());//00000002-0001-0001-0002-000000000003
        System.out.println(editor_2.getValue().getClass());//class java.util.UUID

}

 

2、PropertyEditorRegistry

这个接口可以注册和保存属性编辑器

public interface PropertyEditorRegistry {

    void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);

    void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);

    PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath);

}

PropertyEditorRegistrySupport

PropertyEditorRegistrySupport是PropertyEditorRegistry 默认实现类 
PropertyEditorRegistrySupport中有两个用于保存属性编辑器的Map类型变量:

private Map<Class<?>, PropertyEditor> defaultEditors;

private Map<Class<?>, PropertyEditor> customEditors;

PropertyEditorRegistrySupport通过以下的代码定义默认属性编辑器:

	private void createDefaultEditors() {
		this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(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(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());
		if (zoneIdClass != null) {
			this.defaultEditors.put(zoneIdClass, 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);
		}
	}

 

2. Spring早期类型转换,基于PropertyEditor实现

2. Spring早期类型转换,基于PropertyEditor实现

青年时种下什么,老年时就收获什么。关注公众号【BAT的乌托邦】,有Spring技术栈、MyBatis、JVM、中间件等小而美的原创专栏供以免费学习。分享、成长,拒绝浅尝辄止。本文已被 https://www.yourbatman.cn 收录。

✍前言

你好,我是YourBatman。

Spring早在1.0(2004年发布,2003年孵化中)的时候,就有了类型转换功能模块。此模块存在的必要性不必多说,相信每个同学都可理解。最初,Spring做类型转换器是基于Java标准的java.beans.PropertyEditor这个API去扩展实现的,直到Spring 3.0后才得以出现更好替代方案(Spring 3.0发布于2009 年12月)。

提示:文章末尾附有Spring主要版本的发布时间和以及主要特性,感兴趣者可文末查看

虽说Spring自3.0就提出了更为灵活、优秀的类型转换接口/服务,但是早期基于PropertyEditor实现的转换器并未废弃且还在发挥余热中,因此本文就针对其早期类型转换实现做出专文讲解。

版本约定

  • Spring Framework:5.3.1
  • Spring Boot:2.4.0
说明:版本均于2020-11发布,且版本号均不带有.RELEASE后缀,这和之前是不一样的。具体原因请参考:Spring改变版本号命名规则:此举对非英语国家很友好

✍正文

若你用当下的眼光去看Spring基于PropertyEditor的类型转换实现,会发现这么搞是存在一些设计缺陷的。当然并不能这么去看,毕竟现在都2020年了,那会才哪跟哪呢。既然Spring里的PropertyEditor现如今依然健在,那咱就会会它呗。

PropertyEditor是什么?

PropertyEditor位于java.beans包中,要知道这个包里面的类都是设计为Java GUI程序(AWT)服务的,所以你看官方javadoc对PropertyEditor的介绍也无出其右:

A PropertyEditor class provides support for GUIs that want to allow users to edit a property value of a given type.

为GUI程序提供支持,允许你对给定的value进行编辑,作用类似于一个转换器:GUI上你可以输入、编辑某个属性然后经过它转换成合适的类型。
Java GUI程序

此接口提供的方法挺多的,和本文类型转换有关的最多只有4个:

  1. void setValue(Object value):设置属性值
  2. Object getValue():获取属性值
  3. String getAsText():输出。把属性值转换成String输出
  4. void setAsText(String text):输入。将String转换为属性值类型输入

JDK对PropertyEditor接口提供了一个默认实现java.beans.PropertyEditorSupport,因此我们若需扩展此接口,仅需继承此类,根据需要复写getAsText/setAsText这两个方法即可,Spring无一例外都是这么做的。

PropertyEditor作为一个JDK原生接口,内置了一些基本实现来服务于GUI程序,如:

  • BooleanEditor:将true/false字符串转换为Boolean类型
  • IntegerEditor:将字符串转换为Integer类型

    • 同类别的还有LongEditor、FloatEditor...

JDK内置的实现比较少(如上),功能简陋,但对于服务GUI程序来说已经够用,毕竟界面输入的只可能是字符串,并且还均是基础类型。但这对于复杂的Spring环境、以及富文本的web环境来说就不够用了,所以Spring在此基础上有所扩展,因此才有了本文来讨论。

注意:PropertyEditorSupport线程不安全

PropertyEditor实现的是双向类型转换:String和Object互转。调用setValue()方法后,需要先“缓存”起来后续才能够使用(输出)。PropertyEditorSupport为此提供了一个成员属性来做:

PropertyEditorSupport:

    // 调用setValue()方法对此属性赋值   getValue()方法取值
    private Object value;

这么一来PropertyEditorSupport就是有状态的了,因此是线程不安全的。在使用过程中需要特别注意,避免出现并发风险。

说明:Support类里还看到属性监听器PropertyChangeListener,因它属于GUI程序使用的组件,与我们无关,所以后续丝毫不会提及哦

Spring内置的所有扩展均是基于PropertyEditorSupport来实现的,因此也都是线程不安全的哦~

Spring为何基于它扩展?

官方的javadoc都说得很清楚:PropertyEditor设计是为GUI程序服务的,那么Spring为何看上它了呢?

试想一下:那会的Spring只能支持xml方式配置,而XML属于文本类型配置,因此在给某个属性设定值的时候,书写上去的100%是个字符串,但是此属性对应的类型却不一定是字符串,可能是任意类型。你思考下,这种场景是不是跟GUI程序(AWT)一毛一样:输入字符串,对应任意类型。

为了实现这种需求,在PropertyEditorSupport的基础上只需要复写setAsTextgetAsText这两个方法就行,然后Spring就这么干了。我个人yy一下,当初Spring选择这么干而没自己另起炉灶的原因可能有这么几个:

  1. 本着不重复发明轮子的原则,有得用就直接用呗,况且是100%满足要求的
  2. 示好Java EE技术。毕竟那会Spring地位还并不稳,有大腿就先榜上
  3. 2003年左右,Java GUI程序还并未退出历史舞台,Spring为了通用性就选择基于它扩展喽

    1. 说明:那会的通用性可能和现在通用性含义上是不一样的,需要稍作区别

Spring内建扩展实现有哪些?

Spring为了扩展自身功能,提高配置灵活性,扩展出了非常非常多的PropertyEditor实现,共计40余个,部分截图如下:

PropertyEditor功能举例
ZoneIdEditor转为java.time.ZoneIdAsia/Shanghai
URLEditor转为URL,支持传统方式file:,http:,也支持Spring风格:classpath:,context上下文相对路径等等http://www.baidu.com
StringTrimmerEditortrim()字符串,也可删除指定字符char任意字符串
StringArrayPropertyEditor转为字符串数组A,B,C
PropertiesEditor转为Propertiesname = YourBatman
PatternEditor转为Pattern(\D)(\d+)(.)
PathEditor转为java.nio.file.Path。支持传统URL和Spring风格的urlclasspath:xxx
ClassEditor转为Class全类名
CustomBooleanEditor转为Boolean见示例
CharsetEditor转为Charset见示例
CustomDateEditor转为java.util.Date见示例

Spring把实现基本(大多数)都放在org.springframework.beans.propertyeditors包下,接下来我挑选几个代表性API举例说明。

标准实现示例

  • CustomBooleanEditor
@Test
public void test1() {
    PropertyEditor editor = new CustomBooleanEditor(true);

    // 这些都是true,不区分大小写
    editor.setAsText("trUe");
    System.out.println(editor.getAsText());
    editor.setAsText("on");
    System.out.println(editor.getAsText());
    editor.setAsText("yes");
    System.out.println(editor.getAsText());
    editor.setAsText("1");
    System.out.println(editor.getAsText());

    // 这些都是false(注意:null并不会输出为false,而是输出空串)
    editor.setAsText(null);
    System.out.println(editor.getAsText());
    editor.setAsText("fAlse");
    System.out.println(editor.getAsText());
    editor.setAsText("off");
    System.out.println(editor.getAsText());
    editor.setAsText("no");
    System.out.println(editor.getAsText());
    editor.setAsText("0");
    System.out.println(editor.getAsText());

    // 报错
    editor.setAsText("2");
    System.out.println(editor.getAsText());
}

关注点:对于Spring来说,传入的true、on、yes、1等都会被“翻译”成true(字母不区分大小写),大大提高兼容性。

现在知道为啥你用Postman传个1,用Bool值也能正常封装进去了吧,就是它的功劳。此效果等同于转换器StringToBooleanConverter,将在后面进行讲述对比
  • CharsetEditor
@Test
public void test2() {
    // 虽然都行,但建议你规范书写:UTF-8
    PropertyEditor editor = new CharsetEditor();
    editor.setAsText("UtF-8");
    System.out.println(editor.getAsText()); // UTF-8
    editor.setAsText("utF8");
    System.out.println(editor.getAsText()); // UTF-8
}

关注点:utf-8中间的横杠可要可不要,但建议加上使用标准写法,另外字母也是不区分大小写的。

  • CustomDateEditor
@Test
public void test3() {
    PropertyEditor editor = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),true);
    editor.setAsText("2020-11-30 09:10:10");
    System.out.println(editor.getAsText()); // 2020-11-30 09:10:10

    // null输出空串
    editor.setAsText(null);
    System.out.println(editor.getAsText());

    // 报错
    editor.setAsText("2020-11-30");
    System.out.println(editor.getAsText());
}

关注点:这个时间/日期转换器很不好用,构造时就必须指定一个SimpleDateFormat格式化器。在实际应用中,Spring并没有使用到它,而是用后面会说到的替代方案。

特殊实现

把没有放在org.springframework.beans.propertyeditors包下的实现称作特殊实现(前者称为标准实现)。

  • MediaTypeEditor:位于org.springframework.http。依赖的核心API是MediaType.parseMediaType(text),可以把诸如text/html、application/json转为MediaType对象

    • 显然它属于spring-web包,使用在web环境下
  • FormatterPropertyEditorAdapter:位于org.springframework.format.support。将3.0新增的Formatter接口适配为一个PropertyEditor:setAsText这种转换操作委托给formatter.parse()去完成,反向委托给formatter.print()去完成。

    • 此适配器在DataBinder#addCustomFormatter()得到应用
  • PropertyValuesEditor:位于org.springframework.beans。将k-v字符串(Properties格式)转换为一个PropertyValues,从而方便放进Environment里
  • ResourceEditor:位于org.springframework.core.io。此转换器将String转换为Resource资源,特别实用。作为基础设施,广泛被用到Spring的很多地方

    • 像什么标准的FileEditor、InputSourceEditor、InputStreamEditor、URLEditor等等与资源相关的转换器,均依赖它而实现
@Test
public void test4() {
    // 支持标准URL如file:C:/myfile.txt,也支持classpath:myfile.txt
    // 同时还支持占位符形式
    PropertyEditor editor = new ResourceEditor(new DefaultResourceLoader(), new StandardEnvironment(), true);

    // file:形式本处略
    // editor.setAsText("file:...");
    // System.out.println(editor.getAsText());

    // classpath形式(注意:若文件不存在不会报错,而是输出null)
    editor.setAsText("classpath:app.properties");
    System.out.println(editor.getAsText()); // 输出带盘符的全路径

    System.setProperty("myFile.name", "app.properties");
    editor.setAsText("classpath:${myFile.name}");
    System.out.println(editor.getAsText()); // 结果同上
}

关注点:Spring扩展出来的Resource不仅自持常规file:资源协议,还支持平时使用最多的classpath:协议,可谓非常好用。

  • ConvertingPropertyEditorAdapter:位于org.springframework.core.convert.support。将3.0新增的ConversionService转换服务适配为一个PropertyEditor,内部转换动作都委托给前者去完成。

    • AbstractPropertyBindingResult#findEditor()为属性寻找合适PropertyEditor的时候,若ConversionService能支持就包装为ConvertingPropertyEditorAdapter供以使用,这是适配器模式的典型应用场景。

“谁”在使用ProertyEditor

PropertyEditor自动发现机制

PropertyEditor存在的缺陷

考虑到阅读的舒适性,单篇文章不宜太长,因此涉及到PropertyEditor的这几个问题,放在下篇文章单独列出。这个几个问题会明显比本文更深入,欢迎保持持续关注,peace!

✍总结

本文主要介绍了三点内容:

  1. PropertyEditor是什么?
  2. Spring为何选择基于PropertyEditor?
  3. Spring内建的那些PropertyEditor都有哪些,各自什么作用?

PropertyEditor虽然已经很古老,不适合当下复杂环境。但不可否认它依旧有存在的价值,Spring内部也大量的仍在使用,因此不容忽视。下篇文章将深度探讨Spring内部是如何使用PropertyEditor的,赋予了它哪些机制,以及最终为何还是决定自己另起炉灶搞一套呢?欢迎对本系列保持持续关注~


附:Spring主要版本发布时间和特性

  • 2002-02,开始开发孵化此项目,项目名叫:interface21,它便就是Spring的前身
  • 2004-03,1.0版发布。进入迅速发展期
  • 2006-10,2.0版发布。支持可扩展的xml配置功能、支持Java5、支持动态语言、支持更多扩展点
  • 2007-11,2.5版发布。项目名从此改为Spring Source,支持Java 6,支持注解配置(部分)
  • 2009-12,3.0版发布。这是非常非常重要的一个版本,支持了模块驱动、支持SpEL、支持Java Bean配置、支持嵌入式数据库......
  • 2011和2012,这两年发布了非常多的3.x系列小版本,带来了很多惊喜,同时也让Spring更加扎实
  • 2013-12,4.0版发布。这是有一次进步,提供了对Java 8的全面支持,支持Java EE 7、支持websocket、泛型依赖注入......
  • 2017-09,5.0版发布。最低JDK版本要求是Java 8,同时支持Servlet 3.1。当然最为重要的是引入了全新模块:WebFlux

截止到当前,Spring Framework的最新版本是5.3.x版本,此版本是5.x的最后一个主要功能分支,下个版本将是6.x喽,咱们拭目以待。


✔推荐阅读:
  • 1. 揭秘Spring类型转换 - 框架设计的基石
  • 蚂蚁金服上市了,我不想努力了
  • 如果程序员和产品经理都用凡尔赛文学对话......
  • 程序人生 | 春风得意马蹄疾,一日看尽长安花

♥关注A哥♥

AuthorA哥(YourBatman)
个人站点www.yourbatman.cn
E-mailyourbatman@qq.com
微 信fsx1056342982
活跃平台
公众号BAT的乌托邦(ID:BAT-utopia)
知识星球BAT的乌托邦
每日文章推荐每日文章推荐

BAT的乌托邦

4.盘点springmvc的常用接口之PropertyEditor

4.盘点springmvc的常用接口之PropertyEditor

4.盘点springmvc的常用接口之PropertyEditor###

java.beans.PropertyEditor严格上来说,其实并不能算spring框架的接口,很明显看包名就明白此类是JDK自带的。是Sun所制定的一套JavaBean规范,是为IDE图形化界面准备设置属性值的接口。看接口源码上的说明:

A PropertyEditor class provides support for GUIs that want to allow users to edit a property value of a giventype.

这接口原本是使用在GUI图形程序中,允许用户给giventype设定属性值的。(不熟悉图形化界面API,不知道giventype是什么,怎么翻译)

接口说明

public interface PropertyEditor {

    void setValue(Object value);

    Object getValue();

    boolean isPaintable();

    void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box);

    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);
}

不要被这么多的方法给吓到,对于我们使用者来说其实重点只有下面4个方法:

  • void setValue(Object value);设置属性值
  • Object getValue();获取属性值
  • String getAsText(); 把属性值转换成string
  • void setAsText(String text);把string转换成属性值

所以Java很机智地提供了一个适配器java.beans.PropertyEditorSupport来帮助我们实现属性值的转换,它帮助我们实现了GUI部分的接口,我们只需要重写getAsTextsetAsText的逻辑。

那么这个接口跟spring有什么鸟关系呢,这个接口有什么用呢?请看示例1。

示例1

一个由区号和号码组成的座机号码实体类:

package com.demo.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Telephone {

	/* 区号 */
	private String areaCode;
	/* 号码 */
	private String phone;

	@Override
	public String toString() {
		return areaCode + "-" + phone;
	}
}

一个人的实体(屏蔽了无关属性,只有座机-_-):

package com.demo.domain;

import org.springframework.beans.factory.annotation.Value;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class PersonEntity {

	@Value("010-12345")
	private Telephone telephone;
}

@Value是spring的注解,相当于xml配置<property name="" value=""/>里面的value,给属性赋值的。但是请注意,这里赋的是字符串。

那么问题来了,这个010-12345的字符串是怎么赋到Telephone这个类对象上的呢?类型根本转不过来。

答案就是实现转换器PropertyEditor

package com.demo.mvc.component;

import java.beans.PropertyEditorSupport;

import com.demo.domain.Telephone;

public class TelephonePropertyEditor extends PropertyEditorSupport {

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		if (text.matches("^\\d+\\-\\d+$")) {
			String[] strs = text.split("\\-");
			setValue(new Telephone(strs[0], strs[1]));
		} else {
			throw new IllegalArgumentException();
		}
	}

	@Override
	public String getAsText() {
		return ((Telephone) getValue()).toString();
	}
}

实现完了接口还得注册到spring容器让spring管理:

<bean/>

<bean id="customEditorConfigurer">
  <property name="customEditors">
    <map>
      <entry key="com.demo.domain.Telephone" value="com.demo.mvc.component.TelephonePropertyEditor" />
    </map>
  </property>
</bean>

这个CustomEditorConfigurer类顾名思义就是配置自定义的属性转换器的。把自定义的转换器统统放进这个名叫customEditors的Map结构里。用key指定Telephone,value指定TelephonePropertyEditor。

就是告诉spring,当遇到Telephone这个类型时,使用TelephonePropertyEditor把字符串转化成Telephone。

单元测试:

package com.junit;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.demo.domain.PersonEntity;
import com.demo.domain.Telephone;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring/spring-context.xml")
public class PropertyEditorlJunitTest {

	@Resource
	private PersonEntity personEntity;

	@Test
	public void test() {
		Telephone telephone = personEntity.getTelephone();
		System.out.printf("区号:%s,号码:%s\n", telephone.getAreaCode(), telephone.getPhone());
	}
}

最后控制台输出结果:

区号:010,号码:12345

示例2

示例1是普通spring情况下使用PropertyEditor的例子,那么怎么用在springmvc上呢?

package com.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demo.domain.Telephone;
import com.demo.mvc.component.TelephonePropertyEditor;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("demo4")
public class PropertyEditorDemoController {

	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.registerCustomEditor(Telephone.class, new TelephonePropertyEditor());
	}

	@ResponseBody
	@RequestMapping(method = RequestMethod.POST)
	public String postTelephone(@RequestParam Telephone telephone) {
		log.info(telephone.toString());
		return telephone.toString();
	}
}

使用@InitBinder注解,在WebDataBinder对象上注册转换器。

这样在访问POST http://localhost:8080/demo4 传表单数据 telephone = 010-12345

010-12345的字符串会自动转换为Telephone对象。

当然这种方式有一个缺点,该转换器只在当前的controller下有效。

想要实现全局转换,那就得实现WebBindingInitializer接口

package com.demo.mvc.component;

import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;

import com.demo.domain.Telephone;

public class MyWebBindingInitializer implements WebBindingInitializer {

	@Override
	public void initBinder(WebDataBinder binder, WebRequest request) {
		binder.registerCustomEditor(Telephone.class, new TelephonePropertyEditor());
	}

}

在这里可以批量注册转换器。

spring-boot注册:

package com.demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import com.demo.mvc.component.MyWebBindingInitializer;

@SpringBootApplication
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

	@Override
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
		RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
		adapter.setWebBindingInitializer(new MyWebBindingInitializer());
		return adapter;
	}
}

xml注册:

<bean>  
  <property name="webBindingInitializer">  
    <bean/>  
  </property>  
</bean>

p.s.:这一章介绍了这么大篇幅的PropertyEditor的使用,最后再说一句(不要打我哦)上述介绍的方法其实都已过时-_-。

在spring3.x后,新出了一个更强大的转换器机制,Converter!

那么下一章再来讲解强大的Converter吧。

友情链接:

盘点springmvc的常用接口目录

com.intellij.uiDesigner.propertyInspector.editors.PrimitiveTypeEditor的实例源码

com.intellij.uiDesigner.propertyInspector.editors.PrimitiveTypeEditor的实例源码

项目:intellij-ce-playground    文件:IntroPrimitiveTypeProperty.java   
protected propertyeditor<T> createEditor() {
  return new PrimitiveTypeEditor<T>(myClass);
}
项目:intellij-ce-playground    文件:RadGridBagLayoutManager.java   
public propertyeditor<Double> getEditor() {
  if (myEditor == null) {
    myEditor = new PrimitiveTypeEditor<Double>(Double.class);
  }
  return myEditor;
}
项目:tools-idea    文件:IntroPrimitiveTypeProperty.java   
protected propertyeditor<T> createEditor() {
  return new PrimitiveTypeEditor<T>(myClass);
}
项目:tools-idea    文件:RadGridBagLayoutManager.java   
public propertyeditor<Double> getEditor() {
  if (myEditor == null) {
    myEditor = new PrimitiveTypeEditor<Double>(Double.class);
  }
  return myEditor;
}
项目:consulo-ui-designer    文件:IntroPrimitiveTypeProperty.java   
protected propertyeditor<T> createEditor() {
  return new PrimitiveTypeEditor<T>(myClass);
}
项目:consulo-ui-designer    文件:RadGridBagLayoutManager.java   
public propertyeditor<Double> getEditor() {
  if (myEditor == null) {
    myEditor = new PrimitiveTypeEditor<Double>(Double.class);
  }
  return myEditor;
}

com.intellij.uiDesigner.propertyInspector.PropertyEditor的实例源码

com.intellij.uiDesigner.propertyInspector.PropertyEditor的实例源码

项目:intellij-ce-playground    文件:AbstractInsetsProperty.java   
public final propertyeditor<Insets> getEditor() {
  if (myEditor == null) {
    myEditor = new IntRegexEditor<Insets>(Insets.class,myRenderer,new int[] { 0,0 }) {
      public Insets getValue() throws Exception {
        // if a single number has been entered,interpret it as same value for all parts (IDEADEV-7330)
        try {
          int value = Integer.parseInt(myTf.getText());
          final Insets insets = new Insets(value,value,value);
          myTf.setText(myRenderer.formatText(insets));
          return insets;
        }
        catch(NumberFormatException ex) {
          return super.getValue();
        }
      }
    };
  }
  return myEditor;
}
项目:tools-idea    文件:AbstractInsetsProperty.java   
public final propertyeditor<Insets> getEditor() {
  if (myEditor == null) {
    myEditor = new IntRegexEditor<Insets>(Insets.class,value);
          myTf.setText(myRenderer.formatText(insets));
          return insets;
        }
        catch(NumberFormatException ex) {
          return super.getValue();
        }
      }
    };
  }
  return myEditor;
}
项目:consulo-ui-designer    文件:AbstractInsetsProperty.java   
public final propertyeditor<Insets> getEditor() {
  if (myEditor == null) {
    myEditor = new IntRegexEditor<Insets>(Insets.class,value);
          myTf.setText(myRenderer.formatText(insets));
          return insets;
        }
        catch(NumberFormatException ex) {
          return super.getValue();
        }
      }
    };
  }
  return myEditor;
}
项目:intellij-ce-playground    文件:Palette.java   
private void updateUI(final Property property) {
  final PropertyRenderer renderer = property.getRenderer();
  renderer.updateUI();
  final propertyeditor editor = property.getEditor();
  if (editor != null) {
    editor.updateUI();
  }
  final Property[] children = property.getChildren(null);
  for (int i = children.length - 1; i >= 0; i--) {
    updateUI(children[i]);
  }
}
项目:intellij-ce-playground    文件:IntroIntProperty.java   
public IntroIntProperty(final String name,final Method readMethod,final Method writeMethod,final PropertyRenderer<Integer> renderer,final propertyeditor<Integer> editor,final boolean storeAsClient){
  super(name,readMethod,writeMethod,storeAsClient);
  myRenderer = renderer;
  myEditor = editor;
}
项目:tools-idea    文件:Palette.java   
private void updateUI(final Property property){
  final PropertyRenderer renderer = property.getRenderer();
  renderer.updateUI();
  final propertyeditor editor = property.getEditor();
  if(editor != null){
    editor.updateUI();
  }
  final Property[] children = property.getChildren(null);
  for (int i = children.length - 1; i >= 0; i--) {
    updateUI(children[i]);
  }
}
项目:tools-idea    文件:IntroIntProperty.java   
public IntroIntProperty(final String name,storeAsClient);
  myRenderer = renderer;
  myEditor = editor;
}
项目:consulo-ui-designer    文件:Palette.java   
private void updateUI(final Property property){
  final PropertyRenderer renderer = property.getRenderer();
  renderer.updateUI();
  final propertyeditor editor = property.getEditor();
  if(editor != null){
    editor.updateUI();
  }
  final Property[] children = property.getChildren(null);
  for (int i = children.length - 1; i >= 0; i--) {
    updateUI(children[i]);
  }
}
项目:consulo-ui-designer    文件:IntroIntProperty.java   
public IntroIntProperty(final String name,storeAsClient);
  myRenderer = renderer;
  myEditor = editor;
}
项目:intellij-ce-playground    文件:BindingProperty.java   
public propertyeditor<String> getEditor(){
  return myEditor;
}
项目:intellij-ce-playground    文件:IntFieldProperty.java   
public propertyeditor<Integer> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:AbstractIntProperty.java   
@Nullable public propertyeditor<Integer> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:CustomCreateProperty.java   
public propertyeditor<Boolean> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:BorderProperty.java   
public propertyeditor<BorderType> getEditor() {
  return null;
}
项目:intellij-ce-playground    文件:ButtonGroupProperty.java   
public propertyeditor<RadButtonGroup> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:AbstractDimensionProperty.java   
public final propertyeditor<Dimension> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:AbstractGridLayoutProperty.java   
public propertyeditor<Boolean> getEditor(){
  return myEditor;
}
项目:intellij-ce-playground    文件:LayoutManagerProperty.java   
public propertyeditor<String> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:IntroPrimitiveTypeProperty.java   
public propertyeditor<T> getEditor(){
  if (myEditor == null) {
    myEditor = createEditor();
  }
  return myEditor;
}
项目:intellij-ce-playground    文件:IntroPrimitiveTypeProperty.java   
protected propertyeditor<T> createEditor() {
  return new PrimitiveTypeEditor<T>(myClass);
}
项目:intellij-ce-playground    文件:IntroRectangleProperty.java   
public propertyeditor<Rectangle> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:IntroDimensionProperty.java   
public propertyeditor<Dimension> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:IntroInsetsProperty.java   
public propertyeditor<Insets> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:ClasstoBindProperty.java   
public propertyeditor<String> getEditor(){
  return myEditor;
}
项目:intellij-ce-playground    文件:IntroCharProperty.java   
protected propertyeditor<Character> createEditor() {
  return new CharEditor();
}
项目:intellij-ce-playground    文件:SizePolicyProperty.java   
public final propertyeditor<Integer> getEditor(){
  return null;
}
项目:intellij-ce-playground    文件:SizePolicyProperty.java   
public final propertyeditor<Boolean> getEditor(){
  return myEditor;
}
项目:intellij-ce-playground    文件:ClientPropertyProperty.java   
public propertyeditor getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:InplaceEditingLayer.java   
public void valueCommitted(final propertyeditor source,final boolean continueEditing,final boolean closeEditorOnError) {
  finishInplaceEditing();
}
项目:intellij-ce-playground    文件:InplaceEditingLayer.java   
public void editingCanceled(final propertyeditor source) {
  cancelInplaceEditing();
}
项目:intellij-ce-playground    文件:InplaceEditingLayer.java   
public void preferredSizeChanged(final propertyeditor source) {
  adjustEditorComponentSize();
}
项目:intellij-ce-playground    文件:RadGridBagLayoutManager.java   
public propertyeditor<Double> getEditor() {
  if (myEditor == null) {
    myEditor = new PrimitiveTypeEditor<Double>(Double.class);
  }
  return myEditor;
}
项目:intellij-ce-playground    文件:RadcardlayoutManager.java   
public propertyeditor<String> getEditor() {
  return myEditor;
}
项目:intellij-ce-playground    文件:RadBorderLayoutManager.java   
public propertyeditor<String> getEditor() {
  if (myEditor == null) {
    myEditor = new BorderSideEditor();
  }
  return myEditor;
}
项目:intellij-ce-playground    文件:RadContainer.java   
public propertyeditor<StringDescriptor> getEditor() {
  return myEditor;
}
项目:tools-idea    文件:BindingProperty.java   
public propertyeditor<String> getEditor(){
  return myEditor;
}
项目:tools-idea    文件:IntFieldProperty.java   
public propertyeditor<Integer> getEditor() {
  return myEditor;
}
项目:tools-idea    文件:AbstractIntProperty.java   
@Nullable public propertyeditor<Integer> getEditor() {
  return myEditor;
}
项目:tools-idea    文件:CustomCreateProperty.java   
public propertyeditor<Boolean> getEditor() {
  return myEditor;
}
项目:tools-idea    文件:BorderProperty.java   
public propertyeditor<BorderType> getEditor() {
  return null;
}

关于spring源码分析-PropertyEditorspring源码分析和总结简书的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于2. Spring早期类型转换,基于PropertyEditor实现、4.盘点springmvc的常用接口之PropertyEditor、com.intellij.uiDesigner.propertyInspector.editors.PrimitiveTypeEditor的实例源码、com.intellij.uiDesigner.propertyInspector.PropertyEditor的实例源码的相关知识,请在本站寻找。

本文标签: