Spring源码解析-容器的基本实现

首先来回顾一下简单的bean获取。
1、bean类

public class MyTestBean {
    private String name = "whz";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2、配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="myTestBean" class="bean.MyTestBean"></bean>
</beans>

3、测试程序

public class BeanFactoryTest {

    @Test
    public void testSimpleLoad(){
        BeanFactory bf =new XmlBeanFactory(new ClassPathResource(("beanFactoryTest.xml")));
        MyTestBean myTestBean = (MyTestBean)bf.getBean("myTestBean");
        System.out.println(myTestBean.getName());
    }
}

4、结果

whz

通过初步猜想,我们认为是先加载xml配置文件,获取bean中的类名,然后通过反射机制进行实例化。

现在开始分析源码;
①配置文件封装:
首先通过new ClassPathResource((“beanFactoryTest.xml”)对配置文件进行封装,ClassPathResource继承的类实现了Resource接口,可以返回文件输入流,也就是getInputStream()方法。
我们进入XmlBeanFactory的构造方法:

private final XmlBeanDefinitionReader reader;

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }

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

这个工厂实例化了一个XmlBeanDefinitionReader类,然后这个类有加载了资源文件,也就是这个配置文件。进入loadBeanDefinitions()方法,

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

首先看见对resource用EncodedResource进行封装。这个类是用于对资源文件的编码进行处理。
接着进入loadBeanDefinitions方法

InputStream inputStream = encodedResource.getResource().getInputStream();

我们可以看到这么一段代码,可见先是获取配置文件的输入流,

try {
                //将输入流封装成InputSource,这个类往往用来读取xml文件
                    InputSource inputSource = new InputSource(inputStream);
                    if(encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }

                    var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                } finally {
                    inputStream.close();
                }

进入核心部分doLoadBeanDefinitions()方法,我删除了一些异常处理,留下关键代码,可见通过doLoadDocument加载Doucment对象,学过XML读取的都知道这是xml文件中的一种对象,跟html也有相似之处。然后根据这个Document对象注册Bean信息。这个代码是spring4的代码,可能与spring3版本有点不一样,但是大体过程是一样的。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {

            Document doc = this.doLoadDocument(inputSource, resource);
            return this.registerBeanDefinitions(doc, resource);

    }

进入加载doLoadDocument方法。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
    }

我们能看见有这么一段代码:this.getValidationModeForResource(resource),这段代码是获取xml 的验证模式。了解XML文件的应该知道XML的验证模式,一般有DTD和XSD这两种验证模式,一般会在XML文件的头部加上验证模式的声明,虽然IDE会报错,但是我们在读取xml时还是要验证xml 的格式规范。DTD是一种XML约束模式语言,XSD描述了XML文档的结构。在这对这两种模式就不详细解释了。
首先我们要从xml文件中获取验证模式

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = this.getValidationMode();
        //如果手动制定了验证模式则使用指定的验证模式
        if(validationModeToUse != 1) {
            return validationModeToUse;
        } else {
        //否则就自动检测验证模式
            int detectedMode = this.detectValidationMode(resource);
            return detectedMode != 1?detectedMode:3;
        }
    }

进入detectValidationMode()方法,删除了一些抛出异常

rotected int detectValidationMode(Resource resource) {
        if(resource.isOpen()) {

        } else {
            InputStream inputStream;

                inputStream = resource.getInputStream();
                this.validationModeDetector.detectValidationMode(inputStream);

    }

继续进入detectValidationMode方法

public int detectValidationMode(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        byte var4;
        try {
            boolean isDtdValidated = false;

            while(true) {
                String content;
                //如果是空行就略过
                if((content = reader.readLine()) != null) {
                    content = this.consumeCommentTokens(content);
                    //如果是注释也略过
                    if(this.inComment || !StringUtils.hasText(content)) {
                        continue;
                    }
                    //如果有DOCTYPE标记就说明是DTD模式
                    if(this.hasDoctype(content)) {
                        isDtdValidated = true;
                    } else if(!this.hasOpeningTag(content)) {
                        continue;
                    }
                }

                int var5 = isDtdValidated?2:3;
                return var5;
            }
        } catch (CharConversionException var9) {
            var4 = 1;
        } finally {
            reader.close();
        }

        return var4;
    }

获取了验证模式后,接下来我们要获取Document对象

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
    }

进入这个loadDocument方法
发现是接口,然后我们的找到实体类:

 private DocumentLoader documentLoader = new DefaultDocumentLoader();

进入这个类,找到loadDocument方法

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        //首先创建document工厂
        DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
        if(logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        然后创建documentBuilder
        DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

然后用这个builder来解析输入流,这是通过SAX来解析XML文档,在这其中还传入了一个参数entityResolver,我们知道XML文档解析需要获取DTD约束文档或者XSD的结构文档,来对XML文件进行验证,首先获取这个EntityResolver

protected EntityResolver getEntityResolver() {
        if(this.entityResolver == null) {
            ResourceLoader resourceLoader = this.getResourceLoader();
            if(resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            } else {
                this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
            }
        }

        return this.entityResolver;
    }

spring采用DelegatingEntityResolver为EntityResolver实现类,
从代码中看出,不同的验证模式会采用不同的解析器。

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if(systemId != null) {
            if(systemId.endsWith(".dtd")) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }

            if(systemId.endsWith(".xsd")) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }

        return null;
    }

SAX首先会读取xml上的声明

例如:xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd

因为从网络上获取DTD声明或者XSD非常不可靠,所以SAX
调用EntityResolver的resolveEntity方法(参数为获取的声明),来从本地获取约束文件,加载DTD类型的BeansDtdResolver的resolveEntity方法是直接截取systemId最后的xx.dtd,然后去当前路径下查找,而加载XSD类型的PluggableSchemaResolver,是默认到META-INF/Spring.schemas文件中找到systemId对应的XSD文件并加载。
我们文件中的一部分,可以看书每个声明对应了不同的xsd文件位置。

http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
http\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd
http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd

获取到document对象后,
解析及注册BeanDefinitions,接下来是解析及注册BeanDefinitions

 Document doc = this.doLoadDocument(inputSource, resource);
            return this.registerBeanDefinitions(doc, resource);

进入registerBeanDefinitions方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //创建BeanDefinitionDocumentReader对象
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
        //设置环境变量
        documentReader.setEnvironment(this.getEnvironment());
        //记录统计前beanDefition的个数       
        int countBefore = this.getRegistry().getBeanDefinitionCount();
           //加载(或者说是解析)及注册bean
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
        //记录本次加载的个数
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
    }

BeanDefinitionDocumentReader是个接口,真正的类型是DefaultBeanDefinitionDocumentReade,打开这个类,找到registerBeanDefinitions这个方法。

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

进入doRegisterBeanDefinitions查看

protected void doRegisterBeanDefinitions(Element root) {
        //处理profile属性
        String profileSpec = root.getAttribute("profile");
        if(StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if(!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
        //专门处理解析
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createDelegate(this.readerContext, root, parent);
        //解析前处理,留给子类实现
        this.preProcessXml(root);
        //解析
        this.parseBeanDefinitions(root, this.delegate);
        解析后处理,留给子类实现
        this.postProcessXml(root);
        this.delegate = parent;
  }

profile属性我们不常用,
我们可以再beans标签中加入profile属性

例如
<beans profile="aaa"></beans>
<beans profile="bbb"></beans>

集成到web环境中时在web.xml中加入以下代码

<context-param>
    <param-name>Spring.profiles.active</param-name>
    <param-value>aaa</param-value>
</context-param>

这样我们可以采用两套spirng配置
接着我们来说preProcessXml和postProcessXml方法,这两个方法是空方法,如果我们需要对root进行处理,我们可以用子类继承这个类,然后重写该方法,这种设计模式叫做模板方法。
接着我们进入parseBeanDefinitions方法;该方法是对xml进行读取对bean进行处理。

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)) {
                        //对默认标签处理
                        this.parseDefaultElement(ele, delegate);
                    } else {
                        //对自定义标签处理
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }

总结

这次主要探究了spring在出bean处理之前的动作,接下来回顾一下步骤。
1、封装配置文件资源。
2、创建XmlBeanDefinitionReader读取器,接下来用读取器处理资源文件。
3、从配置文件资源中获取文件输入流。
4、获取验证模式,创建可以找到约束文档的EntityResolver
5、进行解析xml文件,获取Document对象,然后解析bean并注册BeanDefitions

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值