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

来源:互联网 发布:java打印直角三角形 编辑:程序博客网 时间:2024/06/01 11:47

首先来回顾一下简单的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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://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.xsdhttp\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsdhttp\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsdhttp\://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
原创粉丝点击