Criação de contêiner de análise de código-fonte Spring e leitura de configuração XML

Na seção anterior, apresentamos que Spring abstrai recursos em Resource e fornece diferentes implementações para lidar com diferentes tipos de recursos, como File, Http, Ftp, classpath, etc. Neste artigo, falaremos sobre a criação de contêineres Spring e o carregamento de recursos. Para Spring Contêiner, dizemos que XmlBeanFactory ou ApplicationContext pode ser um contêiner Spring, e Bean pode ser obtido a partir dele, mas sua classe principal é DefaultListableBeanFactory . O processo de criação de um contêiner IOC é o processo de criação de uma instância de DefaultListableBeanFactory . Este artigo explicará o processo de criação de um contêiner E o processo de carregamento de arquivos de recursos. Antes disso, primeiro apresentamos várias maneiras como o Java lê arquivos de recursos. Estima-se que a maneira mais fácil de ler recursos seja usando InputStream / OutputStream e outras APIs fornecidas pelos pacotes java.io.File e java.io. O código de amostra é o seguinte:

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

Além da API fornecida pelo Java.io, também podemos carregar arquivos de recursos por meio do ClassLoader para carregar arquivos de recursos no classpath. ClassLoader fornece três métodos para carregar arquivos de recursos, o código é o seguinte:

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

Depois de entender o mecanismo de carregamento de recursos de arquivo do Java, começamos a apresentar o mecanismo de carregamento de recursos do Spring. Anteriormente, dissemos que os recursos no Spring são abstraídos como Recursos. Abaixo, apresentamos como o Spring abstrai recursos como Recursos, ou seja, como Carregue recursos. O Spring também fornece métodos de criação de contêiner, como XmlBeanFactory, ApplicationContext, etc. Precisamos passar um caminho de arquivo de recurso no método de construção e carregar o arquivo de recurso no contêiner criado. O exemplo a seguir é uma maneira comum em Spring, e XmlBeanFactory não é recomendado. usar.

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

O carregamento dos recursos é feito em seu container, aqui tomamos ClassPathXmlApplicationContext como exemplo para apresentar o processo de carregamento dos arquivos Spring. Em ClassPathXmlApplicationContext , um dos dois métodos de construção a seguir eventualmente será chamado de acordo com os diferentes parâmetros, que são construídos da seguinte maneira:

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

A lógica principal do código acima é definir o caminho do recurso ou encapsular o recurso como Recurso e, em seguida, chamar o método refresh (). Carregamento de recursos, criação de contêiner, análise e registro de bean etc. são todos concluídos neste método. A seguir, explicamos primeiro O processo de criação de contêiner e carregamento de recursos será explicado posteriormente no processo de análise e registro do bean. Abaixo, vemos o método refresh ():

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

No código acima, um contêiner IOC é criado por getFreshBeanFactory (). A seguir, explicamos principalmente o processo de criação do contêiner. O método é o seguinte:

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

Os dois métodos chamados acima são métodos de chamar subclasses. Existem duas subclasses diretas: GenericApplicationContext e AbstractRefreshableApplicationContext. O diagrama de hierarquia de classes é o seguinte:

O método acima chamará diferentes subclasses de acordo com diferentes implementações. Por exemplo, se for ClassPathXmlApplicationContext ou FileSystemXmlApplicationContext, ele chamará o método de AbstractRefreshableApplicationContext. Outras implementações, como AnnotationConfigApplicationContext , chamarão o método de criação de GenericApplicationContext. , Há uma instância de DefaultListableBeanFactory na subclasse . Essa instância é o núcleo do Spring IOC. Quase muitas operações IOC são realizadas nesta instância. Também pode ser chamado de contêiner Spring. A seguir estão todas as implementações das duas subclasses:

Para GenericApplicationContext, a instância DefaultListableBeanFactory é criada em seu método de construção.O código é o seguinte para criar um contêiner IOC.

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

 Para AbstractRefreshableApplicationContext é criado no método refreshBeanFactory (), o código é o seguinte:

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

 No método acima, o contêiner IOC é criado por createBeanFactory () , que é DefaultListableBeanFactory . Vemos o código-fonte do método createBeanFactory () . Isso envolve a questão de contêineres pai-filho. Não vou explicar aqui. Você só precisa saber que um contêiner IOC é criado aqui. Sim, apresentaremos o contêiner IOC da Spring mais tarde.

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

Agora que o contêiner IOC foi criado, o seguinte é carregar os recursos. O carregamento dos recursos também é feito em refreshBeanFactory () . O método loadBeanDefinitions ( beanFactory ) é chamado . Este método está em sua subclasse AbstractXmlApplicationContext e é carregado por meio de uma instância de XmlBeanDefinitionReader. Recursos, vamos continuar a ver o 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);
}

Depois de criar a instância XmlBeanDefinitionReader , defina algumas instâncias para a instância XmlBeanDefinitionReader e chame loadBeanDefinitions ( beanDefinitionReader ) para carregar o recurso após a inicialização . código mostrado como abaixo:

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

Duas formas de recursos são carregadas no código acima, uma é para carregar instâncias de Recurso diretamente e a segunda é para carregar arquivos de recursos na forma de caminhos de string, a implementação principal dos quais está na classe AbstractBeanDefinitionReader . O recurso na forma de um caminho de string será encapsulado como uma instância de Recurso, o código é o seguinte:

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

Conforme mostrado no código acima, o carregamento final do recurso chama o método loadBeanDefinitions (recurso Resource). Esse método está na subclasse AbstractBeanDefinitionReader . Diferentes recursos usam diferentes classes Reader. A seguir está o diagrama da estrutura hierárquica da implementação AbstractBeanDefinitionReader .

Como geralmente usamos XML como um arquivo de configuração, podemos visualizar o método XmlBeanDefinitionReader da seguinte maneira: A etapa final é encapsular o recurso como InputSource e, em seguida, chamar doLoadBeanDefinitions para carregar o arquivo de recurso.

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

 Vamos continuar a visualizar o código-fonte do método doLoadBeanDefinitions , o código é o seguinte:

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

O código acima é dividido em duas partes, uma é para carregar o recurso para analisar o XML como uma instância de Documento e a outra é para registrar um Bean. Aqui explicamos principalmente como encapsulá-lo como uma instância de Documento, que é o método doLoadDocument . Na verdade, esse processo é o processo de análise do Xml configurado em um Documento. O que precisamos entender aqui é o modo de validação do XMl , ou seja , o método getValidationModeForResource chamado no método doLoadDocument . O método é o seguinte:

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

O modo de verificação é finalmente detectado pelo método de XmlValidationModeDetector : detectValidationMode ( InputStream inputStream ). Ele lê o arquivo de recursos e determina se ele contém a string DOCTYPE. Se contiver, é o modo de verificação DTD, caso contrário, é o modo de verificação 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 o método getEntityResolver () , é obtido um analisador XML , que é uma instância de org.xml.sax.EntityResolver. A explicação oficial do EntityResolver é que a função do EntityResolver é que o próprio projeto pode fornecer um método de declaração de como encontrar o DTD, ou seja, o processo de localização da declaração do DTD é realizado pelo programa. Por exemplo, colocamos o DTD em algum lugar do projeto e adicionamos diretamente Este documento pode ler e retornar um SAX, o que evita a busca de declarações DTD na rede. A interface é definida da seguinte maneira:

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

Ao passar publicId e systemId para retornar uma instância InputSource, a seguir estão os dois modos de configuração de XMl no Spring. Se estiver no modo xsd, publicId é nulo e systemId é http://www.springframework.org/schema/beans/spring -beans-2.5.xsd , se o modo DTD for adotado, publicId é - // SPRING // DTD BEAN // EN, systemId é http://www.springframework.org/dtd/spring-beans.dtd, modos diferentes são adotados Para o EntityResolver não utilizado , o seguinte é a implementação do EntityResolver fornecido pelo Spring :

Entre eles, BeansDtdResolver e PluggableSchemaResolver são encapsulados nos outros dois para uso, portanto, nosso getEntityResolver () criará instâncias ResourceEntityResolver ou DelegatingEntityResolver e criará instâncias de BeansDtdResolver e PluggableSchemaResolve na classe ResourceEntityResolver ou DelegatingEntityResolver.

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 seguir, tomamos DelegatingEntityResolver como um exemplo para apresentar a criação de BeansDtdResolver e PluggableSchemaResolver. Os métodos de construção são os seguintes, e duas instâncias são criadas 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;
}

Quanto a como analisar um arquivo de recurso XML em uma instância de Documento, não vou apresentá-lo aqui.Isso já está na categoria de análise XMl de Java e você mesmo pode entendê-lo. Resumindo: Este artigo apresenta principalmente a criação do container Spring IOC, ou seja , a criação de DefaultListableBeanFactory e o carregamento de arquivos de configuração XML. A operação subsequente após o carregamento de XML é a operação de registro de Beans, que é o registerBeanDefinitions ( doc , recurso ) , explicaremos essa parte do código em uma postagem de blog de acompanhamento e não a explicaremos aqui. Por fim, anexe um diagrama de sequência da criação do contêiner do Spring:

 

Acho que você gosta

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