spring容器的基本实现-bean的注册

容器的基本用法

我们来先看看bean的定义:

public class MyTestBean {
  private String testStr = "testStr";
  public String getTestStr() {
   return testStr;
 }
  public void setTestStr(String testStr) {
   this.testStr = testStr;
 }
}

bean并没有任何特别之处,就是一个纯粹的POJO,我们再来看看配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.Springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www. Springframework.
 org/schema/beans/Spring-beans.xsd">
  <bean id="myTestBean" class="bean.MyTestBean"/>
</beans>

在上面的配置中我们已经看到了bean的声明方式,spring的入门示例就是这样的了,接着我们写代码来测试一下

@SuppressWarnings("deprecation")
public class BeanFactoryTest {
 @Test
  public void testSimpleLoad(){
   BeanFactory bf = new XmlBeanFactory(new ClassPathResource ( "beanFactoryTest.xml"));
   MyTestBean bean=(MyTestBean) bf.getBean("myTestBean");
  assertEquals("testStr",bean.getTestStr());
 }
}

利用xmlBeanFactory解析xml文件,并将bean注入到bean工厂中,这样我们就可以直接使用getBean()方法来获取到具体的bean实例了;下面我们将分析,spring是如何解析xml,并将对应的实例注入到bean工厂里的。

功能分析

有经验的读者可以很清晰的分析到,无非就一下几点

  1. 读取配置文件beanFactoryTest.xml
  2. 根据配置文件beanFactoryTest.xml中的配置找到对应的类的配置,并实例化
  3. 调用实例化后的实例

作为一个世界级的优秀作品,里面的实现逻辑肯定没有这么简单,以上只是一个简单的开场,让大家了解一下spring的大致面貌,下面我们将深入去分析org.springframework.beans.jar 这个包下面的具体实现。

为避免篇幅过长,本片只阐述解析xml的过程及注册beanDefinitions的过程。

核心类介绍

先看一下整体的结构,主要是是集中的factory下面:

下面来介绍一下最核心的两个类,先大致了解一下其作用,然后我们再去分析

1. DefaultListableBeanFactory

xmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是spring注册及加载bean的默认实现。XmlBeanFactory使用了使用了自动以的Xml自定义读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory以及BeanDefinitionRegistry接口。

 

先来看看DefaultListableBeanFactory的UML图:

下面我们来看一下每个类具体的作用:

  • AliasRegistry:定义对alias简单的增删改查
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
  • SingletonBeanRegistry:定义对单例的注册以及获取
  • BeanFactory:定义获取bean以及bean的各种属性
  • BeanDefinitionRegistry:定义beanDefinition的各种增删改查操作
  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry的各种函数实现
  • HierachicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能至上增加了对parentFactory的支持
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加对FactoryBean的特殊处理功能
  • ConfigurableBeanFactory:提供配置Factory的各种方法
  • ListableBeanFactory:根据各种条件获取bean的配置清单
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能
  • AutowireCapableBeanFactory:提供创建bean、自动注入、初始化已经应用bean的后置处理器
  • AbstractAutowireCapableBeanFactory:综合对AbstractBeanFactory并对AutowireCapableBeanFactory进行实现
  • ConfigurableListableBeanFactory:BeanFactory的配置清单,指定合理类型及接口等
  • DefaultListableBeanFactory:综合上面所有的功能,主要对Bean注册后的处理

XmlBeanFactoryDefaultListableBeanFactory进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用父类继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性,看代码

public class XmlBeanFactory extends DefaultListableBeanFactory {

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	/**
	 * Create a new XmlBeanFactory with the given resource,
	 * which must be parsable using DOM.
	 * @param resource XML resource to load bean definitions from
	 * @throws BeansException in case of loading or parsing errors
	 */
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	/**
	 * Create a new XmlBeanFactory with the given input stream,
	 * which must be parsable using DOM.
	 * @param resource XML resource to load bean definitions from
	 * @param parentBeanFactory parent bean factory
	 * @throws BeansException in case of loading or parsing errors
	 */
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}

}

分析这么一大堆类的功能,主要还是为了引出今天的主角XmlBeanDefinitionReader

2. XmlBeanDefinitionReader

XML配置文件的读取是Spring中最重要的功能,因为Spring大部分功能都是基于配置切入的,那么我们基于XmlBeanDefinitionReader来梳理一下资源文件的读取解析及注册的大致脉络,同样,需要先来看UML图以及各个类的功能。

  • BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能
  • EnvironmentCapable:定于获取Environment的方法
  • AbstractBeanDefinitionReader:对EnvironmentCapable和BeanDefinitionReader定义功能的实现
  • ResourceLoader:定义资源加载器,主要用于根据给定的资源文件地址返回对应的Resource
  • DocumentLoader:定义从资源文件加载到转为Document的功能
  • BeanDefinitionParserDelegate:定义document中各种Element的解析方法

下面三个类是XmlBeanDefinitionReader使用到的类,并不在继承和实现结构里面,所以我用其它颜色标出。那XmlBeanDefinitionReader主要包含以下几个处理步骤:

  1. 通过继承自AbstractBeanDefinitionReader重的方法,来使用ResourceLoader将资源文件转为对应的Resource文件
  2. 通过DocumentLoader对资源文件进行转换,将Resource文件转换为Document文件
  3. 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析

容器的基础XmlBeanFactory

到这里我们一家对Spring容器有一个基本的了解了,尽管你会感到很模糊,但是不要紧,我们来逐步分析一下

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"))

这一行的代码逻辑时先使用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续就可以使用Resource提供的各种服务来操作了,然后就可以进行XmlBeanFactory的初始化了。关于如何从一个文件获取Resource资源文件,这里暂不详细描述。一句话概述:在java中,不同来源的资源可以抽象成URL,通过注册不同的处理器来处理不同来源的资源的读取逻辑,而Spring对其内部使用的资源实现了自己的抽象结构,并使用Resource接口来封装底层资源。

加载Bean

之前我们提到的XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader的loadBeanDefinitions方法,

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
}

这句代码时整个切入点的核心,我们一同来分析一下,先来看一下这个方法loadBeanDefinitions(resource)的时序图

上图不需要看懂,只是为了让你看看什么叫做山路十八弯,从上面的时序图,我们了解到

  1. 封装资源文件,当进入到XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装
  2. 获取输入流,从Resource中获取对应的InputStream并构造InputSource
  3. 通过构造函数的InputSource和Resource实例继续调用函数doLoadBeanDefinitions。

真正核心的部分在于doLoadBeanDefinitions方法,部分代码如下

InputStream inputStream = encodedResource.getResource().getInputStream();
try {
    InputSource inputSource = new InputSource(inputStream);
    if (encodedResource.getEncoding() != null) {
        inputSource.setEncoding(encodedResource.getEncoding());
    }
    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
    inputStream.close();
}

我们一起来看这个方法做了啥

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
    try {
        // 解析获取到文档
        Document doc = doLoadDocument(inputSource, resource);
        // 注册BeanDefinition
        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);
    }
}

不考虑异常catch的话,上面代码只做了2件事,每件都是必不可少

  1. 加载XML文件,并得到对应的Document
  2. 根据返回的document注册Bean信息

解析及注册BeanDefinition

继续上面的分析,当获取到了XML文件的Document实例对象之后,就会引入registerBeanDefinitions(doc, 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;
}

进入到createBeanDefinitionDocumentReader方法中,发现真正的实例化类型是DefaultBeanDefinitionDocumentReader,进入这个类后,发现registerBeanDefinitions这个方法主要就是提取root,以便于以后再将root作为参数继续BeanDefinition的注册。

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}

经历艰难险阻,我们终于到了核心逻辑底层doRegisterBeanDefinitions方法,我们期待的地方才刚刚开始

protected void doRegisterBeanDefinitions(Element root) {
    // 专门解析处理
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

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

通过看到上面,大家可能会疑惑,什么是profile属性呢?

简单解释一下:通过profile标记不同的环境,可以通过设置spring.profiles.active和spring.profiles.default激活指定profile环境。如果设置了active,default便失去了作用。如果两个都没有设置,那么带有profiles的bean都不会生成。

profile其实就是我们日常开发环境下面的各种配置,dev、test、prod,这样区分开了之后,比如数据库就能按照不同的环境来配置不同的链接。

处理完了profile就可以进行XML的读取了,请看parseBeanDefinitions的实现

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 对beans的处理
    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)) {
                    // 对bean的处理
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 对bean的处理
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        // 自定义的bean解析
        delegate.parseCustomElement(root);
    }
}

上面的代码逻辑很清晰,因为在Spring的XML配置中有两大类Bean声明,一个是默认的,如

<bean id="test" class="test.TestBean"/>

另一个就是自定义的,如

<tx:annotation-driven>

具体判定方法是获取命名空间namespace后与spring中固定的命名空间进行对比,感兴趣可以自行了解。

再看parseDefaultElement方法

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 对import标签的处理
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // 对alias标签的处理
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // 对bean标签的处理
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // 对bean标签的处理
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

这里是具体对XML中标签的处理,通过解析bean标签,来实现bean的注册。但是bean标签的解析也是最复杂的,下面我们进入函数processBeanDefinition

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 委托BeanDefinitionParserDelegate的parseBeanDefinitionElement方法进行元素的解析,经过这个方法后
    // bdHolder实例已经包含了我们配置文件中的各种属性了,例如class、name、id、alias等
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 若bdHolder != null的情况下,如存在默认标签的子节点下再有自定义属性,还需再次对自定义标签的解析
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            // 解析完成后,委托BeanDefinitionReaderUtils进行bean的注册
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        // 发出响应事件,通知相关监听器,这个bean已经完成了加载
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

大致逻辑总结一下:

 

  1. 委托BeanDefinitionParserDelegate的parseBeanDefinitionElement方法进行元素的解析,经过这个方法后, bdHolder实例已经包含了我们配置文件中的各种属性了,例如class、name、id、alias等
  2. 若bdHolder != null的情况下,如存在默认标签的子节点下再有自定义属性,还需再次对自定义标签的解析
  3. bdHolder解析完成后,委托BeanDefinitionReaderUtils进行bean的注册
  4. 最后发出响应事件,通知相关监听器,这个bean已经完成了加载

这个bean的加载过程已经完成了,比较重要的方法parseBeanDefinitionElement,这个方法就像剥洋葱一样,一层一层的将xml的标签解析,并将数据全部生成到BeanDefinitionHolder中, 然后使用BeanDefinitionReaderUtils将其注册到BeanDefinitionRegistry中,来看代码

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

    // Register bean definition under primary name.
    // 使用beanName做唯一的标识
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

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

到这里,bean已经注册到BeanDefinitionRegistry中了,注册bean 的方式也有两种,第一是通过beanName注册registerBeanDefinition

,第二是通过别名注册registerAlias。

1. 通过beanName注册BeanDefinition

对于BeanDefinition的注册,这里很多人都会认为,就是将BeanDefinition放入map中,并将BeanName作为key。确实,Spring就是这样做的,只不过除此之外,它还做了一些其他事情,主要代码如下

// 进行合法性的校验
if (beanDefinition instanceof AbstractBeanDefinition) {
    try {
        ((AbstractBeanDefinition) beanDefinition).validate();
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                "Validation of bean definition failed", ex);
    }
}
// 对bean的一些异常情况的处理
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
    if (!isAllowBeanDefinitionOverriding()) {
        throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                "': There is already [" + existingDefinition + "] bound.");
    }
    
    // 加入到map缓存
    this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
    if (hasBeanCreationStarted()) {
        // Cannot modify startup-time collection elements anymore (for stable iteration)
        synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            if (this.manualSingletonNames.contains(beanName)) {
                Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                updatedSingletons.remove(beanName);
                this.manualSingletonNames = updatedSingletons;
            }
        }
    }
    else {
        // Still in startup registration phase
        this.beanDefinitionMap.put(beanName, beanDefinition);
        this.beanDefinitionNames.add(beanName);
        this.manualSingletonNames.remove(beanName);
    }
    this.frozenBeanDefinitionNames = null;
}

if (existingDefinition != null || containsSingleton(beanName)) {
    resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
    // 清除加载过程中留下的缓存
    clearByTypeCache();
}

总结一下流程

  1. 对AbstractBeanDefinition的校验
  2. 对Bean已经注册的情况进行处理,如果设置了不允许bean的覆盖,则抛出异常,否则直接覆盖
  3. 加入map缓存
  4. 清除之前留下的BeanName的缓存

2. 通过别名注册BeanDefinition

理解了以上的逻辑,再看这个流程就比较简单了,

public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    synchronized (this.aliasMap) {
        if (alias.equals(name)) {
            this.aliasMap.remove(alias);
            if (logger.isDebugEnabled()) {
                logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
            }
        }
        else {
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    // An existing alias - no need to re-register
                    return;
                }
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding alias '" + alias + "' definition for registered name '" +
                            registeredName + "' with new target name '" + name + "'");
                }
            }
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
            if (logger.isDebugEnabled()) {
                logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");
            }
        }
    }
}

流程如下:

  1. alias与beanName相同情况下,则不需要处理直接删除掉原有的alias
  2. alias覆盖处理,若aliasName已经使用并指向另一个beanName则需要用户的设置进行处理
  3. alias循环检查,当A->B存在时,若再次出现A->C->B则会抛出异常
  4. 注册alias

以上就是整个bean加载的过程了,其实早期使用Spring的时候还是能很清楚的看到XML文件的配置,随着Spring的迅速发展,在越来越傻瓜式的简单操作下,sprign封装了大量的逻辑在里面,当然也导致我们对底层的细节越来越陌生。如果你想了解更多关于spring的原生知识的话,欢迎留言或者私信。本期内容就这些,下期我们来讲bean的加载。

觉得可以辛苦一键三连,看客老爷万福金安!!!

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周润发的弟弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值