Spring IoC源码学习:obtainFreshBeanFactory 详解

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 接下来是 obtainFreshBeanFactory 方法,该方法用于获得一个新的 BeanFactory,本文将详细介绍该方法。

目录

Spring IoC源码学习全系列

前言

obtainFreshBeanFactory 方法概述

正文

代码块1refreshBeanFactory 方法

代码块2loadBeanDefinitions

代码块3loadBeanDefinitions

代码块4loadBeanDefinitions

代码块5loadBeanDefinitions

方法块6loadBeanDefinitions

代码块7doLoadBeanDefinitions

代码块8doLoadDocument

代码块9registerBeanDefinitions

代码块10createReaderContext

代码块11registerBeanDefinitions

代码块12doRegisterBeanDefinitions

代码块13

代码块14parseBeanDefinitions

总结

相关文章


Spring IoC源码学习全系列


小白也看得懂的 Spring IoC 核心流程介绍

Spring IoC源码学习:总览

Spring IoC源码学习ApplicationContext 刷新前的配置

Spring IoC源码学习obtainFreshBeanFactory详解

Spring IoC源码学习parseDefaultElement详解

Spring IoC源码学习parseCustomElement详解

Spring IoC源码学习:context:component-scan节点详解

Spring IoC源码学习invokeBeanFactoryPostProcessors详解

Spring IoC源码学习registerBeanPostProcessors详解

Spring IoC源码学习finishBeanFactoryInitialization详解

Spring IoC源码学习getBean详解

Spring IoC源码学习createBean详解(上)

Spring IoC码学习createBean详解(下)

Spring IoC源码学习:@Autowire详解

Spring IoC源码学习:finishRefresh详解

 

前言


上文Spring IoCApplicationContext刷新前的配置介绍了 refresh方法前的环境准备操作,接下来正式进入 refresh 方法。prepareRefresh refresh 里的第一个方法,主要是一些准备工作,比较简单的方法,看一下就了解了。接下来是 obtainFreshBeanFactory 方法,该方法用于获得一个新的 BeanFactory,本文将详细介绍该方法。

 

obtainFreshBeanFactory 方法概述


该方法会解析所有 Spring 配置文件(通常我们会放在 resources 目录下),将所有 Spring 配置文件中的 bean 定义封装成BeanDefinition,加载到 BeanFactory 中。常见的,如果解析到<context:component-scan base-package="" /> 注解时,会扫描 base-package 指定的目录,将该目录下使用指定注解(@Controller@Service@Component@Repository)的bean 定义也同样封装成 BeanDefinition,加载到 BeanFactory 中。


上面提到的加载到 BeanFactory 的内容主要指的是添加到以下3个缓存:


·       beanDefinitionNames缓存:所有被加载到 BeanFactory 中的 bean beanName 集合。

·       beanDefinitionMap缓存:所有被加载到 BeanFactory 中的 bean beanName BeanDefinition 映射。

·       aliasMap缓存:所有被加载到 BeanFactory 中的 bean beanName 和别名映射。

 

正文


首先,我们来到 obtainFreshBeanFactory 方法。


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

1.刷新 BeanFactory,由AbstractRefreshableApplicationContext 实现,见代码块1详解

 

代码块1refreshBeanFactory 方法


@Override
protected final void refreshBeanFactory() throws BeansException {
    // 1.判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 2.创建一个新的BeanFactory
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        // 3.加载 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);
    }
}

3.加载 bean 定义,由 XmlWebApplicationContext 实现,见代码块2详解

 

代码块2loadBeanDefinitions

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    // 1.为指定BeanFactory创建XmlBeanDefinitionReader
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    // Configure the bean definition reader with this context's
    // resource loading environment.
    // 2.使用此上下文的资源加载环境配置 XmlBeanDefinitionReader
    beanDefinitionReader.setEnvironment(getEnvironment());
    // resourceLoader赋值为XmlWebApplicationContext
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    initBeanDefinitionReader(beanDefinitionReader);
    // 3.加载 bean 定义
    loadBeanDefinitions(beanDefinitionReader);
}

3.加载 bean 定义,见代码块3详解

 

代码块3loadBeanDefinitions


protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    // 1.获取配置文件路径
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            // 2.根据配置文件路径加载 bean 定义
            reader.loadBeanDefinitions(configLocation);
        }
    }
}
// AbstractRefreshableWebApplicationContext.java
@Override
public String[] getConfigLocations() {
    return super.getConfigLocations();
}
// AbstractRefreshableConfigApplicationContext.java
protected String[] getConfigLocations() {
    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};
    }
}

1.获取配置文件路径:如果 configLocations 属性不为空,则返回 configLocations 的值;否则,调用 getDefaultConfigLocations() 方法。在Spring IoCApplicationContext刷新前的配置文中介绍过configLocations 属性,该属性会被赋值为我们在web.xml中配置的contextConfigLocation 的参数值,例如下图即为:classpath*:config/spring/appcontext-*.xml

image.png

如果web.xml 中没有配置 contextConfigLocation 参数,则会拿到Spring 默认的配置路径:/WEB-INF/applicationContext.xml

2.根据配置文件路径加载 bean 定义,见代码块4详解

 

代码块4loadBeanDefinitions


@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // 1.获取 resourceLoader,这边为 XmlWebApplicationContext
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }
    // 2.判断 resourceLoader 是否为 ResourcePatternResolver 的实例
    if (resourceLoader instanceof ResourcePatternResolver) {
        // Resource pattern matching available.
        try {
            // 2.1 根据路径拿到该路径下所有符合的配置文件,并封装成Resource
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            // 2.2 根据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 {
        // Can only load single resources by absolute URL.
        // 3.只能通过绝对URL加载单个资源
        Resource resource = resourceLoader.getResource(location);
        // 3.1 根据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;
    }
}

1.获取 resourceLoader:这个 resourceLoader 值为 XmlWebApplicationContext。在上文代码块2中会将 resourceLoader 属性值赋为 XmlWebApplicationContext


2.判断 resourceLoader 是否为 ResourcePatternResolver 的实例:这边resourceLoader   XmlWebApplicationContext,而 XmlWebApplicationContext 的继承关系如下图,可以看到是有实现 ResourcePatternResolver 接口的,因此这边判断结果为 true

image.png


2.1 根据路径拿到该路径下所有符合的配置文件,并封装成Resource。如果我们配置的路径为:classpath*:config/spring/appcontext-*.xml,并且项目配置如下图,则该方法会拿到2个配置文件:appcontext-bean.xml appcontext-core.xml

image.png

 

2.2 根据 Resource,加载bean 定义,见代码块5详解

 

代码块5loadBeanDefinitions


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

2.根据 Resource 加载 bean 定义,由XmlBeanDefinitionReader 实现,见代码块6详解

 

方法块6loadBeanDefinitions


@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    // 加载 bean 定义
    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());
    }
    // 1.当前正在加载的EncodedResource
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    // 2.将当前encodedResource添加到currentResources
    if (!currentResources.add(encodedResource)) {
        // 如果添加失败,代表当前的encodedResource已经存在,则表示出现了循环加载
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        // 3.拿到Resource的inputStream
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            // 4.将inputStream封装成org.xml.sax.InputSource
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // 5.加载 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();
        }
    }
}

5.加载 bean 定义,方法以 do 开头,真正处理的方法,见代码块7详解

 

代码块7doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        // 1.根据inputSource和resource加载XML文件,并封装成Document
        Document doc = doLoadDocument(inputSource, resource);
        // 2.根据返回的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);
    }
}

1.根据 inputSource resource 加载XML文件,并封装成 Document见代码块8详解

2.根据返回的 Document 注册 bean 信息,见代码块9详解

 

代码块8doLoadDocument


protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    // 1.getValidationModeForResource(resource): 获取XML配置文件的验证模式
    // 2.documentLoader.loadDocument: 加载XML文件,并得到对应的 Document
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}
protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    // 1.1 如果手动指定了XML文件的验证模式则使用指定的验证模式
    if (validationModeToUse != VALIDATION_AUTO) {
        return validationModeToUse;
    }
    // 1.2 如果未指定则使用自动检测
    int detectedMode = detectValidationMode(resource);
    // 1.3 如果检测出的验证模式不为 VALIDATION_AUTO, 则返回检测出来的验证模式
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    // Hmm, we didn't get a clear indication... Let's assume XSD,
    // since apparently no DTD declaration has been found up until
    // detection stopped (before finding the document's root tag).
    // 1.4 如果最终没找到验证模式,则使用 XSD
    return VALIDATION_XSD;
}
protected int detectValidationMode(Resource resource) {
    // 1.2.1 校验resource是否为open stream
    if (resource.isOpen()) {
        throw new BeanDefinitionStoreException(
                "Passed-in Resource [" + resource + "] contains an open stream: " +
                        "cannot determine validation mode automatically. Either pass in a Resource " +
                        "that is able to create fresh streams, or explicitly specify the validationMode " +
                        "on your XmlBeanDefinitionReader instance.");
    }
    InputStream inputStream;
    try {
        // 1.2.2 校验resource是否可以打开InputStream
        inputStream = resource.getInputStream();
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
                        "Did you attempt to load directly from a SAX InputSource without specifying the " +
                        "validationMode on your XmlBeanDefinitionReader instance?", ex);
    }
    try {
        // 1.2.3 根据inputStream检测验证模式
        return this.validationModeDetector.detectValidationMode(inputStream);
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                resource + "]: an error occurred whilst reading from the InputStream.", ex);
    }
}
public int detectValidationMode(InputStream inputStream) throws IOException {
    // Peek into the file to look for DOCTYPE.
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    try {
        boolean isDtdValidated = false;
        String content;
        // 1.2.3.1 按行遍历xml配置文件,获取xml文件的验证模式
        while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            // 如果读取的行是空或者注释则略过
            if (this.inComment || !StringUtils.hasText(content)) {
                continue;
            }
            // 内容包含"DOCTYPE"则为DTD,否则为XSD
            if (hasDoctype(content)) {
                isDtdValidated = true;
                break;
            }
            // 如果content带有 '<' 开始符号,则结束遍历。因为验证模式一定会在开始符号之前,所以到此可以认为没有验证模式
            if (hasOpeningTag(content)) {
                // End of meaningful data...
                break;
            }
        }
        // 1.2.3.2 根据遍历结果返回验证模式是 DTD 还是 XSD
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    } catch (CharConversionException ex) {
        // Choked on some character encoding...
        // Leave the decision up to the caller.
        return VALIDATION_AUTO;
    } finally {
        reader.close();
    }
}
// DefaultDocumentLoader.java
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    // 2.1 创建DocumentBuilderFactory
    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isDebugEnabled()) {
        logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    // 2.2 通过DocumentBuilderFactory创建DocumentBuilder
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    // 2.3 使用DocumentBuilder解析inputSource返回Document对象
    return builder.parse(inputSource);
}


1.获取 XML 配置文件的验证模式。XML 文件的验证模式是用来保证 XML 文件的正确性,常见的验证模式有两种:DTD XSD,以下简单展示下这两种验证模式的配置。


DTD 验证模式(已停止更新)

要使用DTD 验证模式的时候需要在 XML 文件的头部声明,以下是在 Spring 中使用DTD 声明方式的代码:

image.png

XSD 验证模式

image.png


Spring源码中可以看到,dtd 验证模式已经停止更新,因此目前使用的验证模式基本上是 XSD

image.png

 

代码块9registerBeanDefinitions


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

3.根据 resource 创建一个 XmlReaderContext见代码块10详解

4.加载及注册 bean 定义,由DefaultBeanDefinitionDocumentReader 实现,见代码块11详解

 

代码块10createReaderContext


public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
            this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
// DefaultNamespaceHandlerResolver.java
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;
}

这边会根据 resource构建一个 XmlReaderContext,用于存放解析时会用到的一些上下文信息。


其中namespaceHandlerResolver 会创建默认的 DefaultNamespaceHandlerResolverDefaultNamespaceHandlerResolverhandlerMappingsLocation属性会使用默认的值 “META-INF/spring.handlers”,并且这边有个重要的属性 handlerMappingshandlerMappings 用于存放命名空间和该命名空间handler类的映射,如下图:

image.png

handlerMappings 的值默认位于“META-INF/spring.handlers” 的值默认位

image.png


代码块11registerBeanDefinitions


@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    // 1.拿到文档的子节点,对于Spring的配置文件来说,理论上应该都是<beans>
    Element root = doc.getDocumentElement();
    // 2.通过拿到的节点,注册 Bean 定义
    doRegisterBeanDefinitions(root);
}

2.通过拿到的节点,注册 bean 定义,见代码块12详解

 

代码块12doRegisterBeanDefinitions


protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    // 构建BeanDefinitionParserDelegate
    this.delegate = createDelegate(getReaderContext(), root, parent);
    // 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
    if (this.delegate.isDefaultNamespace(root)) {
        // 2.处理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;
            }
        }
    }
    // 3.解析前处理, 留给子类实现
    preProcessXml(root);
    // 4.解析并注册bean定义
    parseBeanDefinitions(root, this.delegate);
    // 5.解析后处理, 留给子类实现
    postProcessXml(root);
    this.delegate = parent;
}

2.处理 profile 属性,见代码块13详解

4.解析并注册 bean 定义,见代码块14详解

 

代码块13


profile 属性主要用于多环境开发,例如下图:

image.png

我们可以在配置文件中同时写上多套配置来适用于开发环境、测试环境、生产环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。具体使用哪个环境在 web.xml 中通过参数 spring.profiles.active 来配置。

image.png

 

代码块14parseBeanDefinitions


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

最终,我们来到了解析 bean 定义的核心部分,这边会遍历 root 节点(正常为<beans> 节点)下的所有子节点,对子节点进行解析处理。


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


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

parseDefaultElement(ele, delegate) delegate.parseCustomElement(ele) 方法是解析bean 定义的两个核心方法,限于篇幅,将在之后的文章将分别介绍这两个方法

 

如何判断默认命名空间还是自定义命名空间?


默认的命名空间为:http://www.springframework.org/schema/beans,其他都是自定义命名空间,例如下图 aop 的命名空间为:http://www.springframework.org/schema/aop

image.png


总结


本文主要介绍了加载 bean 定义的一些基本工作:

·       创建一个新的 BeanFactoryDefaultListableBeanFactory

·       根据 web.xml contextConfigLocation 配置的路径,读取 Spring 配置文件,验证配置文件的内容,并封装成 Resource

·       根据 Resource 加载XML 配置文件,并解析成 Document 对象

·       拿到 Document 中的根节点,遍历根节点和所有子节点。

核心的节点解析将在之后的文章单独介绍。

 

相关文章


Spring IoC:源码学习总览

Spring IoCApplicationContext刷新前的配置

相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
86 2
|
5天前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
94 69
|
3天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
35 21
|
9天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
8天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
29天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
51 2
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
69 9
|
2月前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
35 0
|
2月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
45 0
|
8月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
105 1