Spring源码解析之容器的创建与XML配置的读取

在前面我们介绍过Spring将资源抽象为Resource,并且提供了不同的实现处理不同类型的资源,比如File、Http、Ftp、classpath等等,本篇我们讲述Spring容器的创建与资源的加载,对于Spring容器,我们说XmlBeanFactory或者ApplicationContext都可以是Spring容器,都可以从其中获取到Bean,但是其核心类为DefaultListableBeanFactory,创建IOC容器的过程也就是创建DefaultListableBeanFactory实例的过程,本篇将会讲解创建容器的过程和资源文件加载的过程。在这之前,我们先介绍Java读取资源文件的几种方式。最简单读取资源的方式估计就是使用java.io.File和java.io包提供的InputStream/OutputStream等API了,示例代码如下所示:

File file = new File("");
InputStream inputStream = new FileInputStream(file);

除了Java.io提供的API之外,我们还可以通过ClassLoader加载资源文件,用于加载classpath下的资源文件。ClassLoader提供了三个方法用于加载资源文件,代码如下所示:

// 返回一个URL标识
ClassLoader classLoader = FileDemo.class.getClassLoader();
URL url = classLoader.getResource("");
// 获取一个输入流
InputStream inputStream = classLoader.getResourceAsStream("");
// 获取一个URL列表
Enumeration<URL> enumeration = classLoader.getResources("");

在了解Java的文件资源加载机制之后,我们开始介绍Spring的资源加载机制,在前面我们说过在Spring中资源是被抽象为Resource,下面我们介绍Spring是如何将资源抽象为Resource的,也就是如何加载资源的。Spring也提供了容器的创建方式,比如XmlBeanFactory,ApplicationContext等,我们需要在构造方法中传入一个资源文件路径,在创建的容器中加载资源文件,如下示例是Spring常用的方式,其中XmlBeanFactory已经不建议使用。

XmlBeanFactory xmlBeanFactory= new XmlBeanFactory(new ClassPathResource("spring.xml"));
ApplicationContext classpathContext = new ClassPathXmlApplicationContext("spring.xml");
ApplicationContext fileSystemContext = new FileSystemXmlApplicationContext("spring.xml");

资源的加载是在其容器完成的,这里我们以ClassPathXmlApplicationContext为例,介绍Spring文件的加载过程。在ClassPathXmlApplicationContext中,根据参数的不同最终会调用以下两个构造方法中的一个,其构造如下:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, ApplicationContext parent) throws BeansException {
    super(parent);
    ......
    this.configResources = new Resource[paths.length];
    for (int i = 0; i < paths.length; i++) {
        this.configResources[i] = new ClassPathResource(paths[i], clazz);
	}
	refresh();
}

上面的代码主要逻辑就是设置资源路径或者封装资源为Resource,然后调用refresh()方法,资源的加载以及容器的创建、Bean的解析与注册等都是在该方法中完成,下面我们首先讲解的是容器的创建和资源的加载流程,后续再讲解Bean的解析与注册流程。下面我们查看refresh()方法:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新容器.
prepareRefresh();
// 告诉子类刷新内部的Bean工厂,其实就是一个创建容器的过程.也是我们主要讲解的
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
.....//其他逻辑后续讲解
}

上面的代码中,通过obtainFreshBeanFactory()创建一个IOC容器,下面我们主要讲解容器的创建过程,其方法如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    return beanFactory;
}

上面调用的两个方法都是调用子类的方法,其有两个直接子类分别为GenericApplicationContext和AbstractRefreshableApplicationContext,其类层次结构图如下:

上面的方法会根据不同的实现调用不同的子类,比如如果是ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext会调用AbstractRefreshableApplicationContext的方法,其他的实现比如AnnotationConfigApplicationContext会调用GenericApplicationContext中的方法,而容器的创建就是在这两个子类中创建,在子类中有一个DefaultListableBeanFactory的实例,该实例是Spring IOC的核心,几乎很多IOC操作都是在该实例中进行,也可以称其为Spring容器。如下为两个子类的所有的实现:

对于GenericApplicationContext来说DefaultListableBeanFactory实例实在其构造方法中创建的,代码如下就创建了一个IOC容器。

public GenericApplicationContext() {
    this.beanFactory = new DefaultListableBeanFactory();
}

 对于AbstractRefreshableApplicationContext则是在refreshBeanFactory()方法中创建的,代码如下:

protected final void refreshBeanFactory() throws BeansException {
   //如果已经存在,销毁Bean,销毁容器
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        //创建工厂,创建IOC容器
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        //加载Bean定义,即加载处理资源
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
        this.beanFactory = beanFactory;
	    }
    }
    ......//catch代码
}

 在上面的方法中,通过createBeanFactory()创建了IOC容器,即DefaultListableBeanFactory,我们查看createBeanFactory()方法源码,这里涉及到父子容器的问题,这里不做讲解,只需要知道在这里创建了一个IOC容器即可,后续我们再介绍Spring的IOC容器。

protected DefaultListableBeanFactory createBeanFactory() {
    return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

既然IOC容器已经创建完毕,下面就是加载资源了,加载资源是在也是在refreshBeanFactory()完成的,调用的是loadBeanDefinitions(beanFactory)方法,该方法是在其子类AbstractXmlApplicationContext中,并且是通过XmlBeanDefinitionReader 实例加载资源,下面我们继续查看代码:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 创建一个XmlBeanDefinitionReader实例.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    //为XmlBeanDefinitionReader配置Environment实例,ResourceLoader实例
    //和EntityResolver实例,这里的配置是为了解析资源文件所用这里不做介绍
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    //初始化XmlBeanDefinitionReader实例
    initBeanDefinitionReader(beanDefinitionReader);
    //加载资源
    loadBeanDefinitions(beanDefinitionReader);
}

在创建XmlBeanDefinitionReader 实例之后,为XmlBeanDefinitionReader 实例设置一些实例并且初始化之后调用loadBeanDefinitions(beanDefinitionReader)加载资源。代码如下:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    Resource[] configResources = getConfigResources();
    if (configResources != null) {
        reader.loadBeanDefinitions(configResources);
    }
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        reader.loadBeanDefinitions(configLocations);
    }
}

上面的代码中加载了两种形式的资源,一种是直接加载Resource实例,第二种则是加载字符串路径形式的资源文件,其核心实现在类AbstractBeanDefinitionReader中。其中以字符串路径形式的资源会被封装为Resource实例,其代码如下:

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
   //获取ResourceLoader实例
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader instanceof ResourcePatternResolver) {
        // 处理Resource实例类型的资源.
        Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
        //调用loadBeanDefinitions(Resource resource)加载资源
        int loadCount = loadBeanDefinitions(resources);
        ......
        return loadCount;
    }......
    else {
        //如果是字符串类型的资源,加载资源封装为Resource
        Resource resource = resourceLoader.getResource(location);
        //调用loadBeanDefinitions(Resource resource)加载资源
        int loadCount = loadBeanDefinitions(resource);
        ......
		return loadCount;
    }
}

如上代码所示,最终资源的加载调用的是loadBeanDefinitions(Resource resource)方法,该方法在AbstractBeanDefinitionReader子类中,不同的资源采用不用的Reader类,如下为AbstractBeanDefinitionReader的实现的层次结构图。

因为我们一般使用XML作为配置文件,因此,我们可以查看XmlBeanDefinitionReader的方法如下,最终就是将资源封装为InputSource,然后调用doLoadBeanDefinitions加载资源文件。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    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());
    }
......			
}

 下面我们继续查看doLoadBeanDefinitions方法的源码,代码如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
    try {
        //加载资源解析为Document实例
        Document doc = doLoadDocument(inputSource, resource);
        //解析并且注册Bean
        return registerBeanDefinitions(doc, resource);
    }
    ......//catch代码
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());
}

上面的代码分为两部分,一是加载资源解析XML为Document实例,二是注册Bean,这里我们主要讲解如何封装为Document实例的,也就是doLoadDocument方法。其实这个过程就是将配置的Xml解析为Document的过程,这里我们需要了解的就是XMl的验证模式,也就是doLoadDocument方法中调用的getValidationModeForResource方法,其方法如下:

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
         //如果验证模式不是自动验证,返回验证模式
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
         //根据资源检测验证模式
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		//返回验证模式
		return VALIDATION_XSD;
}

验证模最终是通过XmlValidationModeDetector的方法:detectValidationMode(InputStream inputStream)检测的,它读取资源文件,判断是否包含DOCTYPE字符串,如果包含则为DTD验证模式,否则为XSD验证模式

public int detectValidationMode(InputStream inputStream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    .....
    boolean isDtdValidated = false;
    String content;
    while ((content = reader.readLine()) != null) {
        //读取字符串,并且过滤文件中的注释
        content = consumeCommentTokens(content);
        if (this.inComment || !StringUtils.hasText(content)) {
            continue;
        }
        //如果包含DOCTYPE,是DTD验证模式
        if (hasDoctype(content)) {
            isDtdValidated = true;
            break;
        }
        if (hasOpeningTag(content)) {
            break;
        }
        //返回验证模式
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    }
    ......
}

对于方法getEntityResolver(),是获取一个XML解析器,也就是org.xml.sax.EntityResolver实例。官方是这么解释EntityResolver,EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 的声明方法, 即:由程序来实现寻找DTD声明的过程,比如我们将DTD放在项目的某处在实现时直接将此文档读取并返回个SAX即可,这样就避免了通过网络来寻找DTD的声明,其接口定义如下:

public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;

通过传入一个publicId,和systemId返回一个InputSource实例,如下为Spring的两种配置XMl的模式,如果为xsd模式,publicId为null,systemId为http://www.springframework.org/schema/beans/spring-beans-2.5.xsd,如果采用DTD模式,publicId为-//SPRING//DTD BEAN//EN,systemId为http://www.springframework.org/dtd/spring-beans.dtd,不同的模式采用了不用的EntityResolver ,如下为Spring提供的EntityResolver 实现:

其中BeansDtdResolver和PluggableSchemaResolver是被封装在另外两个使用的,因此我们的getEntityResolver()会创建的是ResourceEntityResolver或者DelegatingEntityResolver实例,在ResourceEntityResolver或者DelegatingEntityResolver类中创建了BeansDtdResolver和PluggableSchemaResolve实例,代码如下所示:

protected EntityResolver getEntityResolver() {
    if (this.entityResolver == null) {
        // 获取一个EntityResolver实例
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        }else {
            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
	    }
    }
    return this.entityResolver;
}

 下面我们以DelegatingEntityResolver为例介绍BeansDtdResolver和PluggableSchemaResolver的创建,如下为其构造方法,分别创建了两个实例

public DelegatingEntityResolver(ClassLoader classLoader) {
    this.dtdResolver = new BeansDtdResolver();
    this.schemaResolver = new PluggableSchemaResolver(classLoader);
}

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    if (systemId != null) {
        //如果systemId以dtd结尾,使用BeansDtdResolver
    if (systemId.endsWith(DTD_SUFFIX)) {
        return this.dtdResolver.resolveEntity(publicId, systemId);
		} else if (systemId.endsWith(XSD_SUFFIX)) {
           //如果systemId以xsd结尾,使用PluggableSchemaResolver
			return this.schemaResolver.resolveEntity(publicId, systemId);
		}
	}
	return null;
}

至于如何将XML资源文件解析为Document实例,这里不再介绍,这已经属于Java解析XMl的范畴了,可以自行了解。综上所述:本篇主要介绍了Spring IOC容器的创建也就是DefaultListableBeanFactory的创建,以及XML配置文件的加载,XML加载之后后续的操作就是注册Bean的操作也就是前面展示代码中的registerBeanDefinitions(doc, resource)的操作,这部分代码我们在后续的博文中讲解,这里不做讲解。最后附上Spring创建容器的时序图:

 

©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页