1. 程式人生 > >spring4.2.9 java專案環境下ioc原始碼分析(三)——refresh之obtainFreshBeanFactory方法(@1準備工作與載入Resource)

spring4.2.9 java專案環境下ioc原始碼分析(三)——refresh之obtainFreshBeanFactory方法(@1準備工作與載入Resource)

obtainFreshBeanFactory方法從字面的意思看獲取新的Bean工廠,實際上這是一個過程,一個載入Xml資源並解析,根據解析結果組裝BeanDefinitions,然後初始化BeanFactory的過程。

在載入Xml檔案之前,spring還做了一些其他的工作,比如說判斷是否已經存在容器,建立Beanfactory並設定忽略自動裝配等等。下面結合程式碼具體看看

1、準備工作。

在spring中,基本上各司其職,每個類都有每個類的作用。AbstractApplicationContext#obtainFreshBeanFactory()

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

其中refreshBeanFactory是具體的重新整理BeanFactory,負責這個工作做在類AbstractRefreshableApplicationContext中,顧名思義這是專門用來重新整理的。getBeanFactory方法同樣是從此類中獲取得到的Beanfactory並返回。

protected final void refreshBeanFactory() throws BeansException {
		//判斷是否已經存在BeanFactory,存在則銷燬所有Beans,並且關閉BeanFactory.
		//目的是避免重複載入BeanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//建立具體的beanFactory,這裡建立的是DefaultListableBeanFactory,最重要的beanFactory
			//spring註冊及載入bean就靠它。其實這裡還是一個基本的容器
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			//擴充套件
			customizeBeanFactory(beanFactory);
			//初始化XmlBeanDefinitionReader用來讀取xml,並載入解析
			loadBeanDefinitions(beanFactory);
			//設定為全域性變數,AbstractRefreshableApplicationContext持有DefaultListableBeanFactory引用
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

下面看看createBeanFactory方法這裡還有些東西要說。

protected DefaultListableBeanFactory createBeanFactory() {
		return new DefaultListableBeanFactory(getInternalParentBeanFactory());
	}
protected BeanFactory getInternalParentBeanFactory() {
		return (getParent() instanceof ConfigurableApplicationContext) ?
				((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent();
	}
public DefaultListableBeanFactory(BeanFactory parentBeanFactory) {
		super(parentBeanFactory);
	}
public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) {
		this();
		setParentBeanFactory(parentBeanFactory);
	}
public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}

以上是createBeanFactory追溯到的所有程式碼。看看做了些什麼事

1、為DefaultListableBeanFactory設定父factory,如果父容器存在的話。

2、忽略自動裝配。這裡指定的都是介面。什麼意思呢?如果在載入bean的時候發現此bean是忽略介面的實現類,spring就不自動將其初始化,而是交給他自己。下面引用一段看到的內容來看


customizeBeanFactory方法如下,在類AbstractRefreshableApplicationContext中,這裡是根據AbstractRefreshableApplicationContext類的屬性為Beanfactory設定值。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		if (this.allowBeanDefinitionOverriding != null) {
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.allowCircularReferences != null) {
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
	}

allowBeanDefinitionOverriding屬性是指是否允對一個名字相同但definition不同進行重新註冊,預設是true。

allowCircularReferences屬性是指是否允許Bean之間迴圈引用,預設是true.

預設情況下兩個屬性都為空,既然是可擴充套件的,那麼自然可以自己設定屬性,方法就是繼承ClassPathXmlApplicationContext並複寫customizeBeanFactory方法為兩個屬性設定值即可。

接下來看loadBeanDefinitions方法,其實這也是準備工作

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//為指定的beanFactory建立XmlBeanDefinitionReader,做了XmlBeanDefinitionReader的初始化
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		//為XmlBeanDefinitionReader配置環境屬性
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		//擴充套件。。。子類複寫此方法,進行自定義初始化
		initBeanDefinitionReader(beanDefinitionReader);
		//XmlBeanDefinitionReader讀取xml
		loadBeanDefinitions(beanDefinitionReader);
	}

XmlBeanDefinitionReader初始化,無非是初始化自己與父類,父類做了些其他的工作

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		// Determine ResourceLoader to use.
		if (this.registry instanceof ResourceLoader) {
			this.resourceLoader = (ResourceLoader) this.registry;
		}
		else {
			this.resourceLoader = new PathMatchingResourcePatternResolver();
		}

		// Inherit Environment if possible
		if (this.registry instanceof EnvironmentCapable) {
			this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
		}
		else {
			this.environment = new StandardEnvironment();
		}
	}

根據BeanDefinitionRegistry的型別去建立不同的resourceLoader與environment,這裡resourceLoader為

PathMatchingResourcePatternResolver型別,environment為StandardEnvironment型別。但是在為

XmlBeanDefinitionReader配置環境屬性時候又把resourceLoader設定為本身ClassPathXmlApplicationContext,這裡有點不懂為什麼這麼做。不過做了也就這樣了

看下loadBeanDefinitions方法這裡開始載入xml檔案了。

2、XML檔案的讀取

先看程式碼

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		//擴充套件。。。預設是null。子類實現
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		//ClassPathXmlApplicationContext建構函式傳入的applicationContext.xml
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			//解析
			reader.loadBeanDefinitions(configLocations);
		}
	}

哈哈又一個擴充套件點,如果有一天我們的applicationContext.xml不是在calsspath下了,我們只有Resource,那怎麼辦,直接傳遞進來即可,繼承ClassPathXmlApplicationContext類重寫getConfigResources方法,返回Resource即可。

接下來看reader是怎樣解析的。

@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int counter = 0;
		for (String location : locations) {
			counter += loadBeanDefinitions(location);
		}
		return counter;
	}

委託給了AbstractBeanDefinitionReader類進行解析

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		//獲取當前資源載入器,這裡是ClassPathXmlApplicationContext
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}
		//ClassPathXmlApplicationContext繼承了ResourcePatternResolver
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				//吧Xml解析為Resource
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				//解析resource
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			//暫時還不太清楚
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}

重點看把location解析為Resource

@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		//是不是以classpath*:開始的
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			//以classpath*:開始的
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// 路徑中有?或*號萬用字元 如classpath*:applicationContext-*.xml
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 路徑沒有?或*號萬用字元 如classpath*:applicationContext.xml
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			//不以classpath*:開始的
			int prefixEnd = locationPattern.indexOf(":") + 1;
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// 路徑中有?或*號萬用字元
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 路徑沒有?或*號萬用字元
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

解析的依據有兩個是不是以classpath*:開頭的,是不是有萬用字元?或者*

先看findPathMatchingResources方法,比如classpath*:/APP/applicationContext-*.xml

首先不看程式碼想一下我們如果要匹配應該怎麼去做:

是不是要拿到APP目錄下所有的檔案,然後再與applicationContext-*.xml相匹配,符合就返回。

Spring也是這麼做的,下面來看具體程式碼。

	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		//獲取跟目錄路徑這裡是classpath*:/APP/
		String rootDirPath = determineRootDir(locationPattern);
		//匹配模式這裡是applicationContext-*.xml
		String subPattern = locationPattern.substring(rootDirPath.length());
		//然後繼續呼叫getResources,這時候傳進去的是classpath*:/APP/,這裡返回的是classpath*:/APP/的URLResources
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		for (Resource rootDirResource : rootDirResources) {
			//解析,目前是原樣返回
			rootDirResource = resolveRootDirResource(rootDirResource);
			//vfs(略)
			if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
			}
			//jar(略)
			else if (isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
			}
			else {
				//這裡開始做匹配
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		File rootDir;
		try {
			//獲取Resource的絕對檔案
			rootDir = rootDirResource.getFile().getAbsoluteFile();
		}
		catch (IOException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Cannot search for matching files underneath " + rootDirResource +
						" because it does not correspond to a directory in the file system", ex);
			}
			return Collections.emptySet();
		}
		//匹配解析
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
		}
		//具體匹配解析
		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
		//返回的是FileSystemResource
		for (File file : matchingFiles) {
			result.add(new FileSystemResource(file));
		}
		return result;
	}
protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
		if (!rootDir.exists()) {
			// Silently skip non-existing directories.
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
			}
			return Collections.emptySet();
		}
		if (!rootDir.isDirectory()) {
			// Complain louder if it exists but is no directory.
			if (logger.isWarnEnabled()) {
				logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
			}
			return Collections.emptySet();
		}
		if (!rootDir.canRead()) {
			if (logger.isWarnEnabled()) {
				logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
						"] because the application is not allowed to read the directory");
			}
			return Collections.emptySet();
		}
		//真實路徑
		String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
		if (!pattern.startsWith("/")) {
			//最後以“/”結尾
			fullPattern += "/";
		}
		//要匹配的路徑這裡是真實路徑+applicationContext-*.xml
		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
		Set<File> result = new LinkedHashSet<File>(8);
		//do開頭的開始做事
		doRetrieveMatchingFiles(fullPattern, rootDir, result);
		return result;
	}
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Searching directory [" + dir.getAbsolutePath() +
					"] for files matching pattern [" + fullPattern + "]");
		}
		//獲取檔案下的所有檔案
		File[] dirContents = dir.listFiles();
		if (dirContents == null) {
			if (logger.isWarnEnabled()) {
				logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
			}
			return;
		}
		for (File content : dirContents) {
			//檔案的路徑
			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
			//判斷檔案是不是目錄並且和currPath+“/”是匹配fullPattern
			if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
				if (!content.canRead()) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
								"] because the application is not allowed to read the directory");
					}
				}
				else {
					//遞迴呼叫
					doRetrieveMatchingFiles(fullPattern, content, result);
				}
			}
			//不是目錄的話判斷是不是匹配
			if (getPathMatcher().match(fullPattern, currPath)) {
				result.add(content);
			}
		}
	}
程式碼比較多,其實邏輯很簡單,就是拿到路徑下的檔案與設定的檔案匹配,其中穿插著遞迴呼叫。匹配演算法有興趣的可以研究下。

接下來是findAllClassPathResources方法,在上面的程式碼也看到了,需要呼叫findAllClassPathResources,程式碼如下

protected Resource[] findAllClassPathResources(String location) throws IOException {
		//傳進來的是/APP/
		String path = location;
		//以“/”開始的要去掉,相對路徑
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		//接下開始查詢資源
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}
	protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		//獲取類載入器
		ClassLoader cl = getClassLoader();
		//根據類載入器載入資源,預設是classpath下
		Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
		//遍歷
		while (resourceUrls.hasMoreElements()) {
			URL url = resourceUrls.nextElement();
			//轉變為URLResource
			result.add(convertClassLoaderURL(url));
		}
		//這裡是全jar查詢,比如說設定是classpath*:applicationContext-*.xml,就會走這裡的方法
		if ("".equals(path)) {
			// The above result is likely to be incomplete, i.e. only containing file system references.
			// We need to have pointers to each of the jar files on the classpath as well...
			addAllClassLoaderJarRoots(cl, result);
		}
		return result;
	}
上面的兩個方法覆蓋了3中情況,還剩下一種,既不是以classpath*:開頭的,也沒有萬用字元。
返回的程式碼是
return new Resource[] {getResourceLoader().getResource(locationPattern)};

這裡用資源載入器去獲取資源的。這裡的ResourceLoader是ClassPathXmlApplicationContext,getResource方法委託給了其父類DefaultResourceLoader,程式碼如下

public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		//以“/”開始,返回的ClassPathContextResource是ClassPathResource的子類,處理了“/”開始的情況
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		//以"classpath:"開始
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			//返回的是ClassPathResource
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			//都不是的情況下,是不是URL,以http://開始,返回UrlResource
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				//按照path解析,返回的ClassPathContextResource是ClassPathResource的子類
				return getResourceByPath(location);
			}
		}
	}

說點題外話:這裡一共三個方法解析路徑到Resource,返回的卻是不同型別的。

大體上有這幾類:URLResource,ClassPathResource,FileSystemResource.這些都是Spring封裝的,比如URLResource封裝了URL/URI,FileSystemResource封裝了檔案與路徑,ClassPathResource封裝了路徑與類載入器。根據不同的載入資源的方式去用不同的Resource。但是這樣又有點問題,用錯了怎麼辦?所以Spring又提供了一個介面ResourceLoader其實現是DefaultResourceLoader用來根據不同的地址字首去用不同的Resource。

ResourceLoader的程式碼上面提到了,下面看下:

public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

一張圖解決問題。但是這個類並沒有實現萬用字元功能。所以又提供一個介面ResourcePatternResolver其實現類PathMatchingResourcePatternResolver解決了,萬用字元的情況。

至此準備工作與通過xml載入Resource完成,接下來就是解析,後面講。