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的定義是如何被例項化的。