Spring 源码阅读–beans

最近买了本书,来大概学习写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的读取,

时间: 2024-10-29 03:06:55

Spring 源码阅读–beans的相关文章

Spring源码阅读:IOC容器的设计与实现(二)——ApplicationContext

上一主题中,了解了IOC容器的基本概念,以及BeanFactory的设计与实现方式,这里就来了解一下ApplicationContext方式的实现. ApplicationContext 在Spring的参考文档中,为啥要推荐使用ApplicationContext?它能给我们的应用带来什么好处呢?作为BeanFactory的实现之一,它又是如何设计的?在SpringMVC中使用的WebApplictionContext\XmlApplicationContext与之有何关联? Applicat

Spring源码阅读:Spring WebApplicationContext初始化与消亡

使用SpringMVC时,需要不论是使用注解配置,还是使用XML配置Bean,他们都会在Web服务器启动后就初始化.根据J2ee的知识可以知道,肯定是使用了ServletContextListener才完成的这个功能.那Spring又是如何实现的呢?还有我们在Web.xml配置的那些applicationContext.xml相关的XML文件的位置(配置方式多样),又是如何读取到相应的文件的呢,读取到这些文件后,是如何初始化类的呢?我们能不能自定义初始化过程或者自定义WebApplication

Spring源码阅读:Spring JDBC 组件的设计与实现

昨天回忆了我在学习JDBC时自己设计的JDBCTemplate(写在上一篇博客中),在使用Spring过程中,有时会用到Spring给我们提供的JdbcTemplate,这里看看Spring是如何实现这个组件的. 在使用Spring JDBC是你要做的工作很少: 从上面的图上可以看出来,使用Spring JDBC,你只需要做四个工作: 1)定义连接参数:也就是定义url,driver,user,password这个几个参数,一般我们会用一个jdbc.properties文件来配置. 2)指定要执

spring源码阅读(2)-- 容器启动之加载BeanDefinition

在<spring源码阅读(1)-- 容器启动之资源定位>一文中,阅读了spring是怎么根据用户指定的配置加载资源,当加载完资源,接下来便是把从资源中加载BeanDefinition. BeanDefinition作为spring其中一个组件,spring是这样描述BeanDefinition的:BeanDefinition描述了一个bean实例,它具有属性值,构造函数参数值以及具体实现提供的更多信息.个人的理解是BeanDefinition保存了一个bean实例的所有元数据,下面列举一些常用

spring源码阅读(3)-- 容器启动之BeanFactoryPostProcessor

接着上文<spring源码阅读(2)-- 容器启动之加载BeanDefinition>,当spring加载完所有BeanDefinition时,并不会马上去创建bean,而是先配置beanFactory,例如设置一下装配规则和判断是否需要创建一些指定的bean. 1 protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { 2 // Tell the internal bean factor

Spring源码阅读:Spring MVC 如何处理HTTP请求

Spring MVC 对HTTP请求的处理流程 通过之前的源码阅读,知道了ApplicationContext初始的过程,也知道了Spring MVC环境的初始化过程,今天就来了解一下SpringMVC是如何处理HTTP请求的. HTTP请求根据请求方式可以分为GET.POST.PUT.DELETE.OPTIONS.TRACE,最常用的还是GET和POST. Spring对于这几种HTTP请求的处理都是使用了processRequest(req,rep); @Override protected

Spring源码阅读:Spring声明式事务处理和编程式事务处理的设计与实现

之前的学习,了解了Spring事务管理的基础框架(查看).Spring在此基础上又提到了声明式事务管理和编程式事务管理.这里就来看看Spring是如何实现的. Spring声明式事务与EJB事务管理对比 Spring的声明式管理,类似于EJB的CMT,但又有不同.他们的不同之处有: 1)EJB的CMT是与JTA结合使用,而Spring框架的声明式事务管理可以在任何环境下工作.既可以使用全局事务管理,如JTA,也可以使用局部事务管理如JDBCJPA.Hibernate.JDO等. 2)可以在任何类

Spring源码阅读:Spring如何支持各种ORM框架

为了让开发程序更容易,到现在为止,已经有很多ORM框架了,例如:JPA,JDO,Hibernate,Mybatis(之前版本是IBatis)等等.也正因为已经有这么多优秀的ORM框架,Spring团队并没有自己开发一套ORM框架,而是对这些框架都进行了支持,让这些框架在Spring环境下可以得到完全的应用. 通常,在Spring环境下使用这些ORM框架时,都会通过一个Template来使用.Spring对这些框架的集成是这样的: 例如Hibernate,在使用Hibernate时(没有在Spri

Spring源码阅读:Spring事务管理的基础

上一节了解了全局事务与局部事务以及Spring提供的两种事务模式:编程式事务与声明式事务. 不论是编程式的事务处理,还是声明式的事务处理.他们都要对局部事务和全局事务以支持,也就是说要对JDBC进行支持.ORM框架,同时也要对JTA进行支持.他们的公共部分是commit,rollback.通过这一节的了解,我相信以后配置Spring事务时,就不需要在去网上查资料了或者去查Spring的参考文档了. 因此,Spring设计了如下的事务管理框架: 从上面的类图中和容易可以看出分为三部分:Platfo