Spring IoC:obtainFreshBeanFactory详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/v123411739/article/details/86563620

前言

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

 

obtainFreshBeanFactory 方法概述

该方法会解析所有配置文件,加载所有bean的定义,然后将这些 bean 的 beanName、beanName 和 bean定义映射、beanName 和别名映射放到缓存中(beanDefinitionNames、beanDefinitionMap、aliasMap),如果解析到<context:component-scan base-package="com.joonwhee.open" /> 注解时,会扫描 base-package 指定的目录,将该目录下使用注解的 bean 的相关信息加载到缓存中。所有的 bean 相关的信息(bean的定义、初始化后的bean单例等等)和相关缓存都会放到 BeanFactory 中。
 

源码解析

由于有较多方法都比较简单,在解析时会粗略带过或者跳过。

1)首先,我们从 obtainFreshBeanFactory 方法开始,跟着下面的调用链来到 AbstractRefreshableApplicationContext#refreshBeanFactory() 方法。

AbstractApplicationContext.refresh()
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
AbstractApplicationContext.obtainFreshBeanFactory()
    refreshBeanFactory();
AbstractRefreshableApplicationContext.refreshBeanFactory()

 

AbstractRefreshableApplicationContext#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);
    }
}

1.首先走到第3行:判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory。

2.接着走到第8行:正常创建一个新的BeanFactory。

3.接着走到第11行:继续主流程,加载 Bean 定义。

 

2)接着上面的流程,跟着下面的调用链来到,我们来到 XmlWebApplicationContext#loadBeanDefinitions(XmlBeanDefinitionReader reader) 方法。

AbstractRefreshableApplicationContext.refreshBeanFactory()
    loadBeanDefinitions(beanFactory);
XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
    loadBeanDefinitions(beanDefinitionReader);
XmlWebApplicationContext.loadBeanDefinitions(XmlBeanDefinitionReader reader)

 

XmlWebApplicationContext#loadBeanDefinitions(XmlBeanDefinitionReader reader) 方法

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            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.首先走到第2行,接着跳到13行,接着跳到18行:如果 configLocations 属性不为空,则返回 configLocations 的值,否则,调用 getDefaultConfigLocations 方法。在 Spring IoC:refresh前的环境准备 文中介绍过 configLocations 属性,该属性会被赋值为我们在web.xml中配置的 contextConfigLocation 的参数值,即:classpath*:config/spring/appcontext-*.xml 。

假设,web.xml 中没有配置 contextConfigLocation 参数,则会走到第27行,拿到 Spring 默认的配置路径:/WEB-INF/applicationContext.xml。

2.接着走到第5行:继续主流程,根据配置文件路径加载 Bean 定义。

 

3)接着上面的流程,跟着下面的调用链来到,我们来到 AbstractBeanDefinitionReader.loadBeanDefinitions(String location, Set<Resource> actualResources) 方法。

XmlWebApplicationContext.loadBeanDefinitions(XmlBeanDefinitionReader reader)
    reader.loadBeanDefinitions(configLocation);
AbstractBeanDefinitionReader.loadBeanDefinitions(String location)
    return loadBeanDefinitions(location, null);
AbstractBeanDefinitionReader.loadBeanDefinitions(String location, Set<Resource> actualResources)

 

AbstractBeanDefinitionReader.loadBeanDefinitions(String location, Set<Resource> actualResources) 方法

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

1.首先走到第2行:拿到 resourceLoader,这个 resourceLoader 值为 XmlWebApplicationContext。在前面走过的 XmlWebApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 方法中,有这么一行代码:beanDefinitionReader.setResourceLoader(this);,会将 resourceLoader 属性值赋为 XmlWebApplicationContext。

2.接着走到第8行:XmlWebApplicationContext类 继承了 AbstractRefreshableWebApplicationContext类,AbstractRefreshableWebApplicationContext类 实现了 ConfigurableWebApplicationContext 接口,ConfigurableWebApplicationContext 接口继承了 WebApplicationContext 接口, WebApplicationContext 接口继承了 ApplicationContext 接口,ApplicationContext 接口继承了 ResourcePatternResolver 接口。因此,XmlWebApplicationContext 是 ResourcePatternResolver 的实例,第8行判断结果为 true。

3.接着走到第11行:根据我们配置的路径:classpath*:config/spring/appcontext-*.xml,拿到该路径下所有符合的配置文件,例如下图的配置,会拿到2个配置文件:appcontext-bean.xml 和 appcontext-core.xml

4.接着走到第12行:继续主流程,根据配置文件资源加载 Bean 定义。另外,在本方法中,actualResources 为空,因此本方法除了主流程,剩下的代码就是记录下日志,没有其他操作。

 

4)接着上面的流程,跟着下面的调用链来到,我们来到 XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法。

AbstractBeanDefinitionReader.loadBeanDefinitions(String location, Set<Resource> actualResources)
    int loadCount = loadBeanDefinitions(resources);
AbstractBeanDefinitionReader.loadBeanDefinitions(Resource... resources)
    counter += loadBeanDefinitions(resource);
XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)
    return loadBeanDefinitions(new EncodedResource(resource));
XmlBeanDefinitionReader.loadBeanDefinitions(EncodedResource encodedResource)
    doLoadBeanDefinitions(inputSource, encodedResource.getResource());
XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    

 

XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        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);
    }
}

// XmlBeanDefinitionReader.java
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) {
    if (resource.isOpen()) {    // 1.2.1 校验resource是否为open stream
        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 根据resource的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);
    }
}

// XmlValidationModeDetector.java
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, 结束遍历
            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.首先走到第4行:加载xml配置文件,并得到对应的Document对象,具体涉及到的方法代码和对应解析已经在代码中注释里(可以按数字编号进行查看)。主要分为两步:

1)获取 XML 配置文件的验证模式。

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

DTD 验证模式

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

XSD 验证模式

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

2)加载 XML 文件,并得到对应的 Document 信息。

2.最后走到第5行:继续主流程,注册 Bean 定义信息。

 

5)接着上面的流程,跟着下面的调用链来到,我们来到 XmlBeanDefinitionReader.registerBeanDefinitions(Document doc, Resource resource) 方法。

XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    return registerBeanDefinitions(doc, resource);
XmlBeanDefinitionReader.registerBeanDefinitions(Document doc, Resource resource)

 

XmlBeanDefinitionReader.registerBeanDefinitions(Document doc, Resource resource) 方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 记录统计前BeanDefinition的加载个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 加载及注册Bean(核心逻辑)
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 记录本次加载的BeanDefinition个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

1.首先走到第3行:使用默认的Bean定义解析器 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader。

2.接着走到第5行:记录统计前 BeanDefinition 的加载个数,该操作主要用于第9行,求出本次加载的 BeanDefinition 个数。

3.接着走到第7行:继续主流程,注册 Bean 定义信息。此时我们来到方法:DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(Document doc, XmlReaderContext readerContext) ,这边会通过 createReaderContext(resource) 方法创建 XmlReaderContext,见代码块1详解

 

代码块1:createReaderContext(resource)

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

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

其中 namespaceHandlerResolver 会创建默认的 DefaultNamespaceHandlerResolver,DefaultNamespaceHandlerResolver的handlerMappingsLocation 属性会使用默认的值 “META-INF/spring.handlers”,并且创建完 DefaultNamespaceHandlerResolver 后,handlerMappings 属性也已经被赋值(debug源码时,第25行走完,handlerMappings 就已经有值,handlerMappings 具体怎么被赋值目前无法dubug到)。

我们知道,handlerMappings 是存放命名空间和该命名空间handler类的映射,一般在我们定义自定义注解时需要用到。

 

DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    // 提取 root 节点
    Element root = doc.getDocumentElement();
    // 注册 Bean 定义真正开始
    doRegisterBeanDefinitions(root);
}

1.首先走到第6行,拿到Document的 root 节点,对于Spring的配置文件来说,理论上 root 节点应该都是 <beans>。

2.接着走到第8行,继续主流程,开始注册 Bean 定义的真正逻辑。此时我们来到方法:DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root) 

 

DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root) 方法

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

    // 校验 root 节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
    if (this.delegate.isDefaultNamespace(root)) {
        // 处理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(root);
    // 解析并注册BeanDefinition核心逻辑
    parseBeanDefinitions(root, this.delegate);
    // 解析后处理, 留给子类实现
    postProcessXml(root);

    this.delegate = parent;
}

主要逻辑都已经在代码中注释,这边介绍下处理 profile 属性。

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

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

最后,我们从第32行的方法进去,继续主流程,解析 Bean 定义。此时我们来到方法:XmlValidationModeDetector.parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)

 

XmlValidationModeDetector.parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 方法

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 默认命名空间对bean的处理
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes(); // 拿到root的子节点列表
        // 配置文件的所有根元素都会在这边被解析,存放到缓存中去
        // 遍历root的子节点列表
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                // spring默认的命令空间,例如节点: <bean id="test" class="" />
                if (delegate.isDefaultNamespace(ele)) {
                    // 默认命名空间的bean的处理(核心逻辑)
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 自定义命名空间的bean的处理,例如节点:<context:annotation-config />、<context:component-scan>
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        // 自定义命名空间对bean的处理
        delegate.parseCustomElement(root);
    }
}

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

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

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

 

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

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

 

parseDefaultElement(ele, delegate) 和 delegate.parseCustomElement(ele) 方法是解析 Bean定义的两个核心方法,之后的文章将分别介绍这两个方法。

 

相关文章

Spring IoC:源码学习总览

Spring IoC:refresh前的环境准备

 

没有更多推荐了,返回首页