Spring IOC实现原理笔记(二) -- 加载XML配置

这篇文章接着上篇Spring IOC实现原理笔记(一)的测试代码,从ClassPathXmlApplicationContext开始分析spring的装载对象到容器的实现。

先放出继承图,对源码跟踪有帮助

ClassPathXmlApplicationContext

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[]{configLocation}, true, (ApplicationContext)null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
    super(parent);//设置父ApplicationContext
    this.setConfigLocations(configLocations);//设置配置文件路径
    if(refresh) {
        this.refresh();
    }
}

有点意外,逻辑这么简单,然后跟踪refresh方法进去,跳到了AbstractApplicationContext类,然后就头大了,如下

public void refresh() throws BeansException, IllegalStateException {
    Object var1 = this.startupShutdownMonitor;
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();//步骤1:准备刷新上下文环境
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();//步骤2:初始化BeanFactory,并进行XML文件读取
        this.prepareBeanFactory(beanFactory);//步骤3:对BeanFactory各种功能填充
        try {
            this.postProcessBeanFactory(beanFactory);//步骤4:子类富态方法做额外的处理
            this.invokeBeanFactoryPostProcessors(beanFactory);//步骤5:激活各种BeanFactory处理器
            this.registerBeanPostProcessors(beanFactory);//步骤6:注册拦截Bean创建的Bean处理器
            this.initMessageSource();//步骤7:为上下文初始化Message源,即国际化处理
            this.initApplicationEventMulticaster();//步骤8:初始化应用消息广播器
            this.onRefresh();//步骤9:留给子类具体实现
            this.registerListeners();//步骤10:注册消息广播器
            this.finishBeanFactoryInitialization(beanFactory);//步骤11:初始化剩余的单实例(非惰性)
            this.finishRefresh();//步骤12:完成刷新过程,通知生命周期处理器,发出刷新事件
        } catch (BeansException var9) {
            this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt", var9);
            this.destroyBeans();//步骤14
            this.cancelRefresh(var9);//步骤15
            throw var9;
        } finally {
            this.resetCommonCaches();//步骤13:清理缓存
        }

    }
}

因为本篇只写关于加载XML配置信息,而上面的【步骤2】就做了这一步工作,跟踪进去

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    this.refreshBeanFactory();//这里开始初始化BeanFactory并解析XML信息
    ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
    return beanFactory;
}

protected final void refreshBeanFactory() throws BeansException {
    if(this.hasBeanFactory()) {//销毁原有的BeanFactory
        this.destroyBeans();
        this.closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();
        beanFactory.setSerializationId(this.getId());
        this.customizeBeanFactory(beanFactory);
        this.loadBeanDefinitions(beanFactory);//进入解析XML阶段
        Object var2 = this.beanFactoryMonitor;
        synchronized(this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    } catch (IOException var5) {
        ...
    }
}

可以看到refreshBeanFactory方法会把当前的BeanFactory销毁,然后初始化一个DefaultListableBeanFactory实例(作为AbstractRefreshableApplicationContext的一个成员变量)。

DefaultListableBeanFactory继承图如下

DefaultListableBeanFactory

接下来跟踪解析XML阶段:loadBeanDefinitions(beanFactory);

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));//为解决联网获取xsd,dtd文件的问题
    this.initBeanDefinitionReader(beanDefinitionReader);
    this.loadBeanDefinitions(beanDefinitionReader);
}

上面看到创建了一个XmlBeanDefinitionReader实例,并持有beanFactory的引用(构造方法传入的)。XmlBeanDefinitionReader就是用于解析XML,并把解析的bean封装成BeanDefinitionHolder,然后传给BeanDefinitionRegistry(实际就是DefaultListableBeanFactory),进行相关的缓存。

下面出场的是XML解析的主力XmlBeanDefinitionReader

XmlBeanDefinitionReader

    ...上面的loadBeanDefinitions方法,经过山路十八弯,最终进入下面的方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
   try {
//解析XML成一个Document(包:org.w3c.dom)对象
       Document doc = this.doLoadDocument(inputSource, resource);
//解析内容封装成BeanDefinitionHolder,然后放入BeanDefinitionRegistry中缓存起来
       return this.registerBeanDefinitions(doc, resource);
   } catch...
}

最终解析会进入下面的方法

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
//先判断profile属性进行判断是否符合,不符合就退出不用解析了
    if(this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute("profile");
        if(StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }
...
    this.parseBeanDefinitions(root, this.delegate);//开始解析
    ...
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if(delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for(int i = 0; i < nl.getLength(); ++i) {
            Node node = nl.item(i);
            if(node instanceof Element) {
                Element ele = (Element)node;
                if(delegate.isDefaultNamespace(ele)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }
}

对XML解析分两种情况,第一是对默认节点的解析(如bean,alias,import,beans等),第二是对自定义节点的解析(如tx,p,aop等)。解析得到的bean的相关配置信息封装成BeanDefinitionHolder,然后传给BeanDefinitionRegistry(实际就是DefaultListableBeanFactory,可从继承图看到出),进行相关缓存操作。

如果是解析自定义节点则是需要有相对应的NamespaceHandler,后续分析AOP,会分析NamespaceHandler

//解析默认节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if(delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if(delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if(delegate.nodeNameEquals(ele, "bean")) {
        this.processBeanDefinition(ele, delegate);
    } else if(delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }

}

//解析自定义节点
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = this.getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if(handler == null) {
        ...
        return null;
    } else {
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
}

看一下BeanDefinitionHolder类

public class BeanDefinitionHolder implements BeanMetadataElement {
    private final BeanDefinition beanDefinition;
    private final String beanName;
    private final String[] aliases;
    ...
}

它记录了一个bean的配置信息,还有bean的名称、别名,通过它可以把一个bean的配置信息,别名缓存到BeanDefinitionRegistry

总结:

XML解析主要用了SAX技术解析,然后把解析的bean的信息封装成BeanDefinition存入BeanDefinitionRegistry中,方便之后从容器中获取实例时根据解析好的信息进行实例化。当然由于Spring提供了多种强大的配置,其中解析的过程并不轻松,要经过很多逻辑的处理。

Spring IOC实现原理笔记(一)中有一个BeanFactoryPostProcessor实例,它在哪里被调用的呢?其实就在【步骤5】

this.invokeBeanFactoryPostProcessors(beanFactory);

参考:

Spring源码(4.2.2.RELEASE)

《Spring源码深度解析》

猜你喜欢

转载自blog.csdn.net/seasonLai/article/details/82688921