zoukankan      html  css  js  c++  java
  • Spring Framework框架解析(1)- 从图书馆示例来看xml文件的加载过程

    引言

    这个系列是我阅读Spring源码后的一个总结,会从Spring Framework框架的整体结构进行分析,不会先入为主的讲解IOC或者AOP的原理,如果读者有使用Spring的经验再好不过。鉴于每个人对源码阅读角度的不同,如果文中存在理解有误的地方希望读者能够及时提出,共同进步。文章所分析的源码基于5.0.8版本,但使用老版本理解起来问题也不大,因为在框架整体架构上变化并不多。

    如果你使用Spring的时间足够长,相信在最初的开发过程中你一定使用过xml文件来加载各中bean。虽然现在基本都会通过配置文件类或者注解来进行加载,但使用xml也有它的优点,这种方式对代码的侵入性最小,而且配置第三方bean也比较方便。这篇文章通过一个图书馆的例子来讲解xml最原始的加载过程,将加载过程中涉及到的各个模块比做图书馆的各个元素,希望能加深你对Spring框架的理解。

    图书馆和Spring有许多相似的地方,将图书馆比做bean工厂,从图书馆借书相当于getBean的过程,将图书馆买的书放入图书馆的过程可以类比注册bean(registerBeanDefinition)的过程,而生产图书的过程又可以类比实例化BeanDefinition的过程,是不是很相似?这里我会使用下面一段比较原始的代码来分步讲解这一过程。

    ClassPathResource resource = new ClassPathResource("applicationContext.xml");
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 
    reader.loadBeanDefinitions(resource);
    Object beanObject = factory.getBean("beanName");
    

    ClassPathResource与Resource

    ClassPathResource resource = new ClassPathResource("applicationContext.xml");
    

    这一行代码比较简单,它通过一个xml文件初始化了一个Resource,相当于对xml文件做了一个包装,方便以后将xml文件转换为BeanDefinition。可以将这一过程想象成现在的图书还是一堆木头,而将这些木头搅拌成木浆只是为了后面更方便的获取制作图书的原料而已。

    从源码角度来说ClassPathResource继承自Resource接口,是Spring中对资源的抽象,所有需要使用的资源在Spring中都被抽象为Resource,它提供了一系列操作资源的方法,比如获取资源的名称,资源是否存在等等。Resource接口又继承了InputStreamSource接口,在InputStreamSource中,提供了一个核心方法,这个方法将资源转换成InputStream方便后期操作。

    public interface InputStreamSource {
        InputStream getInputStream() throws IOException;
    }
    
    public interface Resource extends InputStreamSource {
        boolean exists();
    
        URL getURL() throws IOException;
    
        String getFilename();
    
        ......
    }
    

    BeanFactory、BeanDefinition与DefaultListableBeanFactory

    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    

    DefaultListableBeanFactory就比较重要了,它是一个Bean工厂,相当于图书馆,所有的书都在DefaultListableBeanFactory中,而借书,买书的过程都需要通过DefaultListableBeanFactory来操作。

    DefaultListableBeanFactory首先是BeanFactory接口的一个实现,BeanFactory定义了通过名称和类型获取Bean的一系列方法。

    public interface BeanFactory {
        Object getBean(String name) throws BeansException;
    
        <T> T getBean(Class<T> requiredType) throws BeansException;
    
        boolean containsBean(String name);
    
        ......
    }
    
    public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
    		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable{}
    

    其次从DefaultListableBeanFactory的定义还可以看到,它在继承BeanFactory接口的基础上,还实现了BeanDefinitionRegistry接口。BeanDefinitionRegistry的核心功能是对Bean的注册,注册是干嘛呢?通过图书馆来对比,BeanFactory的getBean相当于从图书馆借书,那么这些书是哪来的呢?就是通过BeanDefinitionRegistry的registerBeanDefinition方法,它相当于把书放入图书馆,而DefaultListableBeanFactory就相当于图书馆本身了。

    public interface BeanDefinitionRegistry extends AliasRegistry {
        void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
    
        void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
        BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
        ......
    }
    

    在BeanDefinitionRegistry的定义中还有涉及到一个关键接口:BeanDefinition,上面说BeanDefinitionRegistry相当于把书放入图书馆,那么具体图书在图书馆中怎么表示呢?这就是BeanDefinition。BeanDefinition是Bean在Spring中的抽象,也就是说每一个Bean在Spring中都对应一个BeanDefinition,它提供了与Bean相对应的属性,并提供了操作Bean的方法。

    public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
        void setBeanClassName(@Nullable String beanClassName);
    
        String getBeanClassName();
    
        void setScope(@Nullable String scope);
    
        String getScope();
    
        ......
    }
    

    BeanDefinitionReader、BeanDefinitionDocumentReader与XmlBeanDefinitionReader

    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 
    reader.loadBeanDefinitions(resource);
    

    还是通过图书馆来类比:图书馆是(DefaultListableBeanFactory),把书(BeanDefinition)放入图书馆的能力对应(BeanDefinitionRegistry),从图书馆拿编号后的书的能力对应(BeanFactory),书的原材料对应(ClassPathResource),现在就缺把书的原材料(ClassPathResource)变成一本本书(BeanDefinition),并将它放入图书馆中了。那么谁来将原材料(ClassPathResource)变成书(BeanDefinition)并放入到图书馆(DefaultListableBeanFactory)中呢?这就是XmlBeanDefinitionReader的工作了。这一过程可以通过以下源码来分析:

    public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader{}
    public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader{}
    
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }
    
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        ......
        this.registry = registry;
        ......
    }
    
    public interface BeanDefinitionReader {
        BeanDefinitionRegistry getRegistry();
    
        ResourceLoader getResourceLoader();
    
        int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
        ......
    }
    

    首先XmlBeanDefinitionReader实现了BeanDefinitionReader接口,BeanDefinitionReader定义了一个关键方法loadBeanDefinitions(Resource resource),这个方法将resource装载到BeanDefinitionRegistry中,BeanDefinitionRegistry通过XmlBeanDefinitionReader的构造方法传入。具体loadBeanDefinitions又是怎么做的呢?再来继续查看源代码:

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        ......
            //通过InputStreamSource接口的定义的getInputStream方法获取InputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                //将InputStream包装成InputSource
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //将Source装载到BeanDefinitionRegistry中
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        ......
    }
    
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        ......
        try {
            //将Source包装成Document
            Document doc = doLoadDocument(inputSource, resource);
            //将Document装载到BeanDefinitionRegistry中
            return registerBeanDefinitions(doc, resource);
        }
        ......
    }
    
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //这里创建了一个DefaultBeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
    
        //调用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法将将Document装载到BeanDefinitionRegistry中
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    

    首先将Resource转换为EncodedResource,然后通过getInputStream获取InputStream,调用doLoadBeanDefinitions方法来装载资源,在doLoadBeanDefinitions方法中,首先将Resource包装成Document方便操作元素节点,然后把解析并装载Document的功能委托了给BeanDefinitionDocumentReader,这里使用了一个默认的DefaultBeanDefinitionDocumentReader实现。那么可以想象DefaultBeanDefinitionDocumentReader中做了两件事:将Document解析为BeanDefinitions,然后将BeanDefinitions装载到BeanDefinitionRegistry中。

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        //从Document中获取到Element
        Element root = doc.getDocumentElement();
    
        //具体的解析过程
        doRegisterBeanDefinitions(root);
    }
    
    protected void doRegisterBeanDefinitions(Element root) {
        ......
        //前置解析,默认为空,可以重写
        preProcessXml(root);
        //具体的解析xml并注入到过程
        parseBeanDefinitions(root, this.delegate);
        //后置解析,默认为空,可以重写
        postProcessXml(root);
        ......
    }
    
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        ......
        parseDefaultElement(ele, delegate);
        ......
    }
    
    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);
        }
        //解析"beans"节点
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }
    
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //将元素包装成BeanDefinitionHolder,方便操作BeanDefinition
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                //具体的注入方法
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            ......
        }
    }
    
    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
        ......
        //通过BeanDefinitionRegistry将元素注入到DefaultListableBeanFactory中
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        ......
    }
    

    跟之前猜测的一样,首先通过parseBeanDefinitions方法将所有的xml节点分步解析,之后将解析后的节点包装成BeanDefinitionHolder对象,最后通过BeanDefinitionRegistry的registerBeanDefinition方法将元素注入到BeanDefinitionRegistry中。

    整个解析到注入过程非常复杂,我只列出了核心步骤,从中可以看到XmlBeanDefinitionReader是怎么一步步将xml中的Bean节点变为BeanDefinition并放入到DefaultListableBeanFactory中的。还是用图书馆来类比:首先将原材料(ClassPathResource)变成纸张(Document),然后将纸张(Document)通过书籍制造工厂(BeanDefinitionDocumentReader)组装成一本本书籍(BeanDefinition),然后书籍制造工厂(BeanDefinitionDocumentReader)将一本本书籍(BeanDefinition)送到图书馆(DefaultListableBeanFactory),而XmlBeanDefinitionReader就扮演了这一整个过程的组合功能。

    总结

    至此,整个图书馆功能就齐全了,原材料可以造书,书可以放入图书馆,并且你也可以很方便的从图书馆借书。可以说Spring设计理念也在这一过程中得到体现,它将Bean的解析,Bean的定义,Bean的生产以及Bean的获取每一步都单独抽离开来,互不干扰,最后通过DefaultListableBeanFactory将它们整合到一起供用户使用。怎么说呢,这一过程回过头来并没有什么神奇的地方,但能清晰的将每个功能都抽象出来本身就需要非常好的抽象设计能力,而对这一过程的反复阅读与分析,一定能让你在设计抽象能力上有一定的提升。

    看完后你觉得这一过程类比是否恰当呢?如果你有更贴近生活的例子,不妨留言一起探讨,共同进步。说完DefaultListableBeanFactory,在下一篇文章中将会讲讲ApplicationContext接口,并对它的部分实现类做一个简单分析。

    参考资料:

    • 《Spring揭秘》
    • 《Spring源码深度解析》
  • 相关阅读:
    [译] Python 2.7.6 标准库——详见github
    [译] Python 2.7.6 标准库——15. 通用操作系统服务
    [译] Python 2.7.6 标准库——字符串
    Spark Context初始化
    Spark启动程序:Master
    Spark 0.9.0启动脚本——bin/compute-classpath.sh
    Spark 0.9.0启动脚本——bin/spark-class
    游戏开服 报一些 ip 设置 数据格式的异常,但断点明明都是数字 没问题的
    一个不错的shell脚本学习网址-很全又很简单的课程
    国外的一个代码 仓库 github --- 里面类似一个svn 的代码仓库
  • 原文地址:https://www.cnblogs.com/konck/p/9961121.html
Copyright © 2011-2022 走看看