Spring IoC源码学习:obtainFreshBeanFactory 详解

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 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刷新前的配置

相关文章
|
3天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
24 9
|
4天前
|
JavaScript Java 关系型数据库
自主版权的Java诊所管理系统源码,采用Vue 2、Spring Boot等技术栈,支持二次开发
这是一个自主版权的Java诊所管理系统源码,支持二次开发。采用Vue 2、Spring Boot等技术栈,涵盖患者管理、医生管理、门诊管理、药店管理、药品管理、收费管理、医保管理、报表统计及病历电子化等功能模块。
|
4天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
14 1
|
9天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
9天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
9天前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
27 2
|
9天前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
24 1
|
9天前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
13 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
9天前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
12 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
9天前
|
Java 关系型数据库 MySQL
springboot学习五:springboot整合Mybatis 连接 mysql数据库
这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
14 0
springboot学习五:springboot整合Mybatis 连接 mysql数据库