customEditorsForPath作用解释
上面说了,它是和customEditors互斥的。
customEditorsForPath的作用是能够实现更精准匹配,针对属性级别精准处理。此Map的值通过此API注册进来:
public void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);
说明:propertyPath不能为null才进此处,否则会注册进customEditors喽
可能你会想,有了customEditors为何还需要customEditorsForPath呢?这里就不得不说两者的最大区别了:
- customEditors:粒度较粗,通用性强。key为类型,即该类型的转换全部交给此编辑器处理
- 如:registerCustomEditor(UUID.class,new UUIDEditor()),那么此编辑器就能处理全天下所有的String <-> UUID 转换工作
- customEditorsForPath:粒度细精确到属性(字段)级别,有点专车专座的意思
- 如:registerCustomEditor(Person.class, "cat.uuid" , new UUIDEditor()),那么此编辑器就有且仅能处理Person.cat.uuid属性,其它的一概不管
有了这种区别,注册中心在findCustomEditor(requiredType,propertyPath)匹配的时候也是按照优先级顺序执行匹配的:
- 若指定了propertyPath(不为null),就先去customEditorsForPath里找。否则就去customEditors里找
- 若没有指定propertyPath(为null),就直接去customEditors里找
为了加深理解,讲上场景用代码实现如下。
代码示例
创建一个Person类,关联Cat
@Data public class Cat extends Animal { private UUID uuid; } @Data public class Person { private Long id; private String name; private Cat cat; }
现在的需求场景是:
- UUID类型统一交给UUIDEditor处理(当然包括Cat里面的UUID类型)
- Person类里面的Cat的UUID类型,需要单独特殊处理,因此格式不一样需要“特殊照顾”
很明显这就需要两个不同的属性编辑器来实现,然后组织起来协同工作。Spring内置了UUIDEditor可以处理一般性的UUID类型(通用),而Person 专用的 UUID编辑器,自定义如下:
public class PersonCatUUIDEditor extends UUIDEditor { private static final String SUFFIX = "_YourBatman"; @Override public String getAsText() { return super.getAsText().concat(SUFFIX); } @Override public void setAsText(String text) throws IllegalArgumentException { text = text.replace(SUFFIX, ""); super.setAsText(text); } }
向注册中心注册编辑器,并且书写测试代码如下:
@Test public void test6() { PropertyEditorRegistry propertyEditorRegistry = new PropertyEditorRegistrySupport(); // 通用的 propertyEditorRegistry.registerCustomEditor(UUID.class, new UUIDEditor()); // 专用的 propertyEditorRegistry.registerCustomEditor(Person.class, "cat.uuid", new PersonCatUUIDEditor()); String uuidStr = "1-2-3-4-5"; String personCatUuidStr = "1-2-3-4-5_YourBatman"; PropertyEditor customEditor = propertyEditorRegistry.findCustomEditor(UUID.class, null); // customEditor.setAsText(personCatUuidStr); // 抛异常:java.lang.NumberFormatException: For input string: "5_YourBatman" customEditor.setAsText(uuidStr); System.out.println(customEditor.getAsText()); customEditor = propertyEditorRegistry.findCustomEditor(Person.class, "cat.uuid"); customEditor.setAsText(personCatUuidStr); System.out.println(customEditor.getAsText()); }
运行程序,打印输出:
00000001-0002-0003-0004-000000000005 00000001-0002-0003-0004-000000000005_YourBatman
完美。
customEditorsForPath相当于给你留了钩子,当你在某些特殊情况需要特殊照顾的时候,你可以借助它来搞定,十分的方便。
此方式有必要记住并且尝试,在实际开发中使用得还是比较多的。特别在你不想全局定义,且要确保向下兼容性的时候,使用抽象接口类型 + 此种方式缩小影响范围将十分有用
说明:propertyPath不仅支持Java Bean导航方式,还支持集合数组方式,如Person.cats[0].uuid这样格式也是ok的
PropertyEditorRegistrar
Registrar:登记员。它一般和xxxRegistry配合使用,其实内核还是Registry,只是运用了倒排思想屏蔽一些内部实现而已
public interface PropertyEditorRegistrar { void registerCustomEditors(PropertyEditorRegistry registry); }
同样的,Spring内部也有很多类似实现模式:
PropertyEditorRegistrar接口在Spring体系内唯一实现为:ResourceEditorRegistrar。它可值得我们絮叨絮叨。
ResourceEditorRegistrar
从命名上就知道它和Resource资源有关,实际上也确实如此:主要负责将ResourceEditor注册到注册中心里面去,用于处理形如Resource、File、URI等这些资源类型。
你配置classpath:xxx.xml用来启动Spring容器的配置文件,String -> Resource转换就是它的功劳喽
唯一构造器为:
public ResourceEditorRegistrar(ResourceLoader resourceLoader, PropertyResolver propertyResolver) { this.resourceLoader = resourceLoader; this.propertyResolver = propertyResolver; }
- resourceLoader:一般传入ApplicationContext
- propertyResolver:一般传入Environment
很明显,它的设计就是服务于ApplicationContext上下文,在Bean创建过程中辅助BeanWrapper实现资源加载、转换。
BeanFactory在初始化的准备过程中就将它实例化,从而具备资源处理能力:
AbstractApplicationContext: protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { ... beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader())); beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); ... }
这也是PropertyEditorRegistrar在Spring Framework的唯一使用处,值的关注。
PropertyEditor自动发现机制
最后介绍一个使用中的奇淫小技巧:PropertyEditor自动发现机制。
一般来说,我们自定义一个PropertyEditor是为了实现自定义类型 <-> 字符串的自动转换,它一般需要有如下步骤:
- 为自定义类型写好一个xxxPropertyEditor(实现PropertyEditor接口)
- 将写好的编辑器注册到注册中心PropertyEditorRegistry
显然步骤1属个性化行为无法替代,但步骤2属于标准行为,重复劳动是可以标准化的。自动发现机制就是用来解决此问题,对自定义的编辑器制定了如下标准:
- 实现了PropertyEditor接口,具有空构造器
- 与自定义类型同包(在同一个package内),名称必须为:targetType.getName() + "Editor"
这样你就无需再手动注册到注册中心了(当然手动注册了也不碍事),Spring能够自动发现它,这在有大量自定义类型编辑器的需要的时候将很有用。
说明:此段核心逻辑在BeanUtils#findEditorByConvention()里,有兴趣者可看看
值得注意的是:此机制属Spring遵循Java Bean规范而单独提供,在单独使用PropertyEditorRegistry时并未开启,而是在使用Spring产品级能力TypeConverter时有提供,这在后文将有体现,欢迎保持关注。
✍总结
本文在了解PropertyEditor基础支持之上,主要介绍了其注册中心PropertyEditorRegistry的使用。PropertyEditorRegistrySupport作为其“唯一”实现,负责管理PropertyEditor,包括通用处理和专用处理。最后介绍了PropertyEditor的自动发现机制,其实在实际生产中我并不建议使用自动机制,因为对于可能发生改变的因素,显示指定优于隐式约定。
关于Spring类型转换PropertyEditor相关内容就介绍到这了,虽然它很“古老”但并没有退出历史舞台,在排查问题,甚至日常扩展开发中还经常会碰到,因此强烈建议你掌握。下面起将介绍Spring类型转换的另外一个重点:新时代的类型转换服务ConversionService及其周边。