學習優秀框架的原始碼,是提升個人技術水平必不可少的一個環節。如果只是停留在知道怎麼用,但是不懂其中的來龍去脈,在技術的道路上註定走不長遠。最近,學習了一段時間的spring原始碼,現在整理出來,以便日後溫故知新。
IOC容器是spring最核心的模組之一,是整個spring體系的基石,spring其他模組中,都需要用到IOC容器的功能。spring框架為我們提供了多種IOC容器,DefaultableBeanFact
ory、FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext等。雖然我們平時很少在專案中使用這種硬編碼的方式來獲取IOC容器,繼而獲取IOC容器中的bean,但是研究這些IOC容器的原始碼,對我們理解IOC容器的原理還是很有必要的。BeanFactory這個介面是spring所有IOC容器最上層的介面,getBean()這個方法就是在這個介面中定義的,下面是其中定義的方法:
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容器的入口。下面以FileSystemXmlApplicatio
nContext為例,深入原始碼探究IOC容器的實現原理。IOC容器的初始化過程分為三個階段:定位、載入和註冊。接下來一一進行分析,先從XML的定位開始。
相信我們大家都使用以下程式碼獲取過IOC容器,獲取IOC容器之後,我們就可以得到想要的bean,然後進行操作了:
1 FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("bean.xml");
進入FileSystemXmlApplicationContext這個類,發現它定義了各種構造器,但最終都會呼叫下面這個構造器:
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中有實現,其程式碼如下:
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()方法,其程式碼如下:
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 }
這段程式碼可以看到:
1、首先,建立了一個DefaultListableBeanFactory的IOC容器;
2、對容器進行了一些設定;
3、呼叫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方法,還有就是單一職責原則,每個類很清晰,每個方法中都是一個一個方法的呼叫,而不是程式碼的堆砌,這點是值得我們平時好好學習和運用的。