3. 搞定收工,PropertyEditor就到这(下)

简介: 3. 搞定收工,PropertyEditor就到这(下)

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)匹配的时候也是按照优先级顺序执行匹配的:


  1. 若指定了propertyPath(不为null),就先去customEditorsForPath里找。否则就去customEditors里找
  2. 若没有指定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内部也有很多类似实现模式:


image.png


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是为了实现自定义类型 <-> 字符串的自动转换,它一般需要有如下步骤:


  1. 为自定义类型写好一个xxxPropertyEditor(实现PropertyEditor接口)
  2. 将写好的编辑器注册到注册中心PropertyEditorRegistry


显然步骤1属个性化行为无法替代,但步骤2属于标准行为,重复劳动是可以标准化的。自动发现机制就是用来解决此问题,对自定义的编辑器制定了如下标准:


  1. 实现了PropertyEditor接口,具有空构造器
  2. 与自定义类型同包(在同一个package内),名称必须为:targetType.getName() + "Editor"


这样你就无需再手动注册到注册中心了(当然手动注册了也不碍事),Spring能够自动发现它,这在有大量自定义类型编辑器的需要的时候将很有用。


说明:此段核心逻辑在BeanUtils#findEditorByConvention()里,有兴趣者可看看


值得注意的是:此机制属Spring遵循Java Bean规范而单独提供,在单独使用PropertyEditorRegistry时并未开启,而是在使用Spring产品级能力TypeConverter时有提供,这在后文将有体现,欢迎保持关注。


✍总结


本文在了解PropertyEditor基础支持之上,主要介绍了其注册中心PropertyEditorRegistry的使用。PropertyEditorRegistrySupport作为其“唯一”实现,负责管理PropertyEditor,包括通用处理和专用处理。最后介绍了PropertyEditor的自动发现机制,其实在实际生产中我并不建议使用自动机制,因为对于可能发生改变的因素,显示指定优于隐式约定。


关于Spring类型转换PropertyEditor相关内容就介绍到这了,虽然它很“古老”但并没有退出历史舞台,在排查问题,甚至日常扩展开发中还经常会碰到,因此强烈建议你掌握。下面起将介绍Spring类型转换的另外一个重点:新时代的类型转换服务ConversionService及其周边。

相关文章
|
5月前
|
Java Spring 容器
Spring注入
Spring注入
52 13
怎么看注解对应的处理类?
怎么看注解对应的处理类?
64 0
|
5月前
|
XML Java 数据格式
怎么通过类型来获取 bean以及为什么可以通过构造器配置 bean
怎么通过类型来获取 bean以及为什么可以通过构造器配置 bean
53 0
|
11月前
获取类中所有的bean
获取类中所有的bean
40 0
|
Java
十 枚举类&注解
十 枚举类&注解
48 0
|
设计模式 缓存 Java
Java反射(反射与代理设计模式、反射与Annotation、自定义Annotation、反射整合工厂设计模式和代理设计模式)
1.反射与代理设计模式,2.反射与Annotation,3.自定义Annotation,4.Annotation整合工厂设计模式和代理设计模式
64 0
|
Java 容器 Spring
七.Spring源码剖析-Bean的实例化-属性注入
喜欢我的文章的话就给个好评吧,你的肯定是我坚持写作最大的动力,来吧兄弟们,给我一点动力 这一章节我们来讨论创建Bean过程中的属性注入,在Spring的IOC容器启动过程中,会把定义的Bean封装成BeanDefinition注册到一个ConcurrentHashMap中,Bean注册完成后,就会对单利的且lazy-init=false 的Bean进行实例化。创建Bean的代码在 AbstractAutowireCapableBeanFactory#doCreateBean 中,当Bean创建成功之后,会调用AbstractAutowireCapableBeanFactory#populat
SpringAOP导致@Autowired依赖注入失败
SpringAOP导致@Autowired依赖注入失败
188 1
|
存储 XML Java
Spring源码(一)-Bean的定义-BeanDefinition
在 Spring 容器中,我们广泛使用的是一个一个的 Bean,那在Spring 中,我们可以如何去定义一个Bean?
903 1
|
缓存 Java API
3. 搞定收工,PropertyEditor就到这(上)
3. 搞定收工,PropertyEditor就到这(上)
3. 搞定收工,PropertyEditor就到这(上)