Creación de contenedor de análisis de código fuente de Spring y lectura de configuración XML

En la sección anterior, presentamos que Spring abstrae recursos en Resource y proporciona diferentes implementaciones para manejar diferentes tipos de recursos, como File, Http, Ftp, classpath, etc. En este artículo, hablaremos sobre la creación de contenedores Spring y la carga de recursos. Para Spring Contenedor, decimos que XmlBeanFactory o ApplicationContext pueden ser un contenedor Spring y Bean se puede obtener de él, pero su clase principal es DefaultListableBeanFactory . El proceso de creación de un contenedor IOC es el proceso de creación de una instancia de DefaultListableBeanFactory . Este artículo explicará el proceso de creación de un contenedor. Y el proceso de carga de archivos de recursos. Antes de eso, primero presentamos varias formas en que Java lee los archivos de recursos. Se estima que la forma más fácil de leer recursos es utilizar InputStream / OutputStream y otras API proporcionadas por los paquetes java.io.File y java.io. El código de muestra es el siguiente:

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

Además de la API proporcionada por Java.io, también podemos cargar archivos de recursos a través de ClassLoader para cargar archivos de recursos en la ruta de clases. ClassLoader proporciona tres métodos para cargar archivos de recursos, el código es el siguiente:

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

Después de comprender el mecanismo de carga de recursos de archivos de Java, comenzamos a presentar el mecanismo de carga de recursos de Spring. Anteriormente dijimos que los recursos en Spring se abstraen como Recursos. A continuación, presentamos cómo Spring abstrae los recursos como Recursos, es decir, cómo Cargar recursos. Spring también proporciona métodos de creación de contenedores, como XmlBeanFactory, ApplicationContext, etc. Necesitamos pasar una ruta de archivo de recursos en el método de construcción y cargar el archivo de recursos en el contenedor creado. El siguiente ejemplo es una forma común en Spring, y XmlBeanFactory no se recomienda. utilizar.

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

La carga de recursos se realiza en su contenedor, aquí tomamos ClassPathXmlApplicationContext como ejemplo para presentar el proceso de carga de archivos Spring. En ClassPathXmlApplicationContext , dependiendo de los parámetros, eventualmente se llamará a uno de los siguientes dos métodos de construcción, que se construye de la siguiente manera:

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

La lógica principal del código anterior es establecer la ruta del recurso o encapsular el recurso como Recurso, y luego llamar al método refresh (). La carga de recursos, la creación de contenedores, el análisis y registro de Bean, etc. se completan en este método. A continuación, primero explicamos El proceso de creación de contenedores y carga de recursos se explicará más adelante en el proceso de registro y análisis de Bean. A continuación, observamos el método refresh ():

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

En el código anterior, un contenedor IOC se crea a través de getsFreshBeanFactory (). A continuación, explicamos principalmente el proceso de creación del contenedor. El método es el siguiente:

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

Los dos métodos mencionados anteriormente son métodos para llamar a subclases. Sus dos subclases directas son GenericApplicationContext y AbstractRefreshableApplicationContext. El diagrama de jerarquía de clases es el siguiente:

El método anterior llamará a diferentes subclases de acuerdo con diferentes implementaciones. Por ejemplo, si es ClassPathXmlApplicationContext o FileSystemXmlApplicationContext, llamará al método AbstractRefreshableApplicationContext. Otras implementaciones como AnnotationConfigApplicationContext llamarán al método en GenericApplicationContext en estas dos subclases creadas. , Hay una instancia de DefaultListableBeanFactory en la subclase . Esta instancia es el núcleo de Spring IOC. Casi muchas operaciones de IOC se realizan en esta instancia. También se puede llamar un contenedor Spring. Las siguientes son todas las implementaciones de las dos subclases:

Para GenericApplicationContext, la instancia DefaultListableBeanFactory se crea en su método de construcción. El código es el siguiente para crear un contenedor IOC.

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

 Para AbstractRefreshableApplicationContext se crea en el método refreshBeanFactory (), el código es el siguiente:

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

 En el método anterior, el contenedor IOC es creado por createBeanFactory () , que es DefaultListableBeanFactory . Observamos el código fuente del método createBeanFactory () . Esto involucra el problema de los contenedores padre-hijo. No lo explicaré aquí. Solo necesita saber que un contenedor IOC se crea aquí. Sí, presentaremos el contenedor IOC de Spring más adelante.

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

Ahora que se ha creado el contenedor IOC, lo siguiente es cargar los recursos. La carga de los recursos también se hace en refreshBeanFactory () . Se llama al método loadBeanDefinitions ( beanFactory ) . Este método está en su subclase AbstractXmlApplicationContext y se carga a través de una instancia de XmlBeanDefinitionReader. Recursos, continuemos viendo el código:

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

Después de crear la instancia de XmlBeanDefinitionReader , configure algunas instancias para la instancia de XmlBeanDefinitionReader y llame a loadBeanDefinitions ( beanDefinitionReader ) para cargar el recurso después de la inicialización . el código se muestra a continuación:

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

En el código anterior se cargan dos formas de recursos, una para cargar instancias de recursos directamente y la segunda para cargar archivos de recursos en forma de rutas de cadena La implementación principal está en la clase AbstractBeanDefinitionReader . El recurso en forma de ruta de cadena se encapsulará como una instancia de recurso, el código es el siguiente:

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

Como se muestra en el código anterior, la carga de recursos final llama al método loadBeanDefinitions (recurso Resource). Este método está en la subclase AbstractBeanDefinitionReader . Los diferentes recursos usan diferentes clases de Reader. A continuación, se muestra el diagrama de estructura jerárquica de la implementación de AbstractBeanDefinitionReader .

Debido a que generalmente usamos XML como archivo de configuración, podemos ver el método XmlBeanDefinitionReader de la siguiente manera: el paso final es encapsular el recurso como InputSource y luego llamar a doLoadBeanDefinitions para cargar el archivo de recursos.

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

 Continuemos viendo el código fuente del método doLoadBeanDefinitions , el código es el siguiente:

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

El código anterior se divide en dos partes, una es cargar el recurso para analizar el XML como una instancia de documento y la otra es registrar un Bean. Aquí explicamos principalmente cómo encapsularlo como una instancia de documento, que es el método doLoadDocument . De hecho, este proceso es el proceso de analizar el Xml configurado en un documento. Lo que necesitamos entender aquí es el modo de validación de XMl , es decir , el método getValidationModeForResource llamado en el método doLoadDocument . El método es el siguiente:

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

El modo de verificación es finalmente detectado por el método de XmlValidationModeDetector : detectValidationMode ( InputStream inputStream ). Lee el archivo de recursos y determina si contiene la cadena DOCTYPE. Si contiene, es el modo de verificación DTD, de lo contrario es el modo de verificación 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);
    }
    ......
}

Para el método getEntityResolver () , se obtiene un analizador XML , que es una instancia de org.xml.sax.EntityResolver. La explicación oficial de EntityResolver es que la función de EntityResolver es que el proyecto en sí mismo puede proporcionar un método de declaración de cómo encontrar DTD, es decir, el proceso de encontrar la declaración DTD lo realiza el programa. Por ejemplo, colocamos DTD en algún lugar del proyecto y agregamos directamente Este documento puede leer y devolver un SAX, lo que evita buscar declaraciones DTD a través de la Red. La interfaz se define de la siguiente manera:

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

Al pasar un publicId y systemId para devolver una instancia de InputSource, los siguientes son los dos modos de configurar XMl en Spring. Si está en modo xsd, publicId es nulo y systemId es http://www.springframework.org/schema/beans/spring -beans-2.5.xsd , si se adopta el modo DTD, publicId es - // SPRING // DTD BEAN // EN, systemId es http://www.springframework.org/dtd/spring-beans.dtd, se adoptan diferentes modos Para el EntityResolver no utilizado , la siguiente es la implementación de EntityResolver proporcionada por Spring :

Entre ellos, BeansDtdResolver y PluggableSchemaResolver están encapsulados en los otros dos para su uso, por lo que nuestro getEntityResolver () creará instancias ResourceEntityResolver o DelegatingEntityResolver, y creará instancias BeansDtdResolver y PluggableSchemaResolve en ResourceEntityResolver o DelegatingEntity de la siguiente manera:

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

 A continuación, tomamos DelegatingEntityResolver como ejemplo para presentar la creación de BeansDtdResolver y PluggableSchemaResolver. Los métodos de construcción son los siguientes, y se crean dos instancias respectivamente

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

En cuanto a cómo analizar el archivo de recursos XML en una instancia de documento, no lo presentaré aquí, ya está en la categoría de análisis de Java XMl, y puede comprenderlo usted mismo. En resumen: este artículo presenta principalmente la creación del contenedor Spring IOC, es decir , la creación de DefaultListableBeanFactory , y la carga de archivos de configuración XML. La operación posterior después de la carga XML es la operación de registrar Beans, que es el registerBeanDefinitions ( doc , resource ) , explicaremos esta parte del código en una publicación de blog de seguimiento, y no la explicaremos aquí. Finalmente, adjunte un diagrama de secuencia de la creación del contenedor de Spring:

 

Supongo que te gusta

Origin blog.csdn.net/wk19920726/article/details/108804327
Recomendado
Clasificación