Spring源码之Bean容器的基本实现

前言

作为Spring源码的第一篇,首先先简单介绍Spring的整体架构


1.png
  1. Core Container(核心容器)
    它包含了Core、Beans、Context和Expression Language模块。
    Core和Beans模块是框架的基础部分,提供控制反转和依赖注入特性。基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。Core模块主要包含Spring模块基本的核心工具类,Context模块构建于Core和Beans模块基础之上,提供一种类似于JDNI注册器的框架式的对象访问方法。ApplicationContext接口是Context模块的关键。
  2. Data Access / Integration
    包含了JDBC、ORM、OXM、JMS和Transaction模块。
    ORM模块为流行的对象-关系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一个交互层。
  3. WEB
    Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。
  4. AOP
    它让你可以定义例如方法拦截器和切点,从而将逻辑代码分开,降低它们之间的耦合性。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务,通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。

Bean容器

bean是Spring中最核心的东西,因为Spring就像是个大水桶,而bean就像是容器中的水,水桶脱离了水也就没什么作用了。在这里我讲解一下利用读取XML文件进行Bean容器的基本实现。


2.png

核心类一: DefaultListableBeanFactory
XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,对于XmlBeanFactory,使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取。
现在先简单介绍上图中各个类的作用,具体的使用信息在后面的文章会讲到。

  • singletonBeanRegistry: 定义对单例的注册及获取。
  • BeanFactory: 定义获取bean及bean的各种属性。
  • DefaultSingletonBeanRegistry: 对接口SingletonBeanRegistry各函数的实现。
  • HierarchicalBeanFactory: 继承BeanFactory,增加了对parentFactory的支持。
  • BeanDefinitionRegistry: 定义对BeanDefinition的各种增删改操作。
  • ConfigurableBeanFactory: 提供配置Factory的各种方法。
  • ListableBeanFactory: 根据各种 条件获取bean的配置清单。
  • AutowireCapableBeanFactory: 提供创建bean、自动注入、初始化以及应用bean的后处理器。
    DefaultListableBeanFactory综合了上面的所有功能,主要是对bean注册后的处理。
    XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,以及注册及获取bean。
    核心类二: XmlBeanDefinitionReader
    对资源文件进行读取、解析及注册
    简单介绍该类继承的抽象类和接口
  • ResourceLoader: 定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource.
  • BeanDefinitionReader: 主要定义资源文件读取并转换为BeanDefinition的各个功能。
  • BeanDefinitionDocumentReader: 定义读取Document并注册BeanDefinition.

容器的基础XmlBeanFactory

当前获取创建beanFactory的代码如下

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

首先,读取配置文件,用ClassPathResource进行封装。
在Java中,将不同来源的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑。Spring抽象出一个统一的接口来对这些底层资源进行统一访问,即Resource.

public interface InputStreamSource { 
InputStream getInputStream() throws IOException; 
} 

public interface Resource extends InputStreamSource { 
    boolean exists(); 
    boolean isReadable(); 
    boolean isOpen(); 
    URL getURL() throws IOException; 
    URI getURI() throws IOException; 
    File getFile() throws IOException; 
    long contentLength() throws IOException; 
    long lastModified() throws IOException; 
    Resource createRelative(String relativePath) throws IOException; 
    String getFilename(); 
    String getDescription(); 
} 

对不同来源的资源文件都有相应的Resource实现:

  • 文件 FileSystemResource
  • Classpath资源 ClassPathResource
  • URL资源 UrlResource
  • InputStream资源 InputStreamResource
  • Byte数组 ByteArrayResource
  1. XmlBeanFactory loadBeanDefinitions(Resource) --> 进行资源加载
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

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


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

从上面可知,主要加载Bean的步骤

  • 封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
  • 获取输入流。从Resource中获取对应的InputStream并构造InputSource
  • 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions.
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);
            }
                           //通过属性来记录已经加载的资源
            Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
            if (currentResources == null) {
                currentResources = new HashSet<>(4);
                this.resourcesCurrentlyBeingLoaded.set(currentResources);
            }
            if (!currentResources.add(encodedResource)) {
                throw new BeanDefinitionStoreException(
                        "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
            }
            try {
                //从encodedResource中获取封装的Resource对象并再次从Resource中获取其中的inputStream
                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();
                }
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "IOException parsing XML document from " + encodedResource.getResource(), ex);
            }
            finally {
                currentResources.remove(encodedResource);
                if (currentResources.isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }
            }
        }

核心代码doLoadBeanDefinitions

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

主要步骤:

  1. 获取对XML文件的检验方式
  2. 加载XML文件,并得到对应的Document.
  3. 根据返回的Document注册Bean信息。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
           //为DefaultBeanDefinitionDocumentReader
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
           //记录统计前的BeanDefinition
            int countBefore = getRegistry().getBeanDefinitionCount();
          //加载及注册Bean
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
         //记录本次加载的BeanDefinition个数。
            return getRegistry().getBeanDefinitionCount() - countBefore;
    }

之后调用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions()

@Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
            this.readerContext = readerContext;
            logger.debug("Loading bean definitions");
            Element root = doc.getDocumentElement();
            doRegisterBeanDefinitions(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;
            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;
    }

preProcessXml和postProcessXml是两个抽象方法,是为了子类而设计的,这是设计模式的模板方法模式,如果子类想在Bean解析前后做一些处理,那么只需处理这两个方法即可。

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)) {
                            //对bean处理。
                            parseDefaultElement(ele, delegate);
                        }
                        else {
                            //对bean处理
                            delegate.parseCustomElement(ele);
                        }
                    }
                }
            }
            else {
                delegate.parseCustomElement(root);
            }
        }

如果采用Spring默认的配置,Spring当然知道怎么做,但是如果是自定义的,那么就需要用户实现一些接口及配置了。具体的元素解析在下一篇编写。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容