美文网首页
Spring源码阅读笔记之容器初始化

Spring源码阅读笔记之容器初始化

作者: Moine0828 | 来源:发表于2019-07-21 15:49 被阅读0次

1、容器初始化,ApplicationContext.xml的解析

Spring提供了很多种配置的方式,有xml,基于代码,基于注解。自然的,也就有很多种不同的初始化容器的方式。这里以使用最广泛的xml为例。

1.png

(图片来自网络)ApplicationContext有一张庞大的继承网络,源接口来自BeanFactory,即创建bean的工厂。因此,继承自ApplicationContext的ClassPathXmlApplicationContext类,也是Bean工厂的一种,它解析xml文件,创建配置好的bean。

首先来看它的构造方法。若有父AppllicationContext传入,则先调用父方法。随后设置配置路径。最后调用refresh方法。

2.png

setConfigLocations方法,设置配置路径。通过断点我们可以清晰的看到,这个路径就是我们项目里配置的applicationContext.xml的路径。但是这一步并没有对文件做任何的解析,而是将文件名解析出来,放置到一个String类型的数组configLocations当中。

3.png

refresh方法开始进行实质性的容器刷新。方法比较长,步骤也比较多,属于spring比较核心的方法。

@Override
public void refresh() throws BeansException, IllegalStateException {
    //开始刷新之前需要加锁
    synchronized (this.startupShutdownMonitor) {
        // 刷新前的准备工作,记录启动时间
            //设置一些boolean的状态位,处理配置文件里的占位符
        prepareRefresh();

        // 这一步的核心是refreshBeanFactory方法,在方法中加载了bean的定义信息,loadBeanDefinitions
          //方法,即初始化xml阅读器,然后读取配置文件,将bean的定义,或其他定义,读取成Document
          //对象,然后在BeanDenifitionDocumentReader的doRegisterBeanDefinitions
          //方法中,将Document对象逐行解析,判断是import还是bean或者是其他,随即调用对应的
          //处理逻辑,processBeanDefinition,或importBeanDenifitionResource方法等,以
          //processBeanDefinition方法为例,首先获取BeanDefinitionHolder和BeanDefinitionRegistry
          //两个对象,随即对bean进行注册,注册的核心是registry.registerBeanDefinition
          //方法,其实非常简单,就是把bean的名称和一些其他的信息,放到一个map里
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        //对BeanFactory做一些准备工作,例如设置BeanClassLoader,BeanPostPocesser
  //,装载一些特殊的bean,例如environment, systemProperties等等
        prepareBeanFactory(beanFactory);

        try {
            //bean如果是实现了BeanFactoryPostProcessor 接口,那么在这里,可以调用bean的方法
      //来让bean提前做一些事
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            //注册BeanPostProcessor 实现类,这类似于bean初始化的一个切面,分别在bean初始化之前
   //和之后执行方法,则对应的实现类需要给出两个方法的具体实现,postProcessBeforeInitialization和postProcessAfterInitialization
            registerBeanPostProcessors(beanFactory);
   

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // 模板方法,让子类去做一些自定义的操作。
            onRefresh();

            //注册事件监听器,事件监听需要实现ApplicationListener接口
   //例如dubbo的ServiceBean就实现了这个接口
            registerListeners();

            //初始化所有的单例bean。这里同样的也做了很多事情,例如注册注解替换处理器等
            finishBeanFactoryInitialization(beanFactory);

            // 广播事件,刷新完成
            finishRefresh();
        }

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

至此,spring就完成了容器的初始化,bean的初始化,以及一系列相关工具的初始化。下面对其中的方法进行进一步的解析。

prepareRefresh

protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // Initialize any placeholder property sources in the context environment
    //从这句话的注解也可以看得出,这个方法就是处理配置文件中的占位符
      initPropertySources();

        // Validate that all properties marked as required are resolvable
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
    }

initPropertySources方法的默认实现在AbstraceRefreshableWebApplicationCOntext类中,在方法中,获取了当前的ConfigurableEnvironment对象,通过断点可以看到,env环境对象中,包含了一个

propertyResolver类,他的一些属性显而易见,是为了处理${}形式的占位符。

4.png

进入到env对象的initPropertySources方法里,最终是来到了WebApplicationContextUtils.initServletPropertySources方法来处理占位符。

obtainFreshBeanFactory

准备完毕,接下来进入obtainFreshBeanFactory方法,在这个方法里做了很多事情,例如创建BeanFactory,将bean注册(但是不初始化)等等。obtainFreshBeanFactory的核心是refreshBeanFactory方法。直接进入refreshBeanFactory方法

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

refreshBeanFactory

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

首先判断beanFactory是不是为null,如果不是,会把单例的bean全部清理,然后将beanFactory置为null。既然要刷新,就得把之前的先清理干净。

随后createBeanFactory方法,newl了一个DefaultListableBeanFactory,这是ApplicationContext持有的BeanFactory的默认实现。

接下来一行,通过获取类的id来作为序列化ID

接下来一行调用customizeBeanFactory方法,这个方法设置了两个属性,是否允许同名bean覆盖,是否允许循环依赖。

接下来一行loadBeanDefinitions方法,这是解析xml,初始化Bean的核心方法。在方法中先实例化一个XmlBeanDefinitionReader,xml的解析和bean的加载都在reader中完成。重点在loadBeanDefinitions方法里。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

getConfigLocations获取一个String数组。这个数组里装的就是配置文件的路径了,随后放入reader进行解析。loadBeanDefinitions代码如下,核心的逻辑是获取了一个Resource数组然后再次调用了loadBeanDefinitions的重载方法。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }

    if (resourceLoader instanceof ResourcePatternResolver) {
        // Resource pattern matching available.
        try {
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    //将资源进行加载,也就是将xml文件进行解析     
   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;
    }
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //...多余代码不复制

            //核心逻辑,h获取了资源对象的inputStream,开始读取resource里保存的xml文件路径
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
    //读到文件流之后调用doLoadBeanDefinitions方法
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
    }
 //不核心的代码省略

在这个方法里,Resource里保存的xml路径被转换为一个imputStream对象,文件流被读进来并且封装成一个InputResource对象。通过断点可以看到这个对象里啥都没有,只有一个byteStream

5.png

随后调用doLoadBeanDefinitions方法,代码如下

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        return registerBeanDefinitions(doc, resource);
    }
}

doLoadDocument将文件流解析成了Document对象。随后调用registerBeanDefinitions方法对bean进行注册。registerBeanDefinitions最终来到DefaultBeanDefinitaionDocumentReader的doRegisterBeanDefinitions方法,代码如下

protected void doRegisterBeanDefinitions(Element root) {
    // 这里有一大堆注解,被我删掉了
    // 大意是说<beans>标签里可以递归的定义<beans>,所以为了解决递归问题
    // 需要传递一个parent
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    //如果是默认的namespace
    //默认的namespace是http://www.springframework.org/schema/beans
    if (this.delegate.isDefaultNamespace(root)) {
     //然后是一大段关于profile配置的解析。profile就是环境之类的,不常用
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}

preProcessXml和postProcessXml都是模板方法,留给子类实现的,从断点可以看到,其实是没有提供实现。可以跳过。重点关注parseBeanDifinitions方法

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)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

首先判断是不是默认的namespace,也就是说如果是beans标签,自然是要把子标签的bean都一一解析出来的。贴一张项目里的配置文件作为例子

6.png 7.png

可以看到例子中的applicationContext.xml文件是以beans标签开头的,因此进入一个for循环,开始解析标签下的子node。由断点的截图可以看到,当前解析出了Element对象类型的node,即图一中的第一个标签<context:annotation-config>。解析出Element对象以后又判断了一次是不是默认的namespace,如果是那么递归解析。如果不是,那就默认解析,调用的是parseCustomElement方法,在这个方法中,根据其namespace来找到对应的namespaceHandler,所有的handlerMapping都被保存在一个map当中,以namespaceUri为key,查找对应的namespaceHandler。这也是spring的扩展性所在,自定义实现namespaceHandler就可以解析配置文件中的自定义标签了,例如dubbo的<dubbo:xxx>标签。每一个标签对应的bean,最后都被解析成了BeanDefinition对象。

相关文章

网友评论

      本文标题:Spring源码阅读笔记之容器初始化

      本文链接:https://www.haomeiwen.com/subject/rttvlctx.html