码迷,mamicode.com
首页 > 编程语言 > 详细

Spring 源码阅读–beans

时间:2016-05-11 12:53:23      阅读:171      评论:0      收藏:0      [点我收藏+]

标签:

最近买了本书,来大概学习写spring源码

一:先来段代码来测试一下。

照书上的先来试试

<?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="myTestBean" class="com.nico.TestClient.SpringTest.BeanTest.MyTestBean"/>

</beans>
package com.nico.TestClient.SpringTest.BeanTest;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;

/**
 * Created by Administrator on 2016/5/9.
 */
public class SpringBean {

    @Test
    public void testSimpleLoad(){
        //已经过时的方法
        BeanFactory bf =new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml"));

        ApplicationContext context=new ClassPathXmlApplicationContext("BeanFactoryTest.xml");
        MyTestBean myTestBean = (MyTestBean) context.getBean("myTestBean");

        System.out.println("testStr:"+myTestBean.getTestStr());
    }
}

 

当照着书上面的代码样例写出来的时候,擦IDEA告诉我已经过时了,不禁让我心底一凉,这书是不是买的太久了。。。

好吧我们来学习新版本的源码,大概印证下吧,只有。

首先这个类来读取资源文件

ApplicationContext context=new ClassPathXmlApplicationContext("BeanFactoryTest.xml");

然后我们可以通过context.getbean就可以获取各种类型的bean。

大致学习源码还是按照书中的步骤来,节省时间!!只有以后在有机会一个版本一个版本的看看区别。

 

二:载入xml文件,以及读取。

上面测试代码的大致流程:

1.读取配置文件

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

3.调用实例化后的实例

三:分析代码以及实现

首先我们先从第一段代码分析:

(1)BeanFactory bf =new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml"));

大致流程:

1.new ClassPathResource(xml文件)返回ClassPathResource

2.new XmlBeanFactory() 返回beanfactiory对象

(2)配置文件封装

spring的配置文件是通过ClassPathResource来进行封装的,将不用来源的资源抽象成RL,然后我们可以查看Resource来看看都有哪几种方法:

看下类图

技术分享

相比书中所写,已经少了很多,将就看吧。。大致脉络看看。

我们进去看看Resource,其中的方法有:

contentLength
createRelative
exists
getDescription
getFile
getFilename
getURI
getURL
isOpen
isReadable
lastModified

其中抽象了Spring内部使用到的底层资源,file,url,classpath等。并且存在三个判断当前资源状态的三个方法:存在性,刻度性,是否可以打开状态

Resource接口方便对资源进行统一管理,实现方式是通过class或者classLoader提供的底层方法进行调用。

当resource相关类完成了对配置文件的封装后配置文件的读取工作就就交给xmlBeanDefinitionReader来处理了。

XmlBeanFactory的初始化有若干方法,我看下使用Resource实例作为构造参数的方法:

/**
     * Create a new XmlBeanFactory with the given resource,
     * which must be parsable using DOM.
     * @param resource XML resource to load bean definitions from
     * @throws BeansException in case of loading or parsing errors
     */
    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }

    /**
     * Create a new XmlBeanFactory with the given input stream,
     * which must be parsable using DOM.
     * @param resource XML resource to load bean definitions from
     * @param parentBeanFactory parent bean factory
     * @throws BeansException in case of loading or parsing errors
     */
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }

下面的reader.loadBeanDefinitions就是资源加载的真正实现。

在这之前还有一个父类,我点进去瞅一瞅。

/**
     * Create a new AbstractAutowireCapableBeanFactory.
     */
    public AbstractAutowireCapableBeanFactory() {
        super();
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }

其中ignoreDependencyInterface的主要功能就是忽略给定接口的自动装配功能。

 

(3)加载Bean

上面我们走到这个this.reader.loadBeanDefinitions(resource),这里是整个资源加载的切入点。

大致顺序:

1.封装资源文件。当进入

loadBeanDefinitions方法时候使用EncodeResource类进行封装
2.获取输入流。从Resource中获取对应的InputStream并且构造InputSource
3.通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
源码流程:
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));
    }


    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.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            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();
            }
        }
    }


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

大致的源码调用顺序。

首先看EncodedResource的作用,对于资源文件的编码进行处理。主要的方法在于其中的getResder方法,用来设置编码。

当构造好了encodeResource对象后,再次转入

loadBeanDefinitions(EncodedResource encodedResource),这个方法内部就是真正的数据准备阶段。
然后
doLoadBeanDefinitions方法就是核心算法。
然后它大概做了三件事:
1.获取对xml文件的验证模式
2.加载xml文件,并得到对应的document
3.根据返回的document注册bean信息
 
三:获取XML的验证模式
xml文件的验证模式保证了xml文件的正确性,比较常用的是两种:DTD和XSD。
DTD和XSD的区别

都在头部有声明

(1)验证模式的读取

通过这个方法来读取getValidationModeForResource

如果手动指定了验证模式则使用指定的验证模式,如果未指定则使用自动检测

int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }

自动检测方法在

detectValidationMode中实现,最后来
/**
     * Does the content contain the the DTD DOCTYPE declaration?
     */
    private boolean hasDoctype(String content) {
        return (content.indexOf(DOCTYPE) > -1);
    }

判断是否含有DOCTYPE来检测验证模式。

四:获取document

经过验证模式准备的步骤就可以进行document加载了,然后通过loadDocument方法来进行,主要是通过SAX解析XML文档,

@Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

然后其中要有个EntityResolver.

(1)EntityResolver用法

SAX应用需要实现自定义处理外部实体,则必须实现此接口向SAX注册一个实例,对于解析XML,SAX首先读取该XML的声明,根据声明去寻找相应的DTD定义。默认寻址通过网络,下载过程是个漫长的过程,而且网络中断或者不可用,这里会报错。这个的作用是项目本身就可以提供一个DTD声明的方法,避免了通过网络来寻找相应的声明。

五:解析及注册BeanDefinitions

上面我们已经把文件转化成Document,然后就是提取以及注册bean。

当拥有Document实例对象时候,引入下面的方法。

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

很符合面向对象的单一职责的原则。BeanDefinitionDocumentReader是一个接口,实例化是在

createBeanDefinitionDocumentReader完成,通过这个方法,然后进入DefaultBeanDefinition Document Reader,这个方法的重要目的之一就是提取ROOT以便于再次将root作为参数继续BeanDefinition的注册
@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) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }

        // 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(this.readerContext, root, parent);

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

 

之前的所有都是XML加载解析的准备阶段doRegisterBeanDefinitions算是真正地开始进行解析。

 

那么处理流程,首先是对rpofile的处理,然后开始解析。

(1)profile属性的使用

注册bean的最开始是对PROFILE_ATTRIBUTE属性的解析,

profile 用的最多是更换不同的数据库,用来生产环境和开发环境。

(2)解析并注册BeanDeifnition

处理了profile后就可以进行XML的读取了。进入

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)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
这个方法就是XML的读取,

Spring 源码阅读–beans

标签:

原文地址:http://www.cnblogs.com/wuwulalala/p/5481192.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!