spring源碼解析之IOC容器(一)

  學習優秀框架的源碼,是提高我的技術水平必不可少的一個環節。若是隻是停留在知道怎麼用,可是不懂其中的前因後果,在技術的道路上註定走不長遠。最近,學習了一段時間的spring源碼,如今整理出來,以便往後溫故知新。spring

  IOC容器是spring最核心的模塊之一,是整個spring體系的基石,spring其餘模塊中,都須要用到IOC容器的功能。spring框架爲咱們提供了多種IOC容器,DefaultableBeanFact框架

ory、FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext等。雖然咱們平時不多在項目中使用這種硬編碼的方式來獲取IOC容器,繼而獲取IOC容器中的bean,可是研究這些IOC容器的源碼,對咱們理解IOC容器的原理仍是頗有必要的。BeanFactory這個接口是spring全部IOC容器最上層的接口,getBean()這個方法就是在這個接口中定義的,下面是其中定義的方法:ide

 1 public interface BeanFactory {
 2     Object getBean(String name) throws BeansException;
 3     <T> T getBean(String name, Class<T> requiredType) throws BeansException;
 4     Object getBean(String name, Object... args) throws BeansException;
 5     <T> T getBean(Class<T> requiredType) throws BeansException;
 6     <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
 7     boolean containsBean(String name);
 8     boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
 9     boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
10     boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
11     boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
12     Class<?> getType(String name) throws NoSuchBeanDefinitionException;
13     String[] getAliases(String name);
14 }

  能夠看到其中定義了獲取bean的多種方式,和各類對bean的判斷,以及獲取bean的類型和別名的方法。這個接口是spring框架IOC容器的入口。下面以FileSystemXmlApplicatiopost

nContext爲例,深刻源碼探究IOC容器的實現原理。IOC容器的初始化過程分爲三個階段:定位、載入和註冊。接下來一一進行分析,先從XML的定位開始。學習

  相信咱們你們都使用如下代碼獲取過IOC容器,獲取IOC容器以後,咱們就能夠獲得想要的bean,而後進行操做了:ui

1 FileSystemXmlApplicationContext  context = new FileSystemXmlApplicationContext("bean.xml");

  進入FileSystemXmlApplicationContext這個類,發現它定義了各類構造器,但最終都會調用下面這個構造器:this

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     }

  在分析它的流程以前,有必要給一下它的UML圖,上面標註了它的繼承體系結構:編碼

  FileSystemXmlApplicationContext的構造器中有個重要的方法refresh(),這是IOC容器的啓動方法,在它的父類AbstractXmlApplicationContext中有實現,其代碼以下:url

 1 @Override
 2     public void refresh() throws BeansException, IllegalStateException {
 3         synchronized (this.startupShutdownMonitor) {
 4             // Prepare this context for refreshing.
 5             //準備要進行刷新的上下文對象
 6             //例如對系統環境進行準備和驗證
 7             prepareRefresh();
 8 
 9             // Tell the subclass to refresh the internal bean factory.
10             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
11 
12             // Prepare the bean factory for use in this context.
13             prepareBeanFactory(beanFactory);
14 
15             try {
16                 // Allows post-processing of the bean factory in context subclasses.
17                 postProcessBeanFactory(beanFactory);
18 
19                 // Invoke factory processors registered as beans in the context.
20                 invokeBeanFactoryPostProcessors(beanFactory);
21 
22                 // Register bean processors that intercept bean creation.
23                 registerBeanPostProcessors(beanFactory);
24 
25                 // Initialize message source for this context.
26                 initMessageSource();
27 
28                 // Initialize event multicaster for this context.
29                 initApplicationEventMulticaster();
30 
31                 // Initialize other special beans in specific context subclasses.
32                 onRefresh();
33 
34                 // Check for listener beans and register them.
35                 registerListeners();
36 
37                 // Instantiate all remaining (non-lazy-init) singletons.
38                 finishBeanFactoryInitialization(beanFactory);
39 
40                 // Last step: publish corresponding event.
41                 finishRefresh();
42             }
43 
44             catch (BeansException ex) {
45                 if (logger.isWarnEnabled()) {
46                     logger.warn("Exception encountered during context initialization - " +
47                             "cancelling refresh attempt: " + ex);
48                 }
49 
50                 // Destroy already created singletons to avoid dangling resources.
51                 destroyBeans();
52 
53                 // Reset 'active' flag.
54                 cancelRefresh(ex);
55 
56                 // Propagate exception to caller.
57                 throw ex;
58             }
59 
60             finally {
61                 // Reset common introspection caches in Spring's core, since we
62                 // might not ever need metadata for singleton beans anymore...
63                 resetCommonCaches();
64             }
65         }
66     }

  進入obtainFreshBeanFactory()方法,其代碼以下:spa

1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
2         refreshBeanFactory();
3         ConfigurableListableBeanFactory beanFactory = getBeanFactory();
4         if (logger.isDebugEnabled()) {
5             logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
6         }
7         return beanFactory;
8     }

  繼續跟,進入refreshBeanFactory()方法,在父類AbstractRefreshableApplicationContext中有實現,其代碼以下:

 1 @Override
 2     protected final void refreshBeanFactory() throws BeansException {
 3         if (hasBeanFactory()) {
 4             destroyBeans();
 5             closeBeanFactory();
 6         }
 7         try {
 8             //建立DefaultListableBeanFactory
 9             DefaultListableBeanFactory beanFactory = createBeanFactory();
10             //指定序列化的id,因此,若是須要反序列化這個BeanFactory,則能夠直接根據這個id來進行反序列化
11             beanFactory.setSerializationId(getId());
12             //定製化
13             customizeBeanFactory(beanFactory);
14             //初始化DocumentReader,讀取XML
15             loadBeanDefinitions(beanFactory);
16             synchronized (this.beanFactoryMonitor) {
17                 this.beanFactory = beanFactory;
18             }
19         }
20         catch (IOException ex) {
21             throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
22         }
23     }

  這段代碼能夠看到:

  一、首先,建立了一個DefaultListableBeanFactory的IOC容器;

  二、對容器進行了一些設置;

  三、調用loadBeanDefinitions()方法對XML文件進行定位和加載。

  因此,進入loadBeanDefinitions()方法繼續探索,在類AbstractXmlApplicationContext中有實現,它是FileSystemXmlApplicationContext的父類,其代碼以下:

 1 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
 2         // Create a new XmlBeanDefinitionReader for the given BeanFactory.
 3         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
 4 
 5         // Configure the bean definition reader with this context's
 6         // resource loading environment.
 7         beanDefinitionReader.setEnvironment(this.getEnvironment());
 8         beanDefinitionReader.setResourceLoader(this);
 9         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
10 
11         // Allow a subclass to provide custom initialization of the reader,
12         // then proceed with actually loading the bean definitions.
13         initBeanDefinitionReader(beanDefinitionReader);
14         loadBeanDefinitions(beanDefinitionReader);
15     }

  這個方法中,使用XmlBeanDefinitionReader類來加載XML文件,最後通過一系列的設置,調用了loadBeanDefinitions(beanDefinitionReader)這個方法,進入:

 1 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
 2         Resource[] configResources = getConfigResources();
 3         if (configResources != null) {
 4             reader.loadBeanDefinitions(configResources);
 5         }
 6         String[] configLocations = getConfigLocations();
 7         if (configLocations != null) {
 8             reader.loadBeanDefinitions(configLocations);
 9         }
10     }

  跟到這裏,究竟是走哪一個方法呢?咱們再回過頭看一下,FileSystemXmlApplicationContext的那個構造器,其中有個setConfigLocations(configLocations)方法,經過這個方法將咱們配置的XML文件的路徑設置進來了,跟代碼,發現它調用的是父類的方法,並將路徑賦給了AbstractRefreshableConfigApplicationContext類中的configLocations成員變量,而getConfigLocations()方法也是AbstractRefreshableConfigApplicationContext類中的,它正好獲取了configLocations的值,因此configLocations必定不爲null,上面方法應該走下面的loadBeanDefinitions()方法。跟進,其代碼以下:

1 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
2         Assert.notNull(locations, "Location array must not be null");
3         int counter = 0;
4         for (String location : locations) {
5             counter += loadBeanDefinitions(location);
6         }
7         return counter;
8     }
1 public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
2         return loadBeanDefinitions(location, null);
3     }
 1 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
 2         ResourceLoader resourceLoader = getResourceLoader();
 3         if (resourceLoader == null) {
 4             throw new BeanDefinitionStoreException(
 5                     "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
 6         }
 7 
 8         if (resourceLoader instanceof ResourcePatternResolver) {
 9             // Resource pattern matching available.
10             try {
11                 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
12                 int loadCount = loadBeanDefinitions(resources);
13                 if (actualResources != null) {
14                     for (Resource resource : resources) {
15                         actualResources.add(resource);
16                     }
17                 }
18                 if (logger.isDebugEnabled()) {
19                     logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
20                 }
21                 return loadCount;
22             }
23             catch (IOException ex) {
24                 throw new BeanDefinitionStoreException(
25                         "Could not resolve bean definition resource pattern [" + location + "]", ex);
26             }
27         }
28         else {
29             // Can only load single resources by absolute URL.
30             Resource resource = resourceLoader.getResource(location);
31             int loadCount = loadBeanDefinitions(resource);
32             if (actualResources != null) {
33                 actualResources.add(resource);
34             }
35             if (logger.isDebugEnabled()) {
36                 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
37             }
38             return loadCount;
39         }
40     }

  這裏先獲得一個ResourceLoader對象。在類AbstractXmlApplicationContext中的loadBeanDefinitions()方法中有beanDefinitionReader.setResourceLoader(this)這段代碼,而

DefaultListableBeanFactory又是繼承了DefaultResourceLoader的,因此,這裏的resourceLoader對象是DefaultResourceLoader類型的,因此走下面的邏輯。首先,獲取一個resource
對象,getResource方法在DefaultResourceLoader中有實現,其代碼以下:
 1 public Resource getResource(String location) {
 2         Assert.notNull(location, "Location must not be null");
 3 
 4         for (ProtocolResolver protocolResolver : this.protocolResolvers) {
 5             Resource resource = protocolResolver.resolve(location, this);
 6             if (resource != null) {
 7                 return resource;
 8             }
 9         }
10 
11         if (location.startsWith("/")) {
12             return getResourceByPath(location);
13         }
14         else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
15             return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
16         }
17         else {
18             try {
19                 // Try to parse the location as a URL...
20                 URL url = new URL(location);
21                 return new UrlResource(url);
22             }
23             catch (MalformedURLException ex) {
24                 // No URL -> resolve as resource path.
25                 //若是都不是,則使用子類重寫的方法,例如子類FileSystemXMLApplicationContext中就重寫了這個方法
26                 return getResourceByPath(location);
27             }
28         }
29     }

  根據不一樣的狀況,生成一個ResourceLoader對象,這樣就完成了對配置的xml文件的定位。

  通過這麼一長條的跟蹤,終於完成了XML資源的定位工做。spring的繼承體系特別深,剛開始的時候感受特別繞,可是多跟着代碼跟幾遍,基本就清晰了,關鍵是要有耐心。在上面的分析中,咱們發現,spring使用了不少的模板方法,好比getResource方法,還有就是單一職責原則,每一個類很清晰,每一個方法中都是一個一個方法的調用,而不是代碼的堆砌,這點是值得咱們平時好好學習和運用的。

相關文章
相關標籤/搜索