最近在读Spring的源码,参考的是郝佳的《Spring源码深度解析》,这里把一些学习心得分享一下,总结的地方可能还有一些不完善,希望大家指教

IoC(控制反转)是Spring的特性之一,在传统的程序中,对象的生成往往是有开发者自己来完成的,而在Spring中,这一工作交由了Spring框架完成。Spring容器可以帮助我们管理所有的bean。这样的好处是减少了对象之间的耦合。

<?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 = "myFirstBean" class="com.wuzhe.bean.TestBean" />
</beans>

在上述代码中我们定义了对于一个简单的bean的声明。Spring为我们提供了丰富的属性来支持我们各种业务的需求,但只要我们声明成这样就足够满足我们大多数的应用了。

为了能在程序中使用这个在xml声明的bean,我们可以通过如下方式:

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));
        TestBean bean = (TestBean) bf.getBean("myFirstBean");

当然在我们的程序中直接使用BeanFactory作为容器其实并不多见,我们绝大多数情况下会使用ApplicationContext作为Spring的容器,这不妨碍我们分析它的原理

这段代码实现的功能实际上就是如下三点:

1、读取配置文件

2、根据配置文件中的信息,找到对应的类的配置,并实例化

3、调用实例化后的类

对于源码的阅读,书的作者提供了一个好的方法,就是配合UML图去分析,我觉得这个方法挺好。

 

Spring容器完全启动完成后_XML

Spring容器完全启动完成后_封装_02

1、我们先从读取配置文件来看。

Spring的配置文件是通过ClassPathResource进行封装的。

inputStreamSource 封装了所有能返回InputStream的类,Resource接口则抽象了所有资源的一些公共特性,提供了如下的接口

boolean exists();

    default boolean isReadable() {
        return true;
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();

对于不同来源的资源文件都有相应的Resource实现。我觉得在Spring框架的源码中对于已有类进行封装特别的常见。这实际上也是符合我们面对对象编程的对修改封闭,对扩展开放的这个原则。同时通过一层层的封装,把原本复杂的逻辑分摊到每一层去实现,这样更符合单一职责的设计理念,提高了可读性。

 

封装好了配置信息,XmlBeanFactory首先会对封装后的ClassPathResource进行读取

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

配置文件的读取工作交给了XmlBeanDefinitionReader来处理,通过调用XmlBeanDefinitionReader的loadBeanDefinitions(Resource resource)方法来加载资源

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

在这里对于Resource又进行了一层EncodedResource封装,主要是设置了Resource资源的编码和字符集

在看loadBeanDefinitions(EncodedResource encodedResource)的代码

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }

        if (!((Set)currentResources).add(encodedResource)) {
            throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        } else {
            int var5;
            try {
                InputStream inputStream = encodedResource.getResource().getInputStream();

                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }

                    var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                } finally {
                    inputStream.close();
                }
            } catch (IOException var15) {
                throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
            } finally {
                ((Set)currentResources).remove(encodedResource);
                if (((Set)currentResources).isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }

            }

            return var5;
        }
    }

在XmlBeanDefinitionReader中维护了一套当前线程中已加载资源的集合resourcesCurrentlyBeingLoaded,真正的核心处理部分在doLoadBeanDefinitions方法中

 

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            Document doc = this.doLoadDocument(inputSource, resource);
            return this.registerBeanDefinitions(doc, resource);
        } catch (BeanDefinitionStoreException var4) {
            throw var4;
        } catch (SAXParseException var5) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
        } catch (SAXException var6) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
        } catch (ParserConfigurationException var7) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
        } catch (IOException var8) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
        } catch (Throwable var9) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
        }
    }

 

在doLoadDocument方法中使用了DefaultDocumentLoader来加载资源,对生成的Document进行校验和解析。

拿到了转换后的Document后,接下来spring会对注册对应的BeanDefinition

 

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
        documentReader.setEnvironment(this.getEnvironment());
        int countBefore = this.getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
    }

在上面这段代码中,spring将处理Document的逻辑委托给了BeanDefinitionDocumentReader来处理,具体逻辑在 registerBeanDefinitions中

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

这里获取了Document的根节点,开头的配置文件中对应的也就是beans节点。继续深入

protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
        if(this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute("profile");
            if(StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    return;
                }
            }
        }

        this.preProcessXml(root);
        this.parseBeanDefinitions(root, this.delegate);
        this.postProcessXml(root);
        this.delegate = parent;
    }

这里首先对根节点的profile属性做了处理,profile属性实际上对应了我们的开发环境,通常企业的应用会有多种环境,比如生产环境,测试环境,如果想在同一套配置中维护多套环境,就可以用到这个参数,最常用的就是更换不同环境的数据库。在解析时会判断当前的环境和profile属性是否一致,若不一致则不用去解析当前的节点。

处理完profile属性后,到了真正解析BeanDefinition的阶段了,在这里用到了设计模式的模板方法,将整个解析过程抽象成了三个阶段解析前,解析中,解析后。这样做的好处是方便扩展,如果你想要做扩展的话,只需要继承当前类,并覆盖一下模板方法即可。

我们继续跟踪代码到parseBeanDefinitions()方法中

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)) {
                        this.parseDefaultElement(ele, delegate);
                    } else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }

spring的配置文件中总共有两种标签,一种是默认标签,还有一种是自定义标签。对于两种标签,spring的解析方式是不同的,默认标签采用的是parseDefaultElement方法,而自定义标签采用的是parseCustomElement方法。

spring判断标签是否默认是通过标签的命名空间来判断的。对于默认标签来说,它的命名空间就是"http://www.springframework.org/schema/beans"。

关于默认标签的解析会在后续继续介绍。