Spring IOC:obtainFreshBeanFactory调用链

参考资料:

《Spring IoC源码学习:obtainFreshBeanFactory 详解》

前文:

《Spring系列:从ContextLoaderListener到AbstractApplicationContext》

写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

目录

一、概述

        1、重要属性:

        1.1、beanDefinitionNames

        1.2、beanDefinitionMap

        2、源码

        2.1、refreshBeanFactory

        2.2、createBeanFactory

二、loadBeanDefinitions

        1、XmlWebApplicationContext类

        1.1、loadBeanDefinitions

        1.2、loadBeanDefinitions

        2、AbstractBeanDefinitionReade类

        2.1、loadBeanDefinitions

        2.2、loadBeanDefinitions

        3、 XmlBeanDefinitionReader类

        3.1、loadBeanDefinitions

        3.2、doLoadBeanDefinitions

        3.3、doLoadDocument

三、registerBeanDefinitions

        1、createBeanDefinitionDocumentReader

        2、createReaderContext

         3、registerBeanDefinitions

        3、doRegisterBeanDefinitions

        4、parseBeanDefinitions

总结:


一、概述

        obtainFreshBeanFactory 方法会解析所有 Spring 配置文件(即我们在前文中介绍的contextConfigLocation配置),将所有 Spring 配置文件中的 bean 定义封装成 BeanDefinition,加载到 BeanFactory 中。

        如果使用的是注解方式,在解析到<context:component-scan base-package="" /> 注解时,会扫描 base-package 指定的目录,将该目录下使用指定注解的 bean 定义也同样封装成 BeanDefinition,加载到 BeanFactory 中

        1、重要属性:

        1.1、beanDefinitionNames

        所有被加载到 BeanFactory 中的 bean 的 beanName 集合

        1.2、beanDefinitionMap

        所有被加载到 BeanFactory 中的 bean 的 beanName 和 BeanDefinition 映射

        2、源码

        由AbstractApplicationContext类的refresh方法中的obtainFreshBeanFactory往下走。

        2.1、refreshBeanFactory

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    // 刷新 BeanFactory,由AbstractRefreshableApplicationContext实现
    refreshBeanFactory();
    // 拿到刷新后的 BeanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}
 

         刷新 BeanFactory,由 AbstractRefreshableApplicationContext 实现,该方法内部会

@Override
protected final void refreshBeanFactory() throws BeansException {
    // 判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 创建一个新的BeanFactory
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        // 定制beanFactory,设置相关属性,有2个重要属性:
        // 1. allowBeanDefinitionOverriding:是否允许覆盖同名称的不同定义的对象
        // 2. allowCircularReferences:是否允许bean之间的循环依赖
        customizeBeanFactory(beanFactory);
        // 加载 bean 定义。
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    } catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

        2.2、createBeanFactory

        createBeanFactory方法会实例化一个DefaultListableBeanFactory类,并传入一个参数。

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

         这个方法的实现则在AbstractApplicationContext中,getParent方法便是获取前文中第三节所说的父上下文。

	protected BeanFactory getInternalParentBeanFactory() {
		return (getParent() instanceof ConfigurableApplicationContext) ?
				((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent();
	}

	@Override
	public ApplicationContext getParent() {
		return this.parent;
	}

二、loadBeanDefinitions

  

        1、XmlWebApplicationContext类

        loadBeanDefinitions会来到我们上篇文章中创建的默认webapplication实现,即XmlWebApplicationContext类中。

        1.1、loadBeanDefinitions

        这里首先会创建一个用来读取配置文件的对象beanDefinitionReader,然后为其配置环境(上篇文章中创建的StandardServletEnvironment)、资源加载器、解析器等属性,然后使用这个读取对象来加载BeanDefinition。

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容器使用该读取器读取Bean定义资源
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
 

    // 配置上下文的环境,资源加载器、解析器
    beanDefinitionReader.setEnvironment(getEnvironment());
    // resourceLoader赋值为XmlWebApplicationContext
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
 
    // 允许子类自行初始化(比如校验机制),并提供真正的加载方法
    initBeanDefinitionReader(beanDefinitionReader);
    // 加载beanDefinition
    loadBeanDefinitions(beanDefinitionReader);
}

        1.2、loadBeanDefinitions

        这一步会根据我们在前文ApplicationContext刷新步骤中置入的configLocations判断从哪里加载spring的配置文件,如果web.xml中未配置contextConfigLocation参数,则会从默认路径/WEB-INF/applicationContext.xml下获取。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    // 获取配置文件路径
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            // 根据配置文件路径加载 bean 定义
            reader.loadBeanDefinitions(configLocation);
        }
    }
}
 
// AbstractRefreshableWebApplicationContext.java
@Override
public String[] getConfigLocations() {
    return super.getConfigLocations();
}
 
// AbstractRefreshableConfigApplicationContext.java
protected String[] getConfigLocations() {
    // 判断前置步骤是否获取到了配置的configLocations参数,没有则从默认路径获取
    return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}
 
// XmlWebApplicationContext.java
@Override
protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
        return new String[]{DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    } else {
        return new String[]{DEFAULT_CONFIG_LOCATION};
    }
}

        2、AbstractBeanDefinitionReade类

        2.1、loadBeanDefinitions

        该步骤使用XmlWebApplicationContext从前一步骤获取到的configLocation中获取spring配置文件的路径,并封装成 Resource。

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(location, null);
}
 
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // 获取 resourceLoader,这边为XmlWebApplicationContext
    // 上文1.1、loadBeanDefinitions步骤置入
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }
 
    // 判断 resourceLoader 是否为 ResourcePatternResolver 的实例
    if (resourceLoader instanceof ResourcePatternResolver) {
        try {
            // 根据路径拿到该路径下所有符合的配置文件,并封装成Resource
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            // 根据Resource,加载Bean的定义
            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 {
        // 通过绝对路径加载单个资源
        Resource resource = resourceLoader.getResource(location);
        // 根据Resource,加载Bean的定义
        int loadCount = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
        }
        return loadCount;
    }
}

        到这一步,Spring将配置文件封装成了Resource对象,为下一步解析做准备。

        2.2、loadBeanDefinitions

        遍历所有的Resource对象(即配置文件)来加载bean,记录所有生成的bean的个数。

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
    Assert.notNull(resources, "Resource array must not be null");
    int counter = 0;
    // 遍历所有的Resource
    for (Resource resource : resources) {
        // 根据Resource加载bean的定义,XmlBeanDefinitionReader实现
        counter += loadBeanDefinitions(resource);
    }
    return counter;
}

        3、 XmlBeanDefinitionReader类

        这一步真正进入到了配置文件读取阶段(注意只是读取而非解析),下面的步骤主要由XmlBeanDefinitionReader类来完成。

        3.1、loadBeanDefinitions

        首先将resource封装为EncodedResource对象(EncodedResource是带编码格式的Resource)。

        在进行加载前先判断这个EncodedResource是否已在进行加载,避免循环加载。

        如可以加载则获取到该文件的InputSource,进入doLoadBeanDefinitions方法。(根据Spring的习惯,do开头的方法就是真正干活的了)

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    // 加载 bean 定义,将resource封装为EncodedResource对象
    return loadBeanDefinitions(new EncodedResource(resource));
}
 
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());
    }
 
    // 当前正在加载的EncodedResource
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    // 将当前encodedResource添加到currentResources
    if (!currentResources.add(encodedResource)) {
        // 如果添加失败,代表当前的encodedResource已经存在,则表示出现了循环加载
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        // 拿到Resource的inputStream
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            // 将InputStream封装成一个InputSource,InputSource是xml SAX解析的数据源
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // 加载 bean 定义(方法以do开头,真正处理的方法)
            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();
        }
    }
}

        3.2、doLoadBeanDefinitions

        这一步负责调用doLoadDocument方法将resource转换为document对象。然后就可以利用这个document对象对配置文件进行解析。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        // 根据inputSource和resource加载XML文件,并封装成Document
        Document doc = doLoadDocument(inputSource, resource);
        // 根据返回的Document注册Bean信息(对配置文件的解析,核心逻辑)
        return registerBeanDefinitions(doc, resource);
    } catch (BeanDefinitionStoreException ex) {
        throw ex;
    } catch (SAXParseException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    } catch (SAXException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "XML document from " + resource + " is invalid", ex);
    } catch (ParserConfigurationException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Parser configuration exception parsing XML from " + resource, ex);
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "IOException parsing XML document from " + resource, ex);
    } catch (Throwable ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Unexpected exception parsing XML document from " + resource, ex);
    }
}

        3.3、doLoadDocument

        doLoadDocument方法将inputSource和resource封装成Document

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    // getValidationModeForResource(resource): 获取XML配置文件的验证模式
    // documentLoader.loadDocument: 加载XML文件,并得到对应的 Document
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}

        getValidationModeForResource(resource)用于校验xml格式是否正确,这里略过,有兴趣的可以看这里《传送门》

// DefaultDocumentLoader.java
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    // 创建DocumentBuilderFactory
    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isDebugEnabled()) {
        logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    // 通过DocumentBuilderFactory创建DocumentBuilder
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    // 使用DocumentBuilder解析inputSource返回Document对象
    return builder.parse(inputSource);
}

三、registerBeanDefinitions

        在获取到了配置文件的Document 对象后,我们就可以利用它来解析文件获取bean了。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 记录当前BeanDefinition个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // createReaderContext:根据resource创建一个XmlReaderContext
    // registerBeanDefinitions:加载及注册Bean定义
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 计算当前BeanDefinition个数-加载前BeanDefinition个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

        1、createBeanDefinitionDocumentReader

        获取DefaultBeanDefinitionDocumentReader对象来作为document的读取器。

	private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

	protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
		return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
	}

        2、createReaderContext

        createReaderContext的作用在于创建一个读取器的上下文,来存放解析时用到的一些信息。        

public XmlReaderContext createReaderContext(Resource resource) {
    // 根据 resource 构建一个 XmlReaderContext,用于存放解析时会用到的一些上下文信息
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
            this.sourceExtractor, this, getNamespaceHandlerResolver());
}
 
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        // 创建默认的 DefaultNamespaceHandlerResolver
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}
 
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
 
// DefaultNamespaceHandlerResolver.java

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
    this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
 
public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
    Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");

    this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    this.handlerMappingsLocation = handlerMappingsLocation;
}

                深入整个创建流程到DefaultNamespaceHandlerResolver的实例化,这里有个handlerMappingsLocation属性会使用默认的值 “META-INF/spring.handlers”,这个里面存储着命名空间和该命名空间handler类的映射关系,后续将会存入到handlerMappings中。

         3、registerBeanDefinitions

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    // 获取到XML的根元素:<beans/>
    Element root = doc.getDocumentElement();
    // 通过拿到的节点,注册 Bean 定义
    doRegisterBeanDefinitions(root);
}

        3、doRegisterBeanDefinitions

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    // 构建BeanDefinitionParserDelegate
    this.delegate = createDelegate(getReaderContext(), root, parent);
 
    // 校验root节点的命名空间是否为默认的命名空间(默认命名空间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);
            // 校验当前节点的 profile 是否符合当前环境定义的, 如果不是则直接跳过, 不解析该节点下的内容
            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和postProcessXml都是模板方法,默认实现是空的
    // 留给子类进行扩展,子类中可以处理一些自定义的bean
    // 并把这些bean转换为标准的Spring BeanDefinition
    preProcessXml(root);
    // 解析<beans/>下的BeanDefinition
    parseBeanDefinitions(root, this.delegate);
    // 解析后处理, 留给子类实现
    postProcessXml(root);
 
    this.delegate = parent;
}

        4、parseBeanDefinitions

        如果节点的命名空间是 Spring 默认的命名空间,则走 parseDefaultElement(ele, delegate) 方法进行解析,例如最常见的:<bean>。

        如果节点的命名空间不是 Spring 默认的命名空间,也就是自定义命名空间,则走 delegate.parseCustomElement(ele) 方法进行解析,例如常见的: <context:component-scan/>、<aop:aspectj-autoproxy/>。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 默认命名空间的处理
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        // 遍历root的子节点列表
        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)) {
                    // 默认命名空间节点的处理,例如: <bean id="test" class="" />
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        // 自定义命名空间的处理
        delegate.parseCustomElement(root);
    }
}

总结:

        1、创建BeanFactory,类型为DefaultListableBeanFactory

        2、从contextConfigLocation中读取Spring的xml配置文件,验证并封装成Resource

        3、通过Resource加载 XML 配置文件,并解析成 Document 对象

        4、拿到 Document 中的根节点,遍历根节点和所有子节点

        

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值