spring-beans包源码阅读-2-BeanWrapper

欢迎光临我的个人博客:https://www.jelliclecat.cn/

一. BeanWrapper

/**
 * The central interface of Spring's low-level JavaBeans infrastructure.
 *
 * <p>Typically not used directly but rather implicitly via a
 * {@link org.springframework.beans.factory.BeanFactory} or a
 * {@link org.springframework.validation.DataBinder}.
 *
 * <p>Provides operations to analyze and manipulate standard JavaBeans:
 * the ability to get and set property values (individually or in bulk),
 * get property descriptors, and query the readability/writability of properties.
 *
 * <p>This interface supports <b>nested properties</b> enabling the setting
 * of properties on subproperties to an unlimited depth.
 *
 * <p>A BeanWrapper's default for the "extractOldValueForEditor" setting
 * is "false", to avoid side effects caused by getter method invocations.
 * Turn this to "true" to expose present property values to custom editors.
 *
 */
public interface BeanWrapper extends ConfigurablePropertyAccessor {
  ...
}

简单翻译一下:

Spring底层架构的核心接口。

主要用在BeanFactory中,而不是直接使用

提供了分析和操控标准JavaBean的操作:

get和set一个property的能力,获取一个property的descriptors,以及查询properties是否可以读写。

...

从第一句话就知道,这是一个非常重要的接口,被描述为"Spring底层架构的核心接口",可想而知其重要程度。这个接口做的事情也解释的很清楚了。BeanWrapper还继承和间接继承了很多其他的接口,之后一一解读。

这个接口有一个直接实现类:BeanWrapperImpl.java,我们直接去看这个类吧。

二. BeanWrapperImpl

还是先看注释:

/**
 * Default {@link BeanWrapper} implementation that should be sufficient
 * for all typical use cases. Caches introspection results for efficiency.
 * ...
 
 ...
 
 /**
  * Create a new BeanWrapperImpl for the given object.
  * @param object object wrapped by this BeanWrapper
  */
  public BeanWrapperImpl(Object object) {
    super(object);
  }

BeanWrapper的一个默认实现,可以满足所有的典型使用情形,缓存了自省结果。

一开头就告诉你了,这个类是一个完备的实现类,感觉spring得开发者已经在心中悄悄的给这个类标上了final,当然处于严谨没有这么做。而且构造函数也非常简单,就是传入一个实例(还有其他构造函数)。

既然这个类实现了绝大部分的功能,我们就仔细的看看这个类吧:

image

首先我们看继承结构。

顶级接口有三个:

  • PropertyAccessor
  • PropertyEditorRegistry
  • TypeConverter

这里简单解释一下,感兴趣的朋友可以自行研究。

1. PropertyAccessor

顾名思义,提供了对Bean的Property的set和get的方法,其实还有很丰富的方法,比如批量set和get属性,获取一个属性的读写权限信息,获取某个属性的类型或者类型描述(TypeDescriptor:Context about a type to convert from or to.)等方法。

2. PropertyEditorRegistry

修改一个property属性不是我们自己动手修改的,而是通过PropertyEditor接口,这个接口是java.beans包下的标准接口,这个接口用来修改一个bean的特定属性。java.beans包下提供了一批默认的PropertyEditor的实现,用来修改一些常见类型的属性,比如int,List等等,而PropertyEditorRegistry接口的作用,就是让我们可以注册自定义的PropertyEditor,让spring知道对于特定的类型的属性,去调用那个PropertyEditor进行具体的操作。

3. TypeConverter

对类型转换的支持,比如我设置一个属性,但是传入的类型和属性的类型不匹配,怎么办呢?那就进行类型转换,其实常见的类型转换有很多,比如String.valueOf就是讲一个其他类型的变量转换成String类型的方法。当然,可以预计有很多复杂的和自定义的不同类型之间的转换,那就是通过这个接口去实现。

到这里其实就已经能够知道BeanWrapper的主要作用了,那就是将一个Bean包起来,然后去set和get它的各种属性。

要注意的是,BeanWrapper对象的内部没有保存bean的属性的字段,最初我以为bean的属性会以一个map的形式存在BeanWrapper中,然后需要操作那个具体的Property就去根据这个Property的名字去get,其实不是的,所有的bean的信息在后来转换成了CachedIntrospectionResults对象:

public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {

  /**
   * Cached introspections results for this object, to prevent encountering
   * the cost of JavaBeans introspection every time.
   */
  @Nullable
  private CachedIntrospectionResults cachedIntrospectionResults;
  

这个对象里面保存了一个static Map,缓存了所有的Class自省的结果:

public final class CachedIntrospectionResults {
  
  ...
  /**
   * Map keyed by Class containing CachedIntrospectionResults, strongly held.
   * This variant is being used for cache-safe bean classes.
   */
  static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache =
    new ConcurrentHashMap<>(64);
  ...
}

每个CachedIntrospectionResults对象里面又有以下属性:

/** The BeanInfo object for the introspected bean class. */
  private final BeanInfo beanInfo;

  /** PropertyDescriptor objects keyed by property name String. */
  private final Map<String, PropertyDescriptor> propertyDescriptorCache;

  /** TypeDescriptor objects keyed by PropertyDescriptor. */
  private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;

这些属性保存了BeanWrapper包装Bean的BeanInfo、PropertyDescriptor、TypeDescriptor,这些信息就是Bean的自省结果。所有对Bean的属性的设置和获取最后都是通过CachedIntrospectionResults获取的。CachedIntrospectionResults在BeanWrapper中是懒加载的,在用户真正去调用Bean的相关信息的时候才去创建CachedIntrospectionResults。懒加载也是Spring的一贯作风。

三. PropertyEditorRegistrySupport

为了进一步理解BeanWrapper中有哪些东西,我们再看看上层的一些实现类,里面做了一些什么:

public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {

  @Nullable
  private ConversionService conversionService;

  private boolean defaultEditorsActive = false;

  private boolean configValueEditorsActive = false;

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

  @Nullable
  private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;

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

  @Nullable
  private Map<String, CustomEditorHolder> customEditorsForPath;

  @Nullable
  private Map<Class<?>, PropertyEditor> customEditorCache;

  ...
 
  /**
   * Actually register the default editors for this registry instance.
   */
  private void createDefaultEditors() {
    this.defaultEditors = new HashMap<>(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(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());

    // 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);
    }
  }
  
  ...
    
  @Override
    public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
        registerCustomEditor(requiredType, null, propertyEditor);
    }
  
  ...
    
}

首先简单粗暴的加载了各种默认的PropertiesEditor以及Spring自己实现的一些PropertiesEditor,然后最用户自定义的PropertiesEditor做了支持。所有的PropertiesEditor都保存在Map中,用的时候根据不同的Class去查询对应Class的PropertiesEditor。

四. TypeConverterSupport

使用了委托模式,将所有的TypeConvert请求都委托给了真正进行类型转换的类TypeConverterDelegate,而TypeConverterDelegate的转换请求最终又由一个ConversionService去实现,ConversionService也只是一个管理类,并没有做真正的类型转换操作,最最最终的类型转换,交给了负责各种类型互相转的Converter,这些Converter实现了GenericConverter接口,感兴趣的可以仔细阅读一下这部分代码,这部分代码位于spring-core模块下的org.springframework.core.convert包中,里面包含了大量spring已经编码好的Converter。

总之这里就是对不同类型的互相转换做支持。

五. AbstractNestablePropertyAccessor

最后讲一下AbstractNestablePropertyAccessor这个类,这个类完成了非常多的BeanWrapper功能,但是主要是对嵌套类的属性操作做支持。

AbstractNestablePropertyAccessor内部持有一个

private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;

理解这个是理解AbstractNestablePropertyAccessor的关键。

例如有以下的类型:

class bean {
  private A a;
  get() & set()
}

class A {
  private String name;
  get() & set()
}

那么,bean对应的AbstractNestablePropertyAccessor内部的nestedPropertyAccessors就有一个:

a -> AbstractNestablePropertyAccessor of class A 的map entry。

假设AbstractNestablePropertyAccessor of class A 的实例名称是nestablePropertyAccessorOfA,那么nestablePropertyAccessorOfA内部的nestedPropertyAccessors就有一个:

name -> AbstractNestablePropertyAccessor of class String 的map entry。

所以Bean中类型的嵌套和AbstractNestablePropertyAccessor中nestedPropertyAccessors的嵌套是一一对应的。

AbstractNestablePropertyAccessor有一个字段nestedPath,它表示对于一个嵌套属性的路径,比如在上例中,class A的name属性在class Bean中被表示为:a.name,那么如何拿到最name属性的PropertyAccessor呢?

/**
 * Recursively navigate to return a property accessor for the nested property path.
 * @param propertyPath property path, which may be nested
 * @return a property accessor for the target bean
 */
  @SuppressWarnings("unchecked")  // avoid nested generic
  protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
    int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
    // Handle nested properties recursively.
    if (pos > -1) {
      String nestedProperty = propertyPath.substring(0, pos);
      String nestedPath = propertyPath.substring(pos + 1);
      AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
      return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
    }
    else {
      return this;
    }
  }

就在这段代码中,这段代码递归的解析propertyPath,每层递归都返回外层嵌套的AbstractNestablePropertyAccessor,直到拿到最里面的属性,这里,当我们传入propertyPath = "a.name"时,先回拿到属性a对应的AbstractNestablePropertyAccessor,然后调用属性a的AbstractNestablePropertyAccessor去查找"name"属性,最终返回的是"name"属性对应的AbstractNestablePropertyAccessor。

AbstractNestablePropertyAccessor这个类理解起来稍复杂一些,关键是理解如果Bean的属性是其他Bean的情况下,如果去处理。

六. 简单的总结

BeanWrapper的初始化流程:

传入一个实例 -> 将registerDefaultEditors设置为true,表示要注册默认的PropertyEditors -> 然后调用setWrappedInstance:

// AbstractNestablePropertyAccessor.java
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
    this.wrappedObject = ObjectUtils.unwrapOptional(object);
    Assert.notNull(this.wrappedObject, "Target object must not be null");
    this.nestedPath = (nestedPath != null ? nestedPath : "");
    this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.wrappedObject);
    this.nestedPropertyAccessors = null;
    this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
  }

// BeanWrapperImpl.java
@Override
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
    super.setWrappedInstance(object, nestedPath, rootObject);
    setIntrospectionClass(getWrappedClass());
  }

    /**
    * Set the class to introspect.
    * Needs to be called when the target object changes.
    * @param clazz the class to introspect
    */
protected void setIntrospectionClass(Class<?> clazz) {
    if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
      this.cachedIntrospectionResults = null;
    }
  }

AbstractNestablePropertyAccessor中,设置了各种基本的bean的信息,并初始化了TypeConverterDelegate,BeanWrapperImpl中重写了父类AbstractNestablePropertyAccessor中的setWrappedInstance方法,并多了一个操作就是调用setIntrospectionClass。到这里初始化就完成了。

然后再调用convertForProperty、getLocalPropertyHandler、getPropertyDescriptors、getPropertyDescriptor这四个方法中的一个时,对包装的bean进行自省,创建CachedIntrospectionResults并缓存下来。

BeanWrapper主要封装了对一个Bean的属性的操作。

有不对的地方,欢迎指正~

欢迎光临我的个人博客:https://www.jelliclecat.cn/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容