1. 程式人生 > >Spring之ApplicationContext載入含有AOP標籤的配置檔案的流程

Spring之ApplicationContext載入含有AOP標籤的配置檔案的流程

一開始想用DefaultListableBeanFactory 跟蹤AOP的原始碼,發現它不支援AOP,是不支援還是自己配置錯了還沒搞清楚,抽空要比較一下BeanFactory下的子類的區別。 ApplicationContext和我們之前的解析配置檔案和建立bean的有點區別,之前都是採用的延時建立bean,就是當getBean()的時候,才會去例項化指定的bean,而ApplicationContext是解析玩配置檔案馬上去例項化bean,接下來,讓我們去跟著原始碼走一遍流程。 這次我們建立的類有如下幾個: 在這裡插入圖片描述 配置檔案如下: 在這裡插入圖片描述

主要就是想看一下對於AOP是如何解析配置檔案和建立例項的。走起!!!

ApplicationContext beanFactory = new ClassPathXmlApplicationContext("beans.xml");

這行程式碼可是我們這邊文章罪惡的源頭啊,哈哈哈!這是我們分析程式碼的入口。

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
		//呼叫父類的構造方法
		super(parent);
		//設定配置檔案的路徑
		setConfigLocations(configLocations);
		//看是否需要重新載入配置檔案
		if (refresh) {
			refresh();
		}
	}

這個構造方法每一步都很重要,我們先看一下設定配置檔案的路徑:

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;
		}
	}

我們這次用的IOC容器是ApplicationContext可以一次載入多個配置檔案。 Spring剛啟動的時候refresh為true,所以需要載入配置檔案,進入這個方法去看一下:

	synchronized (this.startupShutdownMonitor) {
			// 為重新讀取配置檔案準備環境
			prepareRefresh();

			// 宣告一個內部的IOC容器
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//準備bean factory,以便在此上下文中使用
			prepareBeanFactory(beanFactory);

			try {
				// 允許在上下文子類中對bean工廠進行後期處理。
				postProcessBeanFactory(beanFactory);

				// 呼叫上下文中註冊為bean的工廠處理器。
				invokeBeanFactoryPostProcessors(beanFactory);

				// 註冊攔截bean建立的bean處理器。
				registerBeanPostProcessors(beanFactory);

				// 初始化此上下文的訊息源。
				initMessageSource();

				//為這個上下文初始化事件多主機。
				initApplicationEventMulticaster();

				//初始化特定上下文子類中的其他特殊bean。
				onRefresh();

				// 檢查偵聽器bean並註冊它們
				registerListeners();

				// 例項化所有剩餘的(非延遲-init)單例
				finishBeanFactoryInitialization(beanFactory);

				// 最後一步:釋出響應的事件
				finishRefresh();
			}

			catch (BeansException ex) {
				...
			}

			finally {
				//在Spring的核心中重置常見的內省快取,因為我們可能再也不需要單例bean的元資料了
				resetCommonCaches();
			}
		}
	}

在這個方法中沒有一句廢話,需要做什麼非常清晰,我們就來一個一個方法的進入去看看到底怎麼回事:

protected void prepareRefresh() {
		//本次重新整理開始的時間
		this.startupDate = System.currentTimeMillis();
		this.closed.set(false);
		this.active.set(true);
		...
		// 在上下文環境中初始化任何佔位符屬性源
		initPropertySources();

		// 驗證所有標記為必需的屬性都是可解析的
		getEnvironment().validateRequiredProperties();

		// 允許收集早期的ApplicationEvents,在多媒體伺服器可用後釋出…
		this.earlyApplicationEvents = new LinkedHashSet<>();
	}

這個方法就是在開始解析前進行了一些必要的配置

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		...
		return beanFactory;
	}

告訴子類重新整理內部bean工廠。

protected final void refreshBeanFactory() throws BeansException {
		//如果存在bean工廠,現在要重新整理bean工廠,需要把已經存在的工廠銷燬
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//建立一個新的beanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			//設定序列號
			beanFactory.setSerializationId(getId());
			//自定義此上下文使用的內部bean工廠。
			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(),這不就是我們之前閱讀過的方法麼,但這是ApplicationContext了,應該會有一些不同,在閱讀這個方法之前我們先看一下當前內部的beanFactory是如何建立的:

protected DefaultListableBeanFactory createBeanFactory() {
		return new DefaultListableBeanFactory(getInternalParentBeanFactory());
	}

直接利用構造方法返回了一個DefaultListableBeanFactory例項。 接下來看它是如何解析配置檔案的:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// 建立一個配置檔案的解析器
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// 配置資源載入環境
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// 允許子類提供閱讀器的自定義初始化,然後繼續實際載入bean定義
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

這個方法給配置檔案解析器設定了一些屬性,繼續跟蹤載入bean的定義

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

這個方法根據我們傳的是Recourse物件還是配置檔案的路徑,選擇不同的方法進行解析,在這裡我們傳的是配置檔案的路徑:

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;
	}

由於我們可以傳多個配置檔案,所以在解析的時候就要在迴圈中一個一個的解析:

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		...
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				//獲得路徑上的資源,可能路徑是萬用字元,所以得到的是資源陣列
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				...
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		...
	}

這個方法主要是將路徑上的配置檔案封裝成了Resource物件

public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int counter = 0;
		//一個一個的解析資源
		for (Resource resource : resources) {
			counter += loadBeanDefinitions(resource);
		}
		return counter;
	}

用迴圈去一個一個解析資源物件

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

這段程式碼看著很眼熟吧,這就是我們之前將bean載入的時候一模一樣的方法,就不細講了,之後的程式碼我們也只看沒見過的程式碼:

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		//獲取自定義的名稱空間,在這歷史AOP的名稱空間
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		//得到該名稱空間對應的解析器
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		//開始解析
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

在解析配置檔案的節點的時候遇到了AOP的自定義標籤,所以要選區對應的解析器進行解析:

public BeanDefinition parse(Element element, ParserContext parserContext) {
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);
	}

得到對應自定義標籤的指定解析器開始解析:

public BeanDefinition parse(Element element, ParserContext parserContext) {
		//建立一個<aop:config/>的定義
		CompositeComponentDefinition compositeDef =
				new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
		//將這個定義放到parserContext的棧中
		parserContext.pushContainingComponent(compositeDef);
		//在這個方法裡面吧上面生成的定義註冊到了IOC容器中
		configureAutoProxyCreator(parserContext, element);

		List<Element> childElts = DomUtils.getChildElements(element);
		for (Element elt: childElts) {
			String localName = parserContext.getDelegate().getLocalName(elt);
			if (POINTCUT.equals(localName)) {
				//對<aop:pointcut/>的解析
				parsePointcut(elt, parserContext);
			}
			//對<aop:advisor/>的解析
			else if (ADVISOR.equals(localName)) {
				parseAdvisor(elt, parserContext);
			}
			//對<aop:aspect/>的解析
			else if (ASPECT.equals(localName)) {
				parseAspect(elt, parserContext);
			}
		}

		parserContext.popAndRegisterContainingComponent();
		return null;
	}

在這個方法裡面,一開開始就建立了一個<aop:config/>的定義,接著被放到了解析器的棧中,這一步是為了解析玩子元素後讓他的子元素的定義註冊到他身上,我在讀這段程式碼的時候,略過了configureAutoProxyCreator這個方法,讓我變得很疑惑,建立了一個<aop:config/>的定義之後,怎麼沒註冊到IOC中呢?為了找到這個問題一直找了40分鐘才結局,唉。之後就是遍歷<aop:config/>下面的所有子節點,選用不同的方法進行解析

private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
		//獲得id
		String id = pointcutElement.getAttribute(ID);
		//獲得表示式
		String expression = pointcutElement.getAttribute(EXPRESSION);
		//宣告一個切點的定義
		AbstractBeanDefinition pointcutDefinition = null;

		try {
			this.parseState.push(new PointcutEntry(id));
			//根據表示式建立一個切點的定義
			pointcutDefinition = createPointcutDefinition(expression);
			pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
			//設定切點的名稱
			String pointcutBeanName = id;
			//註冊到IOC中
			if (StringUtils.hasText(pointcutBeanName)) {
				parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
			}
			else {
				pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
			}
			//將切點定義和<aop:config/>的定義繫結到一起
			parserContext.registerComponent(
					new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
		}
		finally {
			this.parseState.pop();
		}

		return pointcutDefinition;
	}

這個方法解析了<aop:pointcut/>這個標籤,並將它的定義註冊到了IOC當中,也將他和父定義繫結在了一起。

protected AbstractBeanDefinition createPointcutDefinition(String expression) {
		//建立一個bean定義
		RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
		//設定為多例
		beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
		beanDefinition.setSynthetic(true);
		//設定表示式屬性
		beanDefinition.getPropertyValues().add(EXPRESSION, expression);
		return beanDefinition;
	}

這個方法根據切點的class物件建立了一個beanDefinition,之後為他填充了一些屬性。

parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);

將載入完的bean定義放到IOC容器中儲存。接下來看看是如何解析切面的:

private void parseAspect(Element aspectElement, ParserContext parserContext) {
		//得到id和name屬性
		String aspectId = aspectElement.getAttribute(ID);
		String aspectName = aspectElement.getAttribute(REF);

		try {
			this.parseState.push(new AspectEntry(aspectId, aspectName));
			//常見一個bean定義的列表和一個bean引用的列表
			List<BeanDefinition> beanDefinitions = new ArrayList<>();
			List<BeanReference> beanReferences = new ArrayList<>();

			List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
			for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
				Element declareParentsElement = declareParents.get(i);
				beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
			}

			//得到<aop:aspect />的所有子節點
			NodeList nodeList = aspectElement.getChildNodes();
			boolean adviceFoundAlready = false;
			for (int i = 0; i < nodeList.getLength(); i++) {
				Node node = nodeList.item(i);
				//如果是advice節點
				if (isAdviceNode(node, parserContext)) {
					if (!adviceFoundAlready) {
						adviceFoundAlready = true;
						if (!StringUtils.hasText(aspectName)) {
							parserContext.getReaderContext().error(
									"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
									aspectElement, this.parseState.snapshot());
							return;
						}
						//將該節點引用的值記錄下來
						beanReferences.add(new RuntimeBeanReference(aspectName));
					}
					//建立一個advisor定義並註冊到IOC容器中
					AbstractBeanDefinition advisorDefinition = parseAdvice(
							aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
					//將建立的advisor定義新增到列表中
					beanDefinitions.add(advisorDefinition);
				}
			}
			//將解析的<aop:aspect/> 封裝成一個定義並且註冊到IOC容器中
			AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
					aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
			parserContext.pushContainingComponent(aspectComponentDefinition);

			List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
			for (Element pointcutElement : pointcuts) {
				parsePointcut(pointcutElement, parserContext);
			}
			//將<aop:aspect/>和<aop:config/>繫結到一起
			parserContext.popAndRegisterContainingComponent();
		}
		finally {
			this.parseState.pop();
		}
	}

這個方法做的工作就是解析<aop:aspect/>和它的子元素,並將它註冊到IOC容器中和<aop:config/>繫結到一起。 解析玩aop標籤後在方法parse裡面還有最後一步:

parserContext.popAndRegisterContainingComponent();

這一步主要就是將<aop:config/>的定義從棧中拿出來,表示aop標籤解析完成,因為他不需要在和誰繫結到一起了。 執行到這我們的配置檔案就解析完成了,這篇文章全都是圍繞這行程式碼展開的:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

在下一篇文章中,我們就要看一下AOP的定義是如何被例項化的。