Spring源码系列——BeanDefinition载入(上)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Spring源码系列——BeanDefinition载入(上)

继上一篇BeanFactory的创建之后,其实就是BeanDefinition载入了。同样也是在AbstractRefreshableApplicationContext类的refreshBeanFactory方法中完成:


//创建默认的DefaultListableBeanFactory工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
//设置Id
beanFactory.setSerializationId(getId());
//这个方法其实就是设置了allowBeanDefinitionOverriding和allowCircularReferences两个属性
customizeBeanFactory(beanFactory);

//调用子类的加载bean定义方法,这里会调用XmlWebApplicationContext子类的复写方法
loadBeanDefinitions(beanFactory);

这里的loadBeanDefinitions也是一个抽象方法,AbstractRefreshableApplicationContext类中并没有给出具体的实现,二是通过子类XmlWebApplicationContext的loadBeanDefinitions完成具体实现。


protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 
        throws BeansException, IOException {
    //创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    //为XmlBeanDefinitionReader配置Environment
    beanDefinitionReader.setEnvironment(getEnvironment());
    //为XmlBeanDefinitionReader配置ResourceLoader,
    //因为DefaultResourceLoader是父类,所以this可以直接被使用
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // 允许子类提供reader的自定义初始化,然后继续实际加载bean定义。
    initBeanDefinitionReader(beanDefinitionReader);
    loadBeanDefinitions(beanDefinitionReader);
}

initBeanDefinitionReader初始化用于加载此上下文的bean定义的bean定义读取器;默认实现是空的。然后下面通过重载的loadBeanDefinitions来做具体的bean解析(这里用到的是XmlBeanDefinitionReader这个解析器);使用给定的XmlBeanDefinitionReader加载bean definitions。


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

此时会将我们的applicationContext.xml读入(当然如何还有其他的spring配置文件,同样会一块拿到路径),如下图所示:
Spring源码系列:BeanDefinition载入(上)
然后继续通过loadBeanDefinitions的重载方法继续委托调用。最后交给AbstractBeanDefinitionReader的loadBeanDefinitions来完成;这个代码比较长,拆开一步一步来说,先看下整体的:


public int loadBeanDefinitions(String location, SetResource actualResources) throws BeanDefinitionStoreException {
    //获取ResourceLoader资源定位器
    ResourceLoader resourceLoader = getResourceLoader();
    //如果定位器为null,则抛出异常
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }
    //是否是ResourcePatternResolver类型的定位器
    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);
        }
    }
    //非ResourcePatternResolver类型的
    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;
    }
}

上面的代码中需要说明下为什么要判断当前resourceLoader是否是ResourcePatternResolver类型的,因为ResourceLoader只是提供了对classpath前缀的支持。而ResourcePatternResolver提供了对classpath*前缀的支持。也就是说ResourceLoader提供classpath下单资源文件的载入,而ResourcePatternResolver提供多资源文件的载入。
先看下假如是ResourcePatternResolver类型的(略去了部分log代码):


try {
    //先得到我们的resources
    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    //解析并返回beanDefinition的数量
    int loadCount = loadBeanDefinitions(resources);
    //加载过程中已经被解析过的实际的Resource的填充集合
    if (actualResources != null) {
        for (Resource resource : resources) {
            actualResources.add(resource);
        }
    }
    return loadCount;
}
catch (IOException ex) {
    throw new BeanDefinitionStoreException(
        "Could not resolve bean definition resource pattern [" + location + "]", ex);
}

非ResourcePatternResolver类型情况:


// Can only load single resources by absolute URL.
//只能通过绝对URL加载单个资源
Resource resource = resourceLoader.getResource(location);
//解析并返回beanDefinition的数量
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
    actualResources.add(resource);
}
return loadCount;

然后继续通过重载方法loadBeanDefinitions(Resource… resources)来解析(AbstractBeanDefinitionReader类中)


public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
    Assert.notNull(resources, "Resource array must not be null");
    //初始化beanDefiniton个数
    int counter = 0;
    //遍历当前资源数组
    for (Resource resource : resources) {
        //解析具体resource中的bean
        counter += loadBeanDefinitions(resource);
    }
    return counter;
}

然后交给子类XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)方法:


public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

继续通过重载方法loadBeanDefinitions(EncodedResource encodedResource)执行,这个方法我们只关注最核心的代码:


//获取输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
    //资源读取inputSource
    InputSource inputSource = new InputSource(inputStream);
    if (encodedResource.getEncoding() != null) {
        inputSource.setEncoding(encodedResource.getEncoding());
    }
    //委托给doLoadBeanDefinitions来完成
    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
    inputStream.close();
}

doLoadBeanDefinitions是XmlBeanDefinitionReader中的方法,来看核心代码:


//解析成符合w3c标准的Document
Document doc = doLoadDocument(inputSource, resource);
//继续交给registerBeanDefinitions来处理
return registerBeanDefinitions(doc, resource);

这个时候已经将loadBeanDefinitions换成registerBeanDefinitions了,也就是载入并注册;registerBeanDefinitions同样也是XmlBeanDefinitionReader中的方法:


public int registerBeanDefinitions(Document doc, Resource resource) throws
BeanDefinitionStoreException {
    //得到documentReader用来读取document文档
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //注册之前的bean个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    //解析并注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

仍然没有处理,继续交给 BeanDefinitionDocumentReader的registerBeanDefinitions方法来完成:


//这个实现根据“spring-beans”XSD(或DTD)解析bean定义。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}

还是没处理,又细化一步,交给 DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法:


protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);
    this.delegate = parent;
}

任何嵌套的beans元素都将导致此方法的递归。 为了正确传播和保存beans default- *属性,请跟踪当前(父)委托,该委托可能为null。 为了回退的目的,创建一个引用父对象的新(子)委托,然后最终重置this.delegate回到它的原始(父)引用。这个行为模仿了一堆委托,而实际上并不需要一个委托。(翻译的有点蹩脚,大概意思就是这)

所以说 DefaultBeanDefinitionDocumentReader自己也没干这事,又给了 BeanDefinitionParserDelegate,然后就是preProcessXml()、parseBeanDefinitions()、postProcessXml()方法;其中preProcessXml()和postProcessXml()默认是空方法,自己没有实现。具体解析在parseBeanDefinitions(root, this.delegate)中完成。

BeanDefinitionParserDelegate用于将 Document 的内容转成 BeanDefinition实例; BeanDefinitionDocumentReader 本身不具备该功能而是交给了该类来完成。


protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //查看定义的命名空间是否为默认的命名空间
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i  nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

这个方法就是解析文档中根目录下的元素:“import”,“alias”,“bean”。


private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    //解析一个“import”元素,并将给定资源的bean定义加载到bean工厂中。
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    //处理给定的别名元素,向注册表注册别名。
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    //处理给定的bean元素,解析bean定义并将其注册到注册表中。
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    //在给定的根beans /元素内注册每个bean定义。
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

先来看 processBeanDefinition这个方法;

BeanDefinitionHolder是一个BeanDefinition的持有者,其定义了一下变量,并对以下变量提供get和set操作。这个在后面的说道BeanDefinition体系的时候再聊。


protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    //获取一个BeanDefinitionHolder
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        //首先根据自定义属性进行装饰。
        //基于自定义嵌套元素进行装饰。
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 注册最终装饰的实例。
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,
            getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // 发送注册事件。
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

接着看 registerBeanDefinition这个方法:通过给定的bean工厂注册给定的bean definition 。


public static void registerBeanDefinition(
    BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    throws BeanDefinitionStoreException {

    // 在主名称下注册bean定义。
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // 如果有的话,注册bean名称的别名,
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

registerBeanDefinition里面又通过调用 BeanDefinitionRegistry接口的实现 DefaultListableBeanFactory来完成具体的注册过程。关于 DefaultListableBeanFactory registerBeanDefinition方法的解析逻辑将在Spring源码系列:BeanDefinition载入(下)中来说.

Spring源码系列:BeanDefinition载入(上) `欢迎关注微信公众号`

原文始发于微信公众号(glmapper工作室):

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Spring源码系列——BeanDefinition载入(上)


 上一篇
Spring源码系列——依赖注入(一)(AbstractBeanFactory-getBean) Spring源码系列——依赖注入(一)(AbstractBeanFactory-getBean)
在Spring源码系列:BeanFactory的创建文章中我们谈到了BeanFactory这容器,这个里面提供了注入的实现接口。其具体的实现还需要从AbstractBeanFactory和DefaultListableBeanFactory
2021-04-05
下一篇 
Spring源码系列——依赖注入-引言 Spring源码系列——依赖注入-引言
在前面文章中大概分析了一下Bean的载入,其实这个过程就是在Ioc容器中建立BeanDefinition的数据映射。但是对于Bean的实例化并未涉及,在之前的分析中也提到,bean的实例化是在依赖注入是才具体完成。 关于依赖注入关于Spri
2021-04-05