天天看點

Spring源碼解析一:IOC容器設計

一、IOC接口設計

IOC容器設計的源碼主要在spring-beans.jar、spring-context.jar這兩個包中。IOC容器主要接口設計如下:

Spring源碼解析一:IOC容器設計

這裡的接口設計有兩條主線:BeanFactory和ApplicationContext

1、BeanFactory-->HierarchicalBeanFactory-->ConfigurableBeanFactory:這是BeanFactory的設計路線,BeanFactory定義了基本的IOC容器規範,HierarchicalBeanFactory中增加了getParentBeanFactory方法,具備了雙親IOC容器的管理功能;ConfigurableBeanFactory中新增一些配置功能。

2、ApplicationContext應用上下文接口:繼承了HierarchicalBeanFactory、ListableBeanFactory等BeanFactory的子接口,這條分支使得ApplicationContext具備了IOC容器的基本功能;在繼承MessageSource、ApplicationEventPublisher等接口的時候,使得ApplicationContext這個簡單的IOC容器添加了許多進階容器的特性。ApplicationContext的子接口有ConfigurableApplicationContext以及在WEB環境下使用的WebApplicationContext。

二、BeanFactory的設計原理

public abstract interface BeanFactory
{
  public static final String FACTORY_BEAN_PREFIX = "&";
  
  public abstract Object getBean(String paramString)
    throws BeansException;

  public abstract <T> T getBean(String paramString, Class<T> paramClass)
    throws BeansException;

  public abstract <T> T getBean(Class<T> paramClass)
    throws BeansException;

  public abstract Object getBean(String paramString, Object[] paramArrayOfObject)
    throws BeansException;

  public abstract boolean containsBean(String paramString);

  public abstract boolean isSingleton(String paramString)
    throws NoSuchBeanDefinitionException;

  public abstract boolean isPrototype(String paramString)
    throws NoSuchBeanDefinitionException;

  public abstract boolean isTypeMatch(String paramString, Class<?> paramClass)
    throws NoSuchBeanDefinitionException;

  public abstract Class<?> getType(String paramString)
    throws NoSuchBeanDefinitionException;

  public abstract String[] getAliases(String paramString);
}
      

 BeanFactory隻是定義了IOC容器的基本輪廓,并沒有給出容器的具體實作(這個後面詳細介紹)。

先來讨論下BeanFactory和FactoryBean之間的差別

1、前者很好了解,就是Spring的一個類工廠,用它可以建立各種類型的Bean,最主要的方法就是getBean(String paramString)。而建立的各種類型的Bean中有一種比較特殊的Bean就是FactoryBean。

2、Spring容器中管理裡兩種Bean,一種是标準的Java Bean,從容器中擷取的是類本身的執行個體;另外一種就是FactoryBean即工廠Bean,從容器中擷取Bean的時候,傳回的并不是類的一個執行個體,而是工廠Bean中getObject方法傳回的對象。工廠Bean必須實作接口FactoryBean。

工廠Bean---->SayHelloFactoryBeanImpl

public class SayHelloFactoryBeanImpl implements FactoryBean
{

    public Object getObject()
        throws Exception
    {
        return new UserBean();
    }

    public Class getObjectType()
    {
        return UserBean.class;
    }

    public boolean isSingleton()
    {
        return false;
    }
}      

工廠Bean傳回的對象:UserBean

public class UserBean
{
    public void show()
    {
        System.out.println("春天來了");
    }
}      

Spring配置檔案:

<bean id="sayHelloBean" class="SayHelloFactoryBeanImpl"></bean>      

測試類:

ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            Object bean = ctx.getBean("sayHelloBean");
            Object bean1 = ctx.getBean("&sayHelloBean");
            System.out.println(bean);
            System.out.println(bean1);                      

執行結果:

UserBean@7225790e
SayHelloFactoryBeanImpl@54a097cc      

從正常的情況下,從容器中擷取ID為“sayHelloBean”的對象應該是SayHelloFactoryBeanImpl。但由于SayHelloFactoryBeanImpl實作了接口FactoryBean,這是一個工廠Bean,是以通過ID擷取到的Bean是SayHelloFactoryBeanImpl類中getObject方法傳回的對象。如果要擷取FactoryBean自身的一個執行個體,必須通過&+BeanID的形式去擷取。

網上有許多對工廠Bean總結歸納,如:工廠Bean是實作了FactoryBean接口的bean  它不是一個簡單的Bean 而是一個生産或修飾對象生成的工廠Bean。這裡我先Mark一下:為什麼Spring要設計這種類型的Bean。

三、XmlBeanFactory的解讀

Spring源碼解析一:IOC容器設計

XmlBeanFactory是IOC容器系列最底層的實作,它繼承自DefaultListableBeanFactory這個類。而後者是Spring中非常重要的一個類,它是Spring容器中一個基本産品,可以把它當做一個預設的功能完整的IOC容器來使用。

XmlBeanFactory除了從DefaultListableBeanFactory繼承到IOC容器基本功能之外,還新增了一些其他功能,從名稱就可以猜測出來,它是一個可以讀取以XML檔案方式定義BeanDefinition的容器。

XmlBeanFactory源碼如下:

1 public class XmlBeanFactory extends DefaultListableBeanFactory
 2 {
 3   private final XmlBeanDefinitionReader reader;
 4 
 5   public XmlBeanFactory(Resource resource)
 6     throws BeansException
 7   {
 8     this(resource, null);
 9   }
10 
11   public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)
12     throws BeansException
13   {
14     super(parentBeanFactory);
15 
16     this.reader = new XmlBeanDefinitionReader(this);
17 
18     this.reader.loadBeanDefinitions(resource);
19   }
20 }      

實際上,實作XML讀取功能并不是直接由XmlBeanFactory來完成的。而是由XmlBeanFactory内部定義的XmlBeanDefinitionReader來進行處理的。在構造XmlBeanFactory容器的時候,需要給出BeanDefinition的資訊來源,而這個資訊來源需要封裝成Spring中的Resource類的形式給出。

來看下一個基本的IOC容器的初始化過程:

1、建立IOC配置檔案的抽象資源,也就是源碼中的Resource,這個Resource中包含了BeanDefinition的定義資訊。

2、通過構造函數建立一個BeanFactory。

3、建立一個載入BeanDefinition的讀取器,即源碼中的reader。這裡使用XmlBeanDefinitionReader來載入XML檔案形式的BeanDefinition。

4、調用reader的loadBeanDefinitions方法,來完成從Resource中載入BeanDefinition資訊,進而完成IOC容器的初始化。

我們可以将上面的源碼做簡化,使用程式設計式的方式來表達IOC容器的初始化:

Spring源碼解析一:IOC容器設計

總結:DefaultListableBeanFactory是IOC容器的一個基類,XmlBeanFactory是在其基礎上擴充而來的。而其他的IOC容器,例如ApplicationContext,它的實作原理和XmlBeanFactory類似,也是通過擴充DefaultListableBeanFactory來擷取基本的IOC容器功能的。

四、ApplicationContext的設計原理

接口設計圖:

Spring源碼解析一:IOC容器設計

1、ApplicationContext繼承接口ListableBeanFactory、HierarchicalBeanFactory,實作了IOC容器的基本功能。

2、繼承接口MessageSource:支援不同資訊源,支援國際化的實作。

3、繼承接口ResourceLoader:支援該容器可以從不同I/O途徑得到Bean的定義資訊。

4、繼承接口ApplicationEventPublisher:在上下文中引入了事件機制。這些事件機制和Bean的生命周期結合為Bean的管理提供了便利。

ApplicationContext增加了這些附加功能,使得基本IOC容器的功能更加豐富,是以建議在開發應用的時候使用ApplicationContext作為IOC容器的基本形式。

ApplicationContext的設計原理

以子類FileSystemXmlApplicationContext的實作為例說明其設計原理。接口設計圖如下:

Spring源碼解析一:IOC容器設計

這個接口設計中,ApplicationContext應用上下文的主要功能已經在FileSystemXmlApplicationContext的基類AbstractXmlApplicationContext中實作了,而FileSystemXmlApplicationContext作為一個具體的IOC容器,隻需要實作和其本身相關的功能即可。

源碼片段:

1   public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
 2     throws BeansException
 3   {
 4     super(parent);
 5     setConfigLocations(configLocations);
 6     if (refresh)
 7       refresh();
 8   }
 9 
10   protected Resource getResourceByPath(String path)
11   {
12     if ((path != null) && (path.startsWith("/"))) {
13       path = path.substring(1);
14     }
15     return new FileSystemResource(path);
16   }
17 }      

這裡面有兩個主要的方法:refresh()、getResourceByPath(String path)

1、refresh涉及到IOC容器啟動的一系列操作,由于這個啟動過程對于不同類型的容器來說都是相似的,是以這個啟動過程被封裝在基類中,具體的容器隻需要調用即可。refresh方法後面會有詳細介紹。

2、getResourceByPath這個方法是跟FileSystemXmlApplicationContext差別于其他具體容器的功能。通過這個方法可以讓容器在檔案系統中讀取以XML形式存在的BeanDefinition。

繼續閱讀