1. 程式人生 > 實用技巧 >Spring解析Xml註冊Bean流程

Spring解析Xml註冊Bean流程

有道無術,術可求;

有術無道,止於術;

讀原始碼是一個很枯燥的過程,但是Spring原始碼裡面有很多值得學習的地方

加油~!!!!!

前言

使用SpringMVC的時候,通常使用下面這行程式碼來載入Spring的配置檔案

ApplicationContext application = new ClassPathXmlApplicationContext("webmvc.xml"),那麼這行程式碼到底進行了怎麼的操作,接下來就一探究境,看看是如何載入配置檔案的

Spring的配置檔案

這個配置檔案對於已經學會使用SpringMVC的你來說已經再熟悉不過了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <bean id="study" class="com.xiaobai.Student">
    </bean>
</beans>

那麼Spring是如何進行Bean的註冊的呢?經過這幾天的原始碼檢視我寫下了這篇文章來作為筆記,

因為我剛開始看Spring的原始碼,裡面有些內容可能理解的不是很到位,有錯誤請指出

原始碼檢視

再此之前我先bb幾句,為了方便檢視原始碼,可以去GitHub上下載Spring的原始碼匯入到Idea或者是eclipse中這樣檢視起來更方便些,同時還可以在上面寫一些註釋

既然使用的是ClassPathXmlApplicationContext("webmvc.xml")那就找到這個類的單參構造器檢視跟蹤下原始碼

/**
	 * Create a new ClassPathXmlApplicationContext, loading the definitions
	 * from the given XML file and automatically refreshing the context.
	 * @param configLocation resource location
	 * @throws BeansException if context creation failed
	 * 這個是建立 了 一個 ClassPathXmlApplicationContext,用來從給的XMl檔案中載入規定
	 */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

這裡呼叫的是本類中的另外一個三個引數的構造方法,便進入到了下面這些程式碼中

/**
	 * Create a new ClassPathXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of resource locations
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
    	//設定配置檔案的路徑
		setConfigLocations(configLocations);
		if (refresh) {
      	   //重要的方法,需要進入檢視
			refresh();
		}
	}

這裡來說下這個方法的引數的意思:

  • configLocations:這個裡面儲存的是配置檔案的路徑
  • Refresh:是否自動重新整理上下文
  • parent:父上下文

設定資源載入器

要跟蹤下super(parent)這行程式碼,在它的父類中(AbstractApplicationContext類裡面),有下面的程式碼,這段程式碼的作 用是獲取一個SpringResource的載入器用來載入資原始檔(這裡你可以理解為是為了載入webmvc.xml配置檔案做前期的準備)

protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}
//下面的方法在PathMatchingResourcePatternResolver類中,為了檢視方便我將這兩個方法寫在了一起
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
	Assert.notNull(resourceLoader, "ResourceLoader must not be null");
	this.resourceLoader = resourceLoader;
}

在PathMatchingResourcePatternResolver構造方法中就設定了一個資源載入器

設定Bean資訊位置

這個裡面有一個setConfigLocations方法,這個裡面會設定Bean配置資訊的位置,這個方法的所在的類是AbstractRefreshableConfigApplicationContext,它和CLassPathXmlApplicationContext之間是繼承的關係

@Nullable
private String[] configLocations;
public void setConfigLocations(@Nullable 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;
		}
	}

這裡面的configLocations的是一個數組,setConfigLocations方法的引數是一個可變引數,這個方法的作用是將多個路徑放到configLocations陣列中

閱讀refresh

這個方法可以說是一個非常重要的一個方法,這在個方法裡面規定了容器的啟動流程,具體的邏輯通過ConfigurableApplicationContext介面的子類實現

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
      	   //進入到此方法檢視
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				//這裡面的程式碼我刪除掉了,因為我們本文是看的解析XML建立 Bean的文章,這裡的程式碼暫時用不到,我就刪除了,要不然程式碼太多了
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

Bean的配置檔案是在這個方法裡面的refreshBeanFactory方法來處理的,這個方法是在AbstractRefreshableApplicationContext類中實現的

@Override
protected final void refreshBeanFactory() throws BeansException {
  if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
  }
  try {
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    beanFactory.setSerializationId(getId());
    customizeBeanFactory(beanFactory);
    //開始解析配置檔案
    loadBeanDefinitions(beanFactory);
    synchronized (this.beanFactoryMonitor) {
      this.beanFactory = beanFactory;
    }
  }
  catch (IOException ex) {
    throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
}

這裡有一個方法是loadBeanDefinitions(beanFactory)在這個方法裡面就開始解析配置檔案了,進入這個方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

  // Configure the bean definition reader with this context's
  // resource loading environment.
  beanDefinitionReader.setEnvironment(this.getEnvironment());
  beanDefinitionReader.setResourceLoader(this);
  beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

  // Allow a subclass to provide custom initialization of the reader,
  // then proceed with actually loading the bean definitions.
  initBeanDefinitionReader(beanDefinitionReader);
  //Bean讀取器實現載入的方法
  loadBeanDefinitions(beanDefinitionReader);
}

進入到loadBeanDefinitions(XmlBeanDefinitionReader reader)方法

XML Bean讀取器載入Bean配置資源

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
 //獲娶Bean配置資源的位置
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
    reader.loadBeanDefinitions(configResources);
  }
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
    reader.loadBeanDefinitions(configLocations);
  }
}

但是本文的教程是通過ClassPathXmlApplicationContext來舉的例子,getConfigResources()方法返回的是空的,就執行下面的分支

說點和本文有關也有可能沒有關係的話

當代碼看到這裡,學習過設計模式的同鞋可能會發現我們看過的這些程式碼裡也涉及到了委派模式策略模式因為Spring框架中使用到了很多的設計模式,所以說在看一些框架原始碼的時候,我們儘可能的先學習下設計模式,不管是對於看原始碼來說或者是對於在公司中工作都是啟到了很重要的作用,在工作中使用了設計模式對於以後系統的擴充套件或者是維護來說都是比較方便的。當然學習設計模式也是沒有那麼的簡單,或許你看了關於設計模式的視訊或者是一些書籍,但是在工作中如果是想很好的運用出來,還是要寫很多的程式碼和常用設計模式的。

學習設計模式也是投入精力的,Scott Mayer在《Effective C++》也說過:C++新手和老手的區別就是前者手背上有很多的傷疤。

未完....