Spring source code analysis container creation and XML configuration reading

In the previous section, we introduced that Spring abstracts resources into Resource, and provides different implementations to handle different types of resources, such as File, Http, Ftp, classpath, etc. In this article, we will talk about the creation of Spring containers and the loading of resources. For Spring Container, we say that either XmlBeanFactory or ApplicationContext can be a Spring container, and Bean can be obtained from it, but its core class is DefaultListableBeanFactory . The process of creating an IOC container is the process of creating an instance of DefaultListableBeanFactory . This article will explain the process of creating a container And the process of loading resource files. Before that, we first introduce several ways that Java reads resource files. The easiest way to read resources is estimated to be to use the InputStream/OutputStream and other APIs provided by the java.io.File and java.io packages. The sample code is as follows:

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

In addition to the API provided by Java.io, we can also load resource files through ClassLoader for loading resource files under the classpath. ClassLoader provides three methods for loading resource files, the code is as follows:

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

After understanding Java's file resource loading mechanism, we began to introduce Spring's resource loading mechanism. Earlier we said that resources in Spring are abstracted as Resources. Below we introduce how Spring abstracts resources as Resources, that is, how Load resources. Spring also provides container creation methods, such as XmlBeanFactory, ApplicationContext, etc. We need to pass in a resource file path in the construction method, and load the resource file in the created container. The following example is a common way in Spring, and XmlBeanFactory is not recommended. use.

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

The loading of resources is done in its container. Here we take ClassPathXmlApplicationContext as an example to introduce the loading process of Spring files. In ClassPathXmlApplicationContext , depending on the parameters, one of the following two construction methods will eventually be called, which is constructed as follows:

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

The main logic of the above code is to set the resource path or encapsulate the resource as Resource, and then call the refresh() method. Resource loading, container creation, Bean parsing and registration, etc. are all completed in this method. Below we first explain The process of container creation and resource loading will be explained later on Bean parsing and registration process. Below we look at the refresh() method:

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

In the above code, an IOC container is created by obtainFreshBeanFactory (). Below we mainly explain the creation process of the container. The method is as follows:

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

The two methods called above are methods of calling subclasses. There are two direct subclasses: GenericApplicationContext and AbstractRefreshableApplicationContext. The class hierarchy diagram is as follows:

The above method will call different subclasses according to different implementations. For example, if it is ClassPathXmlApplicationContext or FileSystemXmlApplicationContext, it will call the method of AbstractRefreshableApplicationContext. Other implementations such as AnnotationConfigApplicationContext will call the method in GenericApplicationContext. The creation of the container is created in these two subclasses. , There is an instance of DefaultListableBeanFactory in the subclass . This instance is the core of Spring IOC. Almost many IOC operations are performed in this instance. It can also be called a Spring container. The following are all implementations of the two subclasses:

For GenericApplicationContext, the DefaultListableBeanFactory instance is created in its construction method. The code is as follows to create an IOC container.

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

 For AbstractRefreshableApplicationContext is created in the refreshBeanFactory() method, the code is as follows:

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代码
}

 In the above method, the IOC container is created by createBeanFactory() , which is DefaultListableBeanFactory . We look at the source code of the createBeanFactory() method. This involves the issue of parent-child containers. I won’t explain it here. You just need to know that an IOC container is created here. Yes, we will introduce Spring's IOC container later.

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

Now that the IOC container has been created, the following is to load the resources. Loading the resources is also done in refreshBeanFactory() . The loadBeanDefinitions( beanFactory ) method is called . This method is in its subclass AbstractXmlApplicationContext and is loaded through an instance of XmlBeanDefinitionReader. Resources, let's continue to view the code:

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

After creating the XmlBeanDefinitionReader instance, set some instances for the XmlBeanDefinitionReader instance and call loadBeanDefinitions( beanDefinitionReader ) to load the resource after initialization . code show as below:

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

Two forms of resources are loaded in the above code, one is to load Resource instances directly , and the second is to load resource files in the form of string paths, the core implementation of which is in the class AbstractBeanDefinitionReader . The resource in the form of a string path will be encapsulated as a Resource instance, the code is as follows:

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

As shown in the above code, the final resource loading calls the loadBeanDefinitions (Resource resource) method. This method is in the AbstractBeanDefinitionReader subclass. Different resources use different Reader classes. The following is the hierarchical structure diagram of the AbstractBeanDefinitionReader implementation.

Because we generally use XML as a configuration file, we can view the XmlBeanDefinitionReader method as follows. The final step is to encapsulate the resource as InputSource, and then call doLoadBeanDefinitions to load the resource file.

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

 Let's continue to view the source code of the doLoadBeanDefinitions method, the code is as follows:

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

The above code is divided into two parts, one is to load resources to parse XML as a Document instance, and the other is to register a Bean. Here we mainly explain how to encapsulate it as a Document instance, which is the doLoadDocument method. In fact, this process is the process of parsing the configured Xml into a Document. What we need to understand here is the validation mode of XMl , that is , the getValidationModeForResource method called in the doLoadDocument method. The method is as follows:

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

The verification mode is finally detected by the method of XmlValidationModeDetector : detectValidationMode ( InputStream inputStream ). It reads the resource file and determines whether it contains the DOCTYPE string. If it contains, it is the DTD verification mode, otherwise it is the XSD verification mode.

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

For the method getEntityResolver() , an XML parser is obtained , which is an instance of org.xml.sax.EntityResolver. The official explanation of EntityResolver is that the role of EntityResolver is that the project itself can provide a declaration method of how to find DTD, that is, the process of finding DTD declaration is realized by the program. For example, we put DTD somewhere in the project and directly add This document can read and return a SAX, which avoids searching for DTD declarations through the network. The interface is defined as follows:

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

By passing in a publicId and systemId to return an InputSource instance, the following are the two modes of configuring XMl in Spring. If it is in xsd mode, publicId is null and systemId is http://www.springframework.org/schema/beans/spring -beans-2.5.xsd , if DTD mode is adopted, publicId is -//SPRING//DTD BEAN//EN, systemId is http://www.springframework.org/dtd/spring-beans.dtd, different modes are adopted For the unused EntityResolver, the following is the implementation of EntityResolver provided by Spring :

Among them, BeansDtdResolver and PluggableSchemaResolver are encapsulated in the other two for use, so our getEntityResolver() will create ResourceEntityResolver or DelegatingEntityResolver instances, and create BeansDtdResolver and PluggableSchemaResolve instances in the ResourceEntityResolver or DelegatingEntityResolver class. The code is as follows:

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

 Below we take DelegatingEntityResolver as an example to introduce the creation of BeansDtdResolver and PluggableSchemaResolver. The construction methods are as follows, and two instances are created respectively

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

As for how to parse an XML resource file into a Document instance, I will not introduce it here. This is already in the category of Java parsing XMl, and you can understand it yourself. To sum up: This article mainly introduces the creation of Spring IOC container, that is , the creation of DefaultListableBeanFactory , and the loading of XML configuration files. The subsequent operation after XML loading is the operation of registering Beans, which is the registerBeanDefinitions( doc , resource ) operation, we will explain this part of the code in a follow-up blog post, and will not explain it here. Finally, attach a sequence diagram of Spring's container creation:

 

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108804327