spring vo是什么_Spring源码分析(十四)Spring中的BeanWrapper及类型转换

BeanWrapper是Spring中一个很重要的接口,Spring在通过配信息创建对象时,第一步首先就是创建一个BeanWrapper。这篇文章就来分析下这个接口。本文内容对应官网的3.3及3.1小节

接口定义

Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用BeanWrapper,而是借助BeanFactory或者DataBinder来一起使用,BeanWrapper对Spring中的Bean做了包装,为的是更加方便的操作Bean中的属性

0edcb3da8cc7ec762bedf7d00c8b08f6.png

这里需要解释一个概念,什么是属性描述符

PropertyDescriptor:属性描述符,能够描述JavaBean中的属性,通过属性描述符我们能知道这个属性的类型,获取操纵属性的方法(getter/setter)

继承关系

0a2f3c0839218411014beb1b971752e6.png

BeanWrapper的子类只有一个:BeanWrapperImpl,它继承了ConfigurablePropertyAccessor,这个接口的主要功能是进行属性访问,同时它又有三个父接口,接下来一一分析它们的功能。

接口功能

  1. PropertyEditorRegistry(属性编译器注册器)

接口定义

这个接口的功能很简单,就是用来注入属性编辑器(PropertyEditor),那么什么是PropertyEditor呢?

482130101b3bd9e504ef0bb460c3c537.png

PropertyEditor

概念

propertyEditor是JavaBean规范定义的接口,这是java.beans中的一个接口,其设计意图是在图形化编程上,方便对象与String之间的转换工作,而Spring将其扩展,方便各种对象与String之间的转换工作。

Spring中对PropertyEditor使用的实例

  • 我们在通过XML的方式对Spring中的bean进行配置时,不管bean中的属性是何种类型,都是直接通过字面值来设置bean中的属性,那么是什么在这其中做转换呢?这里用到的就是PropertyEditor。
  • SpringMVC在解析请求参数时,也是使用PropertyEditor。

Spring内置的PropertyEditor

d981c711d5f3d9a1bbea5b848b975fdb.png

2. PropertyAccessor

接口定义

1dca7137921497b23235c7e6f4a345ca.png

这里需要解释一个概念,什么是PropertyValue?

当设置属性值时,少不了两样东西

  • 属性访问表达式:如listMap[0][0]
  • 属性值:PropertyValue对象就是用来封装这些信息的。如果某个值要赋值给bean属性,Spring都会把这个值包装成PropertyValue对象

3. TypeConverter(类型转换器)

接口定义

定义了进行类型转换时的一些规范,就像名字定义的那样,主要用来做类型转换

877ddf07f202445915982afff55c80ae.png

4. ConfigurablePropertyAccessor

8fe68f8a5b1a8eb36b9ed3c7062e12b6.png

从上面可以看到,BeanWrapper接口自身对Bean进行了一层包装。另外它的几个通过间接继承了几个接口,所以它还能对bean中的属性进行操作。PropertyAccessor赋予了BeanWrapper对属性进行访问及设置的能力,在对bean中属性进行设置时,不可避免的需要对类型进行转换,而恰好PropertyEditorRegistry,TypeConverter就提高了类型转换的统一约束。

在了解接口之后,接下来看看它的唯一实现类BeanWrapperImpl

唯一子类(BeanWrapperImpl)

继承关系

33a82d1173218a3e863d8afa1067d0d9.png

结合之前对接口的分析以及上面这种UML类图,我们可以知道BeanWrapperImpl主要实现了以下几个功能:

  • 对bean进行包装
  • 对bean的属性进行访问以及设置
  • 在操作属性的过程中,比如涉及到类型转换,所以还有类型转换的功能

Java中的内置机制

在详细了解BeanWrapperImpl前,必须要了解Java中的一个机制:内省

核心概念

首先可以先了解下JavaBean的概念:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规范。如果两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为"值对象"(Value Object),或"VO"

因此JavaBean都有如下几个特征:

  1. 属性都是私有的
  2. 有无参的public构造方法
  3. 对私有属性根据需要提供公有的getXxx方法以及setXxx方法
  4. getters必须有返回值没有方法参数,setter没有返回值,有方法参数

符合这些特征的类,被称为JavaBean,JDK提供了一套API用来访问某个属性的getter/setter方法,这些API存放在java.beans中,这就是内省(Introspector)。

内省和反射的区别:

反射:java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法,它针对的是任意类。

内省:是java语言对JavaBean类属性,事件的处理方法

  1. 反射可以操作各种类的属性,而内省只是通过反射来操作JavaBean的属性
  2. 内省设置属性值肯定会调用setter方法,反射可以不用(反射可以直接操作属性Field)
  3. 反射就像镜子,然后能看到.class的所有,是客观的事实。内省更像主观的判断:比如看到getName(),内省就会认为这个类中有name字段,但事实上并不一定会有name。通过内省可以获取bean的getter/setter

使用示例

public class Test {
   public static void main(String[] args) throws Exception{
      BeanInfo beanInfo = Introspector.getBeanInfo(People.class);
      PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
      for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
         System.out.print(propertyDescriptor.getName()+"   ");
      }
   }
}


class People{
   String name;


   int age;


   public String getName() {
      return name;
   }


   public void setName(String name) {
      this.name = name;
   }


   public int getAge() {
      return age;
   }


   public void setAge(int age) {
      this.age = age;
   }
}
 

源码分析

这个类我只保留一些关键的代码,其余的琐碎代码都不看了

public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
   // 缓存内省的结果,BeanWrapperImpl就是通过这个对象来完成对包装的Bean的属性的控制
   @Nullable
   private CachedIntrospectionResults cachedIntrospectionResults;
   ......
   public void setBeanInstance(Object object) {
      this.wrappedObject = object;
      this.rootObject = object;
      // 实际进行类型转换的对象:typeConverterDelegate
      this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
      setIntrospectionClass(object.getClass());
   }
   ......
   // 最终调用的就是CachedIntrospectionResults的forClass方法进行内省并缓存,底层调用的就是java的内省机制
   private CachedIntrospectionResults getCachedIntrospectionResults() {
      if (this.cachedIntrospectionResults == null) {
         this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
      }
      return this.cachedIntrospectionResults;
   }
  .......
   // 最终进行类型转换的方法
   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");
      try {
         // 可以看到,最后就是调用typeConverterDelegate来进行类型转换
         return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
      }
      ......
   }
}

父类作用分析

对应接口,已经分析过了,下面重点看下BeanWrapperImpl继承的几个父类

PropertyEditorRegistrySupport

这个类最大的作用在于管理PropertyEditor,添加了很多默认的PropertyEditor。在PropertyEditorRegistryRegistry的基础上做了进一步的扩展,提供的还是属性编辑器注册的功能。

TypeConverterSupport

0b5e8cdb6a1074172bf154d3acb3039f.png

这个接口实现了TypeConverter,所以它具有类型转换的能力,而它这种能力的实现,依赖于它所持有的一个TypeConverterDelegate.

AbstractPropertyAccessor

public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
   // 省略部分代码......
   @Override
   public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
         throws BeansException {


      List<PropertyAccessException> propertyAccessExceptions = null;
      List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
            ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
      for (PropertyValue pv : propertyValues) {
         try {
            setPropertyValue(pv);
         }
         // ....
      }
   }


   @Override
   @Nullable
   public abstract Object getPropertyValue(String propertyName) throws BeansException;


   @Override
   public abstract void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
}

核心代码就是这些,这个类继承了TyConverterSupport,所以它也具备类型转换的能力。同时它也是一个属性访问器,但是它只是实现了批量设置属性的方法,真正的setPropertyValue还是留在子类实现。可以看到,到这个类为止,还没有将属性的设置和类型转换的能力结合起来。

AbstractNestablePropertyAccessor

这个类开始真正的将属性访问和类型转换结合到一起,它真正实现了setPropertyValue,并在设置属性的时候会进行类型的转换,具体的代码不看了,非常繁杂,但是整体不难。

上面多次提到了类型转换,但是还没有真正看到类型转换的逻辑,因为上面类最终将类型转换的逻辑委托给了TypeConverDelegate。接下来看看,类型转换到底是怎么完成的。

类型转换

TypeConverterDelegate

这个类只看一个核心方法,如下:

class TypeConverterDelegate {


   private final PropertyEditorRegistrySupport propertyEditorRegistry;


   @Nullable
   private final Object targetObject;


   public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
                           @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {


      // 查看是否为当前这个类型配置了定制的PropertyEditor
      PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);


      ConversionFailedException conversionAttemptEx = null;


      // 获取当前容器中的类型转换业务类
      ConversionService conversionService = this.propertyEditorRegistry.getConversionService();


      // 在这里可以看出,Spring底层在进行类型转换时有两套机制
      // 1.首选的是采用PropertyEditor
      // 2.在没有配置PropertyEditor的情况下,会采用conversionService
      if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
         TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
         if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
            try {
               // 通过conversionService进行类型转换
               return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
            }
            catch (ConversionFailedException ex) {
               // fallback to default conversion logic below
               conversionAttemptEx = ex;
            }
         }
      }


      Object convertedValue = newValue;


      // 配置了定制的属性编辑器,采用PropertyEditor进行属性转换
      if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
         if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
               convertedValue instanceof String) {
            TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
            if (elementTypeDesc != null) {
               Class<?> elementType = elementTypeDesc.getType();
               if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                  convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
               }
            }
         }
         if (editor == null) {
            // 没有配置定制的属性编辑器,采用默认的属性编辑器
            editor = findDefaultEditor(requiredType);
         }
         // 采用属性编辑器进行转换,需要注意的是,默认情况下PropertyEditor只会对String类型的值进行类型转换
         convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
      }
      // .....
      return (T) convertedValue;
}

从上面的代码可以知道,Spring在实现类型转换时,有两套机制,第一套机制依赖于PropertyEditor,第二套机制依赖于ConversionService。关于属性编辑器PropertyEditor之前已经介绍过了,主要进行的是Spring到Object的转换,正因为如此,属性编辑器进行类型转换有很大的局限性,所以Spring又推出一套ConversionService的体系。

ConversionService体系

  1. Converter

接口定义

9e96f650a69c24f98008e277c9f48143.png

这个接口只能进行一对一转换,S->T

2. ConverFactory

接口定义

f3dcd9ce8a97a82cd70acd04ed4cbe83.png

利用这个转换工厂,我们可以进行一对多的转换,以Spring内置的一个转换器为例:

99dce05884aff4301b1480c1ab2d030b.png

通过传入不同的枚举类型,我们可以从这个工厂中获取到不同的转换器,并把对应的String来下参数转换成对应的枚举类型数据。

可以看到,通过ConverterFactory,能实现一对多的类型转换S->(T extends R)

3. GenericConverter

接口定义

1d35582f0ebcd69c4f83a408734fb8d2.png

相比于前面的Converter以及ConverterFactory,这个接口更加牛逼了,使用它能完成多对多的转换,因为它内部保存了一个能够进行转换的ConvertiblePair的集合,每个ConvertiblePair代表一组能进行转换的数据类型。同时,这个接口相比我们前面介绍的两个接口,更加复杂,所以一般情况下也不推荐使用这个接口,没有非常必要的话,最好是使用上两种。

一般GenericConverter会与ConditionalGenericConverter配合使用,其接口定义如下:

05bd3e6cbdc2d118de405160f2c4dccf.png

704926a8dedb38e025473aa37b92b60f.png

我们来看下Spring内部的一个实际使用的例子:

5f5c03061200a54fb593b13b86b7a47d.png

4. ConversionService

接口定义

8dfbad43075c802232c8a4efc0255fad.png

UML类图

853072281f27544ffe0be923be9acf0b.png

一般来说,实现了ConversionService和ConverterRegistry会结合使用,对于这种xxxRegistry我相信大家猜都能猜出来它是干嘛的。代码如下:

a75e38d80bc0950da5c9f3d1c431d292.png

ConfigurableConversionService

02327fe2bffc3660fc37d5e0e4d10221.png

GenericConversionService

这个类是一个具体的实现类,可以直接使用,但是我们一般不会直接使用它,而是使用它的子类DefaultConversionService,因为子类提供了很多默认的转换器。

DefaultConversionService

6483825ff56894353997ff1beaedb19c.png

相比其父类GenericConversionService,这个子类默认添加了很多转换器,这样可以极大的方便我们开发,所以一般情况下都会使用这个类。

如何配置ConversionService

写了这么多,那么如何往容器中配置一个ConversionService呢?我们需要借助Spring提供的一个ConversionServiceFactoryBean。其代码如下:

f090666f909fb23b87012b97d2c8fac8.png

这个类的实现逻辑很简单,ConversionServiceFactoryBean创建完成后,在进行初始化调用afterPropertiesSet方法,创建一个DefaultConversionService,然后将提供的converters全部注册到这个DefaultConversionService,所以我们进行如下的配置就行了:

总结

这篇文章,写了BeanWrapper,知道一个BeanWrapper其实就是一个Bean的包装器,它对bean包装的目的是为了能操纵bean中的属性,所以它同时需要具备获取以及设置bean中的属性能力,所以它也必须是一个属性访问器(PropertyAccessor),另外为了将不同类型的配置数据绑定到bean的属性上,那么它还得具有属性转换的能力,因为它还得是一个类型转换器(TypeCpnverter)。

通过上面的分析,我们知道Spring中将类型转换的功能都委托给了一个TypeConverterDelegate,这个委托类在进行类型转换时会有两套方案:

  1. PropertyEditor,这是Spring最初提供的方案,扩展了java中的PropertyEditor(Java原先提供这个接口的目的更多是为了进行图形化编程)
  2. ConversionService,Spring后来提供的一个进行类型转换的体系,用来取代PropertyEditor,因为PropertyEditor有很大的局限性,只能进行String->Object的转换

画图如下:

4f2de4acd421e2293bd2c03b6d8e3b34.png

不吃竹子的滚滚:Spring源码分析(十三)ApplicationContext详解(下)

不吃竹子的滚滚:Spring源码分析(十二)ApplicationContext详解(中)

不吃竹子的滚滚:Spring源码分析(十一)ApplicationContext详细介绍(上)

不吃竹子的滚滚:Spring源码分析(十)Spring中Bean的生命周期(下)

不吃竹子的滚滚:Spring源码分析(九)Spring中Bean的生命周期(上)

不吃竹子的滚滚:Spring源码分析(八)容器的扩展点(BeanPostProcessor)

不吃竹子的滚滚:Spring源码分析(七)容器的扩展点(FactoryBean)

不吃竹子的滚滚:Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)

不吃竹子的滚滚:Spring源码分析(五)BeanDefinition(下)

不吃竹子的滚滚:Spring源码分析(四)BeanDefinition(上)

不吃竹子的滚滚:Spring源码分析(三)自动注入与精确注入

不吃竹子的滚滚:Spring源码分析(二)依赖注入及方法注入

不吃竹子的滚滚:Spring源码分析(一)Spring容器及Spring Bean

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值