Spring原始碼分析之IoC(一)

JAVA線上發表於2019-02-26

什麼是IoC

控制反轉(Inversion of Control,縮寫為IoC),是一種設計模式,用來解耦元件之間的耦合度。

IoC容器系列的設計與實現

在Spring IoC容器的設計中,有兩種主要的容器系列:BeanFactory與ApplicationContext。
BeanFactory,IoC容器的介面定義,該系列容器提供的是最基本的IoC容器的功能。ApplicationContext,容器的高階形態,它通過繼承 MessageSource,ResourceLoader,ApplicationEventPublisher介面,在簡單容器的基礎上,增加了許多對高階容器的支援。

在Spring提供的最基本的IoC容器的介面定義和實現的基礎上,Spring通過定義BeanDefinition來管理基於Spring的應用中的各種物件以及他們直接的相互依賴關係。BeanDefinition 抽象了我們對 Bean的定義,是讓容器起作用的主要資料型別。對 IOC 容器來說,BeanDefinition 就是對依賴反轉模式中管理的物件依賴關係的資料抽象。也是容器實現依賴反轉功能的核心資料結構。

BeanFactory介面定義的方法:

Spring原始碼分析之IoC(一)

BeanFactory容器的設計原理

BeanFactory介面提供了使用IoC容器的規範,我們以XmlBeanFactory的實現為例來說明簡單IoC容器的設計原理。XmlBeanFactory類繼承關係圖:

Spring原始碼分析之IoC(一)
XmlBeanFactory繼承自DefaultListableBeanFactory類,DefaultListableBeanFactory包含了基本IoC容器所具有的重要功能,XmlBeanFactory在繼承了DefaultListableBeanFactory容器的功能的同時,增加了新的功能,它是一個可以讀取以XML檔案定義的BeanDefinition的IoC容器,來看下具體實現:

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }
}
複製程式碼

在XmlBeanFactory中,初始化了一個XmlBeanDefinitionReader物件,該物件用於對XML檔案定義資訊的處理。XmlBeanFactory建構函式需要傳入一個Resource引數,該引數封裝了BeanDefinition的來源資訊。在構造方法中,XmlBeanDefinitionReader會呼叫loadBeanDefinitions方法來完成BeanDefinition的載入。

ApplicationContext容器的設計原理

Spring原始碼分析之IoC(一)
我們以常用的FileSystemXmlApplicationContext為例,debug原始碼來看下ApplicationContext容器的設計原理。
測試程式碼如下:

@Test
public void testApplicationContext() {
    ApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/bean.xml");
    context.getBean("helloWorld",HelloWorld.class).sayHello();
}
複製程式碼

進入 FileSystemXmlApplicationContext 的構造方法:

public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);
}
複製程式碼

該方法呼叫過載方法,傳入三個引數:陣列型別的configLocation,預設屬性為true,父容器為null:

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}
複製程式碼

該方法首先會呼叫父類的構造方法,引數為null,在跟進去之前,我們先看下FileSystemXmlApplicationContext類的繼承結構:

Spring原始碼分析之IoC(一)
super(parent)方法會一直往上呼叫其父類構造方法,直到AbstractApplicationContext:

public AbstractApplicationContext(ApplicationContext parent) {
    this();
    setParent(parent);
}
複製程式碼

this方法呼叫預設建構函式,為屬性resourcePatternResolver賦值:

public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}
複製程式碼
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}
複製程式碼

回到setConfigLocations(configLocations)方法,看下該方法的具體實現:

public void setConfigLocations(String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}
複製程式碼

先畫出方法時序圖,跟著圖一步步跟下去:

Spring原始碼分析之IoC(一)

該方法會呼叫resolvePath方法,解析路徑,跟進去看下:

protected String resolvePath(String path) {
    return getEnvironment().resolveRequiredPlaceholders(path);
}
複製程式碼
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        this.environment = createEnvironment();
    }
    return this.environment;
}
複製程式碼
protected ConfigurableEnvironment createEnvironment() {
    return new StandardEnvironment();
}
複製程式碼

getEnvironment方法返回的是StandardEnvironment物件,該物件是標準環境,會自動註冊System.getProperties() 和 System.getenv()到環境。

接下來,回到resolvePath方法,該方法會呼叫AbstractEnvironment類的resolveRequiredPlaceholders方法:

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    return this.propertyResolver.resolveRequiredPlaceholders(text);
}
複製程式碼
private final ConfigurablePropertyResolver propertyResolver =
        new PropertySourcesPropertyResolver(this.propertySources);
複製程式碼

此時propertyResolver屬性已經有值了,緊接著會呼叫propertyResolver屬性的resolveRequiredPlaceholders方法,我們先看下ConfigurablePropertyResolver繼承結構:

Spring原始碼分析之IoC(一)
進入resolveRequiredPlaceholders方法看下,該方法在AbstractPropertyResolver中:

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}
複製程式碼

該方法做了兩件事:1.呼叫createPlaceholderHelper方法為屬性strictHelper賦值;2.呼叫doResolvePlaceholders方法,該方法是具體解析方法。

其中屬性strictHelper,型別為PropertyPlaceholderHelper,持有要解析的字首字尾,我們看下該類屬性

Spring原始碼分析之IoC(一)
繼續跟進,會呼叫doResolvePlaceholders方法:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
            return getPropertyAsRawString(placeholderName);
        }
    });
}
複製程式碼

具體不在深入,講了半天,我們來總結下setConfigLocations方法幹了什麼事情:解析FileSystemXmlApplicationContext建構函式中引數存在的佔位符,並替換為真實值。如:

@Test
public void testApplicationContext() {
    Properties properties = System.getProperties();
    properties.setProperty("aaaa", "bbbb");
    properties.setProperty("bbbb", "src/main/resources/bean.xml");
    ApplicationContext context = new FileSystemXmlApplicationContext("${${aaaa}}");
    context.getBean("helloWorld",HelloWorld.class).sayHello();
}
複製程式碼

setConfigLocations方法會將${${aaaa}},遞迴解析為bbbb,然後查詢對應的系統屬性存在aaaa為key的value bbbb,然後再根據bbbb查詢系統屬性,替換為applicationContext.xml。

如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號【Java線上】,查閱更多精彩歷史!!!

相關文章