深入分析Spring中的類型轉(zhuǎn)換與校驗(1)

前言

在平時的開發(fā)中,對于Spring為我們提供的數(shù)據(jù)類型轉(zhuǎn)換校驗功能似乎已經(jīng)使用的習(xí)以為常,但是對于其是如何在Spring框架背后運行的以及它自身的體系結(jié)構(gòu),一直以來我都沒有一個特別清晰的認(rèn)識,因此花了幾天時間將Spring文檔再次精讀了一遍,同時對部分源碼做了分析,故在此將自己的學(xué)習(xí)成果進(jìn)行一個總結(jié)。原本想一篇文章搞定的,發(fā)現(xiàn)如果想細(xì)致分析根本一篇文章搞不定,所以我在這里將其拆分成3文章,本系列文章所分析的內(nèi)容主要包括以下幾個方面:

  • PropertyEditor類型轉(zhuǎn)換體系以及源碼分析其背后的工作流程
  • Type Conversion、Formatter SPI轉(zhuǎn)換體系的結(jié)構(gòu)以及核心類的源碼分析
  • Spring的數(shù)據(jù)校驗的工作機制以及源碼分析校驗的執(zhí)行流程

本篇文章首先對ProeprtyEditor轉(zhuǎn)換體系以及源碼做一個分析,如果分析有不對之處,還望指正錯誤?。?!

Spring類型轉(zhuǎn)換體系

Spring的類型轉(zhuǎn)換系統(tǒng)在經(jīng)歷了版本升級變更之后,也隨之發(fā)生了很大的變化,從3.0前的PropertyEditor體系轉(zhuǎn)變到了3.0后的ConverterFormatter轉(zhuǎn)換體系。這里會先從早期的PropertyEditor開始分析,然后再分析3.0后的類型轉(zhuǎn)換體系;分析ProperyEditor更有助于我們對Spring內(nèi)部實現(xiàn)機制的理解,然后通過不同的類型轉(zhuǎn)換系統(tǒng)進(jìn)行一個對比,理解版本升級所帶來的意義與好處。

1. PropertyEditor類型轉(zhuǎn)換體系分析

首先讓我們對傳統(tǒng)的PropertyEditor類型轉(zhuǎn)換體系做一個復(fù)習(xí)與分析,來看看在一個看似不起眼的數(shù)據(jù)轉(zhuǎn)換操作在其背后到底發(fā)生了什么。

1.1 PropertyEditor

通過java.beans.PropertyEditor其包名我們就可以看出,其本身并非Spring中定義的接口,它其實是Java Bean規(guī)范中所定義的一個接口,其設(shè)計初衷在于是用于完成GUI中的輸入與對象屬性之間的轉(zhuǎn)換操作;Spring只是借用了PropertyEditor這個接口的定義與功能,來完成字符串與對象屬性之間的轉(zhuǎn)換。在Spring 3.0之前,在Spring整個體系中,所有完成字符串與其他數(shù)據(jù)類型轉(zhuǎn)換操作都是由ProperyEditor接口來完成的。
Spring通過PropertyEditor作為其數(shù)據(jù)類型轉(zhuǎn)換的基礎(chǔ)架構(gòu),同時自己還定義了許多默認(rèn)的ProrpertyEditor的實現(xiàn),這些實現(xiàn)類都位于spring-beans這個模塊中的propertyeditors包中。

Spring內(nèi)置的PropertyEditor的實現(xiàn).jpg

這些內(nèi)置的PropertyEditor會有部分在默認(rèn)情況下就已經(jīng)被加了到IOC容器中,而有些PropertyEditor在默認(rèn)情況下并沒有自動加入,需要用戶手動進(jìn)行配置,后面我們通過源碼可以看到Spring默認(rèn)所注冊了哪些PropertyEditor

1.2 PropertyEditorSupport

由于PropertyEditor是一個類型轉(zhuǎn)換的接口,其里面定義了很多與我們實際使用上無關(guān)的方法。如果我們想要使用PropertyEditor的話,我們通常只需要繼承java.beans.PropertyEditorSupport這個類,并且重寫其setAsText(String source)方法即可,通過它將輸入的字符串轉(zhuǎn)換成我們期望的數(shù)據(jù)類型。

Spring中的PorperyEdidtor.jpg

通過上圖可以看到,Spring中的所提供的內(nèi)置的PropertyEditor也都是繼承PropertyEditorSupport來完成類型轉(zhuǎn)換的。

1.3 PropertyEditor的基本使用

下面我們通過一個簡單的例子來復(fù)習(xí)一下PropertyEditor的基本使用。這里為了減少篇幅,我就不給出需要的maven依賴,之后如果需要參考的話可以直接到GitHub下載源碼。

package com.panlingxiao.spring.validation.domain;
import lombok.Data;
/**
 * Created by panlingxiao on 2016/5/29.
 * 這里使用lombok來取代getter和setter方法。
 */
@Data
public class Circle {
    private Point point;
}
package com.panlingxiao.spring.validation.domain;
import lombok.Data;

@Data
public class Point {
    int x, y;

}

上面給出兩個非常簡單的類,我們希望完成的是輸入1;2,能夠自動進(jìn)行分割,然后轉(zhuǎn)換成point的x和y屬性。
下面我們自己定義PropertyEditor來完成數(shù)據(jù)的轉(zhuǎn)換:

package com.panlingxiao.spring.validation.editor;
import com.panlingxiao.spring.validation.domain.Point;

import java.beans.PropertyEditorSupport;

/**
 * 自定義PropertyEditor,完成String到Point的轉(zhuǎn)換
 */
public class PointEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] splits = text.split(";");
        Point point = new Point();
        point.setX(Integer.parseInt(splits[0]));
        point.setY(Integer.parseInt(splits[1]));
        /*
         *需要將裝換后的結(jié)果設(shè)置到Editor的value屬性中,因為外部會通過getValue獲取到轉(zhuǎn)換的結(jié)果。
         */
        setValue(point);
    }
}

在完成ProperyEditor編寫完成后,我們只需將其注冊到IOC容器中就可以自動完成StringPoint之間的轉(zhuǎn)換,下面來編寫spring的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
   <!-- 通過CustomEditorConfigurer這個BeanFactoryProcessor來完成自定義的ProperyEditor到IOC容器的添加功能 -->
    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <bean class="com.panlingxiao.spring.validation.editor.MyPropertyEditorRegistrar"/>
        </property>
    </bean>

    <bean class="com.panlingxiao.spring.validation.domain.Circle" id="circle">
        <property name="point" value="1;2"/>
    </bean>

</beans>

在編寫完成配置文件之后,我們編寫一個測試類來驗證一下配置的結(jié)果是否有效:

package com.panlingxiao.spring.propertyeditor;

import com.panlingxiao.spring.validation.domain.Circle;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by panlingxiao on 2016/6/1.
 */
public class TestPropertyEditor {

    @Test
    public void testCustomEditorConfigurer() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("prop-editor-app-context.xml");
        Circle circle = applicationContext.getBean("circle", Circle.class);
        System.out.println(circle.getPoint());
    }
}
PropertyEditor測試運行結(jié)果.jpg

除了通過CustomEditorConfigurer來完成自定義PropertyEditor的注入,Spring也支持另外一種基于Java Bean規(guī)范的自動查找機制。下面我們也通過一個例子來驗證一下這種方式:

package com.panlingxiao.spring.validation.domain;

import lombok.Data;

/**
 * Created by panlingxiao on 2016/5/29.
 */
@Data
public class Boo {
    private Foo foo;
}
package com.panlingxiao.spring.validation.domain;

import lombok.Data;

/**
 * Created by panlingxiao on 2016/5/29.
 */
@Data
public class Foo {
    private int x,y;
}

上面給出兩個類也同樣非常簡單,我們也希望完成的是輸入1;2,能夠自動進(jìn)行分割,然后轉(zhuǎn)換成一個Foo對象的x和y屬性即可,下面我們再看定義自己的PropertyEditor:

package com.panlingxiao.spring.validation.domain;

import java.beans.PropertyEditorSupport;

/**
 * Created by panlingxiao on 2016/5/29.
 */
public class FooEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] splits = text.split(";");
        Foo foo = new Foo();
        foo.setX(Integer.parseInt(splits[0]));
        foo.setY(Integer.parseInt(splits[1]));
        setValue(foo);
    }
}

注意到:自定義的PropertyEditor必須與對應(yīng)的類型在同一個包下,并且其名字必須為xxxEditor,這樣才能實現(xiàn)自動查詢的功能

PropertyEditor自動查詢機制.png

下面,我們通過一個測試類來簡單的驗證一下該功能是否真的有效,首先編寫一個XML配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

       <bean class="com.panlingxiao.spring.validation.domain.Boo"  id="boo">
              <property name="foo" value="1;2"/>
       </bean>
</beans>
    /**
     * 通過約定機制來自動查詢PropertyEditor完成類型轉(zhuǎn)換
     */
    @Test
    public void testPropertyEditorByConvention(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("prop-convention-app-context.xml");
        Boo boo = ctx.getBean("boo", Boo.class);
        System.out.println("foo.x: "+boo.getFoo().getX()+",foo.y: "+boo.getFoo().getY());
    }
PropertyEditor自動查詢機制運行結(jié)果.png

通過上面的運行結(jié)果我們可以看到,我們自己所定義的ProeprtyEditor確實已經(jīng)被Spring的IOC容器所管理,并且成功地完成了類型的轉(zhuǎn)換操作。但是Spring容器在背后具體做了什么,對于我們而言卻完全一無所知,下面我們將繼續(xù)深入學(xué)習(xí)一下,并且從源碼的角度仔細(xì)分析一下Spring如何對PropertyEditor進(jìn)行管理的,如何將字符串轉(zhuǎn)換成我們期望的類型。

1.4 深入分析PropertyEditor類型轉(zhuǎn)換體系以及源碼分析

PropertyEditor類型轉(zhuǎn)換體系牽涉到的類比較多,下面我們對其涉及到接口與類逐一進(jìn)行分析,在了解了核心接口與類之后,再去看Spring中的PropertyEditor類型轉(zhuǎn)換體系就會清晰很多。

首先,我們應(yīng)該所思考的問題是:Spring中的內(nèi)置的PropertEditor與我們定義的ProeprtyEditor是被來完成管理的呢?是BeanFactory還是ApplicationContext呢?這樣的問題都不算非常準(zhǔn)確,真正完成管理功能應(yīng)該是PropertyEditorRegistry,下面我們對這個接口做一個比較細(xì)致的分析。

PropertyEditorRegistry

org.springframework.beans.PropertyEditorRegistry接口提供了對PropertyEditor注冊以及查找功能,因此其主要提供是提供了對PropertyEditor的管理功能,首先來看看這個接口的描述:

PropertyEditorRegistry接口描述.png

下面是該接口中所定義的方法:

/**
  根據(jù)屬性的類型來指定其對應(yīng)的PropertyEditor
*/
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);

/**
*  根據(jù)屬性的類型以及屬性的名字來指定其對應(yīng)的PropertyEditor,該方法并  
*  不常用,但是可以做到更細(xì)粒度的轉(zhuǎn)換操作
*/
void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);

/**
 根據(jù)指定的類型以及屬性的名字,查詢其對應(yīng)的ProeprtyEditor,屬性的名字可以為null
*/
PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath);

通過上面的描述,我們應(yīng)該可以明白PropertyEditorRegistry這個接口的作用,下面我們來看看其具體的幾個實現(xiàn)類。

PropertyEditorRegistrySupport

由于PropertyEditorRegistry只是定義對PropertyEditor注冊和查找的方法,其具體的核心實現(xiàn)類是org.springframework.beans.PropertyEditorRegistrySupport,真正對PropertyEditor管理的操作全部在該類中實現(xiàn),下面來看看PropertyEditorRegistrySupport的源碼,由于PropertyEditorRegistrySupport源碼篇幅比較多,這里就采用截圖來說明其實現(xiàn):

PropertyEditorSupport分析.jpg

通過上面的標(biāo)注我們看到PropertyEditorSupport底層對于不同種類的PropertyEditor使用不同的Map來進(jìn)行存儲,下面我們看下它是如何進(jìn)行注冊的。

PropertyEditorSupport的注冊過程.jpg

其注冊的實現(xiàn)機制也并沒有出人意料的地方,就是判斷存儲ClasssPropertyEditor之間映射關(guān)系的Map是否已經(jīng)存在,如果不存在則先創(chuàng)建一個LinkedHashMap,如果有就直接進(jìn)行存儲映射關(guān)系。
前面我們提到過在IOC容器中默認(rèn)就會內(nèi)置一些PropertyEditor,通過createDefaultEditors()我們可以清楚地看到其默認(rèn)所添加的PropertyEditor

IOC中默認(rèn)所內(nèi)置的PropertyEditor.jpg

上面就是PropertyEditorRegistrySupport最核心的實現(xiàn),下面繼續(xù)來看另外一個重要的接口。

PropertyEditorRegistrar

初看PropertyEditorRegistrar時,可能會因為與PropertyEditorRegistry在名字上的相似性而混淆,下面我們看下PropertyEditorRegistrar到底是干嘛的。

PropertyRegistrar的接口說明.png

從接口的描述上我們可以看到,PropertyEditorRegistrar的作用是將用戶自定義的PropertyEditor注冊到PropertyEditorRegistry中。通過其registerCustomEditor方法中的參數(shù)我們可以看到,其所接受的正是一個PropertyEditorRegistry,通過方法的參數(shù)將用戶自定義的ProepertyEditor加入到PropertyEditorRegistry被其進(jìn)行管理。
PropertyEditorRegistrar對于如果我們希望將一組相同的PropertyEditor應(yīng)用在多個地方時是非常有用的 ( 比如希望將相同的一組PropertyEditor既應(yīng)用在IOC容器中,同時又應(yīng)用在Spring MVC的DataBinder中),此時就可以先定義一個PropertyEditorRegistrar的實現(xiàn)類,來完成通用的ProepertyEditor注冊操作,然后將PropertyEditorRegistrar作為一個ProeprtyEditor的集合設(shè)置到不同的地方,此時就可以做到代碼復(fù)用。
PS:相同的PropertyEditor需要在多處進(jìn)行注冊的原因是因為我們在IOC容器中通過CustomEditorConfigurer添加了自定義的PropertyEditor后,其并不會對SpringMVC中所使用的DataBinder而生效,因此需要再次進(jìn)行注冊,我們通過分析CustomEditorConfigurer可以在其注釋說明中清楚地看到這點說明。

使用PropertyRegistartr進(jìn)行配置的原因.png

下面我們來看一個PropertyEditorRegistrar的例子,我們還是使用上面的例子作為基礎(chǔ),首先定義一個PropertyEditorRegistrar來完成PropertyEditor的注冊功能:

package com.panlingxiao.spring.validation.editor;

import com.panlingxiao.spring.validation.domain.Point;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;

/**
 * Created by panlingxiao on 2016/6/2.
 */
public class MyPropertyEditorRegistrar implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        //將自己所定義的PropertyEditor注冊到PropertyEditorRegistry中
        registry.registerCustomEditor(Point.class,"point",new PointEditor());
    }
}

我們只需修改一下配置文件,現(xiàn)在要注入的不再是一個Map,而是一個PropertyEditorRegistrar。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
        通過CustomEditorConfigurer這個BeanFactoryProcessor來完成自定義的ProperyEditor到IOC容器的添加功能
    -->
    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <bean class="com.panlingxiao.spring.validation.editor.MyPropertyEditorRegistrar"/>
        </property>

    <bean class="com.panlingxiao.spring.validation.domain.Circle" id="circle">
        <property name="point" value="1;2"/>
    </bean>

</beans>
    /**
     * 測試基于{@link org.springframework.beans.PropertyEditorRegistrar}的PropertyEditor的注冊
     */
    @Test
    public void testPropertyEditorByPropertyRegistrar(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("prop-registrar-app-context.xml");
        Circle circle = ctx.getBean("circle", Circle.class);
        Assert.assertEquals(1,circle.getPoint().getX());
        Assert.assertEquals(2,circle.getPoint().getY());
    }

運行結(jié)果:

PropertyEditorRegistart的注冊運行結(jié)果.png

通過上面的結(jié)果我們看到了PropertyRegistrar的作用以及基本用法,下面我們再介紹一個比較底層接口,由于該接口和源碼息息相關(guān),因此不得不在此做出說明,它就是BeanWrapper接口:

BeanWrapper

BeanWrapper是Spring中一個比較底層的接口,在通常情況下,我們作為普通用戶是不會涉及到這個接口的使用的,其主要被Spring的內(nèi)部所使用,但是由于我們下面會設(shè)計到IOC部分的源碼分析,故在此也對其做一個說明,對于查看后面的源碼會更有幫助。


BeanWrapper接口說明.png

從上面的描述我們可以了解到BeanWrapper接口的基本作用。BeanWrapper除了提供了標(biāo)準(zhǔn)的JavaBean的訪問方式以外,同時還繼承了PropertyEditorRegistry,因此BeanWrapper也同樣具有對ProeprtyEditor的管理功能,下面是BeanWrapper的繼承結(jié)構(gòu)圖:

BeanWrapper繼承結(jié)構(gòu)圖.png

Spring底層其實在創(chuàng)建一個Bean時,IOC容器首先將用戶自定義的PropertyEditor注冊到BeanWrapper中,然后通過BeanWrapper通過注入的PropertyEditor完成數(shù)據(jù)類型的轉(zhuǎn)換,再將轉(zhuǎn)換后的結(jié)果通過反射注入到Bean的屬性中,該結(jié)論可以在調(diào)式后續(xù)的源碼中得以驗證。

下面我們先通過一個簡單的例子來簡單說明一下BeanWrapper的使用:

package com.panlingxiao.spring.validation.domain;

import com.panlingxiao.spring.validation.annotation.MyDate;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.Id;
import java.util.Date;
import java.util.List;

/**
 * Created by panlingxiao on 2016/5/26.
 */
@Data
public class Person {
    @Id
    private Integer id;
    private  String name;
    private int age;
    private Date birthday;
    private List<Integer> nums;
    private Boo boo;
    private Point point; 
}
   /**
     * 使用BeanWrapperImpl設(shè)置基本屬性
     */
    @Test
    public void testSetBasicPropertyValue(){
        Person person = new Person();
        BeanWrapperImpl wrapper = new BeanWrapperImpl(person);
        wrapper.setPropertyValue("id", "1");
        Assert.assertEquals(1, person.getId().intValue());

        //設(shè)置BeanWrapper自動創(chuàng)建內(nèi)聯(lián)屬性,否則會引發(fā)NullValueInNestedPathException
        wrapper.setAutoGrowNestedPaths(true);
        wrapper.setPropertyValue("nums[0]", "123");
        wrapper.setPropertyValue("nums[1]", "456");
        System.out.println(person.getNums());

        wrapper.setPropertyValue("boo.foo.x", "1");
        System.out.println(person.getBoo().getFoo().getX());
          
        //注冊自定義的PropertyEditor到BeanWrapper中
        wrapper.registerCustomEditor(Point.class,new PointEditor());
        
       //BeanWrapper通過注冊的PropertyEditor完成數(shù)據(jù)類型的轉(zhuǎn)換
        wrapper.setPropertyValue("point","1;2");        
    }

運行結(jié)果:

BeanWrapper測試運行結(jié)果.png

至此,對于PropertyEditor體系中牽涉到幾個重要概念就介紹完了,下面我們需要具體來看看我們自己定義的PropertyEditor是如何被添加到IOC容器中,又是如何被IOC添加到BeanWrapper中的,最終BeanWrapper又是如何完成數(shù)據(jù)類型的轉(zhuǎn)換的。

1.首先我們看看CustomEditorConfigurer是如何自動被IOC容器所加載然后調(diào)用的。

CustomEditorConfigurer實現(xiàn)了BeanFactoryProcessor.png

我們可以看到CustomEditorConfigurer實現(xiàn)了BeanFactoryPostProcessor接口,BeanFactoryPostProcessor是Spring提供給用戶用于動態(tài)修改application context信息的接口,對于application context它會自動去查詢所有的BeanFactoryPostProcessor,并且調(diào)用其postProcessBeanFactory方法來完成處理,需要注意的是,該方法的調(diào)用是在所有單例Bean初始化前調(diào)用的。

其具體代碼體現(xiàn)為:


ApplicationContext完成BeanFactoryPostProcessor初始化過程.png

通過上面的代碼可以看到,AbstractApplicationContextrefresh()方法會自動完成BeanFactoryPostProcessor的查找和調(diào)用,下面我們具體看看它的具體處理流程,由于invokeBeanFactoryPostProcessors中的方法代碼過多,這里我只截取核心的處理代碼:

ApplicationContext自動查找BeanFactoryPostProcessor.png
ApplicationContext自動查找BeanFactoryPostProcessor2.png
BeanFactoryPostProcessor在BeanFactory中的執(zhí)行.png

可以看到,它會自動去BeanFactory根據(jù)類型獲取到BeanFactoryPostProcessor的名字,然后根據(jù)名字查詢到具體的實例,最后調(diào)用其定義postProcessBeanFactory完成用戶自己的處理。
通過上面的分析,我們應(yīng)該能夠清楚地看到一個BeanFactoryPostProcesor是如何被ApplicationContext所處理的,而我們所配置的CustomEditorConfigurer正是一個BeanFactoryPostProcesor。

2.下面我們來看看CustomEditorConfigurerpostProcessBeanFactory具體實現(xiàn),分析我們所定義的PropertyEditor是如何被IOC容器所使用:

    @SuppressWarnings("unchecked")
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
         /*
          * 將用戶所定義的PropertyEditorRegistrar注入到BeanFactory中,
          * BeanFactory底層會通過BeanWrapper來創(chuàng)建Bean,此時BeanFactory就會將其所管理的PropertyEditor傳遞給
          * BeanWrapper底層又維護(hù)著一個TypeConverterDelegate,通過TypeConverterDelegate來真正完成真正的類型轉(zhuǎn)換
          * 再將轉(zhuǎn)換后結(jié)果通過BeanWrapper使用反射注入到屬性中。
          */
        if (this.propertyEditorRegistrars != null) {
            for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
                beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
            }
        }
            //迭代存儲Class與PropertyEditor映射關(guān)系的map
        if (this.customEditors != null) {
            for (Map.Entry<String, ?> entry : this.customEditors.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                Class requiredType = null;

                try {
                    //通過反射獲取具體要轉(zhuǎn)換的Java類型
                    requiredType = ClassUtils.forName(key, this.beanClassLoader);
                    //判斷value的具體類型,可能是一個PropertyEditor、也可能是一個Class、也可能是一個String
                    //如果設(shè)置的是一個具體的PropertyEditor,那么該被PropertyEditor會被共享使用,每次使用都會被加鎖來處理,因此建議不要
                    //將PropertyEditor設(shè)置為共享,從而影響性能               
                    if (value instanceof PropertyEditor) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("Passing PropertyEditor instances into CustomEditorConfigurer is deprecated: " +
                                    "use PropertyEditorRegistrars or PropertyEditor class names instead. " +
                                    "Offending key [" + key + "; offending editor instance: " + value);
                        }
                        beanFactory.addPropertyEditorRegistrar(
                                new SharedPropertyEditorRegistrar(requiredType, (PropertyEditor) value));
                    }
                     //當(dāng)設(shè)置的是一個Class,則直接存儲轉(zhuǎn)換類型與PropertyEditor的類型                       
                    else if (value instanceof Class) {
                        beanFactory.registerCustomEditor(requiredType, (Class) value);
                    }
                     //當(dāng)設(shè)置的是一個String,則先反射出PropertyEditor,再存儲轉(zhuǎn)換類型與PropertyEditor的類型         
                    else if (value instanceof String) {
                        Class editorClass = ClassUtils.forName((String) value, this.beanClassLoader);
                        Assert.isAssignable(PropertyEditor.class, editorClass);
                        beanFactory.registerCustomEditor(requiredType, editorClass);
                    }
                    else {
                        throw new IllegalArgumentException("Mapped value [" + value + "] for custom editor key [" +
                                key + "] is not of required type [" + PropertyEditor.class.getName() +
                                "] or a corresponding Class or String value indicating a PropertyEditor implementation");
                    }
                }
                catch (ClassNotFoundException ex) {
                    if (this.ignoreUnresolvableEditors) {
                        logger.info("Skipping editor [" + value + "] for required type [" + key + "]: " +
                                (requiredType != null ? "editor" : "required type") + " class not found.");
                    }
                    else {
                        throw new FatalBeanException(
                                (requiredType != null ? "Editor" : "Required type") + " class not found", ex);
                    }
                }
            }
        }
    }

3.看完自定義的PropertyEditor如何被加入到BeanFactory中之后,下面我們繼續(xù)看BeanFactory是在何時創(chuàng)建一個我們所定義的Bean,并且在創(chuàng)建Bean后如何通過BeanWrapperPropertyEditor完成類型轉(zhuǎn)換。

ApplicationContext完成SingleTonBean的創(chuàng)建.png
ApplicationContext完成SingleTonBean的創(chuàng)建2.png

這里為了減少篇幅,我直接給出代碼的截圖,通過上面的的方法名我們可以看到,ApplicationContext會預(yù)先完成單例的Bean的初始化操作,下面我們看看它是如何預(yù)初始化單例的Bean的,由于這里的代碼比較深,我們直接跳到我們所最關(guān)注的部分,即IOC如何使用BeanWrapper部分。

Bean的初始化過程.png

通過上圖我們看到了一個initBeanWrapper方法,該方法就是IOC容器將用戶所配置的PropertyEditor注冊到BeanWrapper的過程,下面我們繼續(xù)查看該方法具體做了什么。


IOC注冊PropertyEditor到BeanWrapper過程.png
IOC注冊ProeprtyEditor的具體實現(xiàn).png
IOC注冊ProeprtyEditor的具體實現(xiàn)2.png

看到BeanFactory將PropertyEditor往BeanWrapper注冊之后,下面就應(yīng)該完成Bean的屬性的注入操作。

屬性注入的過程.png

populate方法是完成Bean的屬性注入操作,通過上面的截圖看到,在未調(diào)用該方法之前,此時對象的屬性還是null,下面是其如何完成數(shù)據(jù)類型的轉(zhuǎn)換過程:

類型轉(zhuǎn)換的具體實現(xiàn).png
類型轉(zhuǎn)換過程2.png

在通過TypeConverterDelegate完成屬性類型轉(zhuǎn)換后,最后再通過BeanWrapper將屬性注入到對象中。

屬性注入代碼.png

至此,PropertyEditor的體系結(jié)構(gòu)以及其如何在IOC容器中的過程已經(jīng)分析完了。這里需要說明的一下,由于Spring的源碼過多,我曾嘗試想在博客中進(jìn)行逐行分析,但是發(fā)現(xiàn)根本不可能,所以上面的分析也只能以截圖的形式給出,對于具體的細(xì)節(jié)如果想更進(jìn)一步深入,就需要自己再進(jìn)一步看源碼和調(diào)式。說了這么多關(guān)于PropertyEditor的事,我們最后所要思考的是PropertyEditor其所存在的缺點。

PropertyEditor的缺點分析

1.只能完成字符串到Java類型的轉(zhuǎn)換,并不能完成任意類型之間的轉(zhuǎn)換。
2.由于PropertyEditor是非線程安全,因此對于每一次的類型轉(zhuǎn)換,都需要創(chuàng)建一個新的PropertyEdtitor,如果希望達(dá)到共享,那么底層會使用synchronized來對其進(jìn)行并發(fā)地控制。

PropertyEditor線程安全問題.png

由于PropertyEditor存在著這些問題,因此Spring3.0后推出了新的類型轉(zhuǎn)換系統(tǒng),下一節(jié)將繼續(xù)對Conversion Service的體系結(jié)構(gòu)以及部分源碼進(jìn)行分析。

PS:用心寫作真心不易,如果您覺得本文對您有幫助,請點個贊,這是對我最大的鼓勵,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,692評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評論 6 342
  • 十里桃花 /青葵 三月。自你離開后的醒來 青袍玉簫,杳如黃鶴 我卸下濃妝,除去艷抹 折取青杯,捻不破一顆紅豆 我不...
    石上青葵閱讀 431評論 10 7
  • 寫在開頭,本人不是專業(yè)作家寫手,格式之類的也不會特別重視,文章純屬個人情感抒發(fā)或是經(jīng)驗積累,如若給看官您造成任何負(fù)...
    吃貨小耳貓閱讀 410評論 0 0
  • 初始化項目 這里我們使用vue-cli來自動生成vue.js項目的模板。 安裝Node.js 用npm安裝vue-...
    danejahn閱讀 1,043評論 2 52

友情鏈接更多精彩內(nèi)容