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底層架構(gòu)的核心接口。

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

提供了分析和操控標(biāo)準(zhǔn)JavaBean的操作:

get和set一個property的能力聘萨,獲取一個property的descriptors华弓,以及查詢properties是否可以讀寫族跛。

...

從第一句話就知道鞍爱,這是一個非常重要的接口初肉,被描述為"Spring底層架構(gòu)的核心接口",可想而知其重要程度荆责。這個接口做的事情也解釋的很清楚了滥比。BeanWrapper還繼承和間接繼承了很多其他的接口,之后一一解讀草巡。

這個接口有一個直接實現(xiàn)類: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的一個默認(rèn)實現(xiàn)山憨,可以滿足所有的典型使用情形查乒,緩存了自省結(jié)果。

一開頭就告訴你了郁竟,這個類是一個完備的實現(xiàn)類玛迄,感覺spring得開發(fā)者已經(jīng)在心中悄悄的給這個類標(biāo)上了final,當(dāng)然處于嚴(yán)謹(jǐn)沒有這么做棚亩。而且構(gòu)造函數(shù)也非常簡單蓖议,就是傳入一個實例(還有其他構(gòu)造函數(shù))。

既然這個類實現(xiàn)了絕大部分的功能讥蟆,我們就仔細(xì)的看看這個類吧:

image

首先我們看繼承結(jié)構(gòu)勒虾。

頂級接口有三個:

  • PropertyAccessor
  • PropertyEditorRegistry
  • TypeConverter

這里簡單解釋一下,感興趣的朋友可以自行研究瘸彤。

1. PropertyAccessor

顧名思義修然,提供了對Bean的Property的set和get的方法,其實還有很豐富的方法质况,比如批量set和get屬性愕宋,獲取一個屬性的讀寫權(quán)限信息,獲取某個屬性的類型或者類型描述(TypeDescriptor:Context about a type to convert from or to.)等方法结榄。

2. PropertyEditorRegistry

修改一個property屬性不是我們自己動手修改的中贝,而是通過PropertyEditor接口,這個接口是java.beans包下的標(biāo)準(zhǔn)接口臼朗,這個接口用來修改一個bean的特定屬性邻寿。java.beans包下提供了一批默認(rèn)的PropertyEditor的實現(xiàn),用來修改一些常見類型的屬性依溯,比如int老厌,List等等,而PropertyEditorRegistry接口的作用黎炉,就是讓我們可以注冊自定義的PropertyEditor枝秤,讓spring知道對于特定的類型的屬性,去調(diào)用那個PropertyEditor進(jìn)行具體的操作慷嗜。

3. TypeConverter

對類型轉(zhuǎn)換的支持淀弹,比如我設(shè)置一個屬性丹壕,但是傳入的類型和屬性的類型不匹配,怎么辦呢薇溃?那就進(jìn)行類型轉(zhuǎn)換菌赖,其實常見的類型轉(zhuǎn)換有很多,比如String.valueOf就是講一個其他類型的變量轉(zhuǎn)換成String類型的方法沐序。當(dāng)然琉用,可以預(yù)計有很多復(fù)雜的和自定義的不同類型之間的轉(zhuǎn)換,那就是通過這個接口去實現(xiàn)策幼。

到這里其實就已經(jīng)能夠知道BeanWrapper的主要作用了邑时,那就是將一個Bean包起來,然后去set和get它的各種屬性特姐。

要注意的是晶丘,BeanWrapper對象的內(nèi)部沒有保存bean的屬性的字段,最初我以為bean的屬性會以一個map的形式存在BeanWrapper中唐含,然后需要操作那個具體的Property就去根據(jù)這個Property的名字去get浅浮,其實不是的,所有的bean的信息在后來轉(zhuǎn)換成了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自省的結(jié)果:

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的自省結(jié)果叔遂。所有對Bean的屬性的設(shè)置和獲取最后都是通過CachedIntrospectionResults獲取的。CachedIntrospectionResults在BeanWrapper中是懶加載的争剿,在用戶真正去調(diào)用Bean的相關(guān)信息的時候才去創(chuàng)建CachedIntrospectionResults。懶加載也是Spring的一貫作風(fēng)痊末。

三. PropertyEditorRegistrySupport

為了進(jìn)一步理解BeanWrapper中有哪些東西蚕苇,我們再看看上層的一些實現(xiàn)類,里面做了一些什么:

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

首先簡單粗暴的加載了各種默認(rèn)的PropertiesEditor以及Spring自己實現(xiàn)的一些PropertiesEditor凿叠,然后最用戶自定義的PropertiesEditor做了支持涩笤。所有的PropertiesEditor都保存在Map中,用的時候根據(jù)不同的Class去查詢對應(yīng)Class的PropertiesEditor盒件。

四. TypeConverterSupport

使用了委托模式蹬碧,將所有的TypeConvert請求都委托給了真正進(jìn)行類型轉(zhuǎn)換的類TypeConverterDelegate,而TypeConverterDelegate的轉(zhuǎn)換請求最終又由一個ConversionService去實現(xiàn)炒刁,ConversionService也只是一個管理類恩沽,并沒有做真正的類型轉(zhuǎn)換操作,最最最終的類型轉(zhuǎn)換翔始,交給了負(fù)責(zé)各種類型互相轉(zhuǎn)的Converter罗心,這些Converter實現(xiàn)了GenericConverter接口里伯,感興趣的可以仔細(xì)閱讀一下這部分代碼,這部分代碼位于spring-core模塊下的org.springframework.core.convert包中渤闷,里面包含了大量spring已經(jīng)編碼好的Converter疾瓮。

總之這里就是對不同類型的互相轉(zhuǎn)換做支持。

五. AbstractNestablePropertyAccessor

最后講一下AbstractNestablePropertyAccessor這個類飒箭,這個類完成了非常多的BeanWrapper功能狼电,但是主要是對嵌套類的屬性操作做支持。

AbstractNestablePropertyAccessor內(nèi)部持有一個

private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;

理解這個是理解AbstractNestablePropertyAccessor的關(guān)鍵弦蹂。

例如有以下的類型:

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

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

那么肩碟,bean對應(yīng)的AbstractNestablePropertyAccessor內(nèi)部的nestedPropertyAccessors就有一個:

a -> AbstractNestablePropertyAccessor of class A 的map entry。

假設(shè)AbstractNestablePropertyAccessor of class A 的實例名稱是nestablePropertyAccessorOfA盈匾,那么nestablePropertyAccessorOfA內(nèi)部的nestedPropertyAccessors就有一個:

name -> AbstractNestablePropertyAccessor of class String 的map entry腾务。

所以Bean中類型的嵌套和AbstractNestablePropertyAccessor中nestedPropertyAccessors的嵌套是一一對應(yīng)的。

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密末,直到拿到最里面的屬性,這里跛璧,當(dāng)我們傳入propertyPath = "a.name"時严里,先回拿到屬性a對應(yīng)的AbstractNestablePropertyAccessor,然后調(diào)用屬性a的AbstractNestablePropertyAccessor去查找"name"屬性追城,最終返回的是"name"屬性對應(yīng)的AbstractNestablePropertyAccessor刹碾。

AbstractNestablePropertyAccessor這個類理解起來稍復(fù)雜一些,關(guān)鍵是理解如果Bean的屬性是其他Bean的情況下座柱,如果去處理迷帜。

六. 簡單的總結(jié)

BeanWrapper的初始化流程:

傳入一個實例 -> 將registerDefaultEditors設(shè)置為true,表示要注冊默認(rèn)的PropertyEditors -> 然后調(diào)用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中色洞,設(shè)置了各種基本的bean的信息戏锹,并初始化了TypeConverterDelegate,BeanWrapperImpl中重寫了父類AbstractNestablePropertyAccessor中的setWrappedInstance方法火诸,并多了一個操作就是調(diào)用setIntrospectionClass锦针。到這里初始化就完成了。

然后再調(diào)用convertForProperty、getLocalPropertyHandler伞插、getPropertyDescriptors割粮、getPropertyDescriptor這四個方法中的一個時,對包裝的bean進(jìn)行自省媚污,創(chuàng)建CachedIntrospectionResults并緩存下來舀瓢。

BeanWrapper主要封裝了對一個Bean的屬性的操作。

有不對的地方耗美,歡迎指正~

歡迎光臨我的個人博客:https://www.jelliclecat.cn/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末京髓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子商架,更是在濱河造成了極大的恐慌堰怨,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛇摸,死亡現(xiàn)場離奇詭異备图,居然都是意外死亡,警方通過查閱死者的電腦和手機赶袄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門揽涮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饿肺,你說我怎么就攤上這事蒋困。” “怎么了敬辣?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵雪标,是天一觀的道長。 經(jīng)常有香客問我溉跃,道長村刨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任撰茎,我火速辦了婚禮烹困,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乾吻。我一直安慰自己,他們只是感情好拟蜻,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布绎签。 她就那樣靜靜地躺著,像睡著了一般酝锅。 火紅的嫁衣襯著肌膚如雪诡必。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音爸舒,去河邊找鬼蟋字。 笑死,一個胖子當(dāng)著我的面吹牛扭勉,可吹牛的內(nèi)容都是我干的鹊奖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涂炎,長吁一口氣:“原來是場噩夢啊……” “哼忠聚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唱捣,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤两蟀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后震缭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赂毯,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年拣宰,在試婚紗的時候發(fā)現(xiàn)自己被綠了党涕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡徐裸,死狀恐怖遣鼓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情重贺,我是刑警寧澤骑祟,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站气笙,受9級特大地震影響次企,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜潜圃,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一缸棵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谭期,春花似錦堵第、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胀瞪,卻和暖如春针余,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工圆雁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忍级,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓伪朽,卻偏偏與公主長得像轴咱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子驱负,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容