Olá! Enquanto a administração do JavaRush trabalha em novos níveis, quero iniciar uma série de artigos de treinamento sobre o Spring Framework. Sim, eu sei que já existe muito material sobre esse assunto na Internet, mas, como mostra a prática, estão todos no nível Hello World. Quero falar não sobre como colocar anotações corretamente, mas sobre como tudo funciona “nos bastidores”. O artigo é destinado a quem já trabalhou de uma forma ou de outra com esse framework e está familiarizado com os conceitos básicos.
Inicializando o contexto.
Então, vamos começar com o básico. Na minha opinião, um dos pontos mais importantes é entender como o contexto é configurado e os beans são inicializados. Como você sabe, antes do Spring começar a funcionar, ele precisa ser configurado. Nos tempos antediluvianos, isso era feito usando arquivos xml (em alguns projetos, principalmente nos antigos, eles continuam fazendo isso até hoje). Aqui está um pequeno exemplo desse arquivo de configuração:<?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-3.0.xsd">
<bean id="helloWorld" class="ru.javarush.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
Em geral, isso é suficiente para criar alguns controladores e iniciar uma inicialização (que não decolará). Mas como essa configuração fará o Spring funcionar? E é aqui que as coisas ficam interessantes. Para que nossa configuração seja compreendida pelo Spring, existe um arquivo XmlBeanDefinitionReader
. BeanDefinition
Este é um componente interno do Spring que verifica (analisa) xml e cria com base no que escrevemos lá . BeanDefinition
é um objeto que armazena informações sobre o bean. Isto inclui: de qual classe ele (o bean) deve ser criado; escopo; se a inicialização lenta está instalada; É necessário inicializar antes deste bean outra e outras propriedades que estão descritas no xml. Todos os recebidos BeanDefinition
são adicionados a HashMap
, em que o identificador é o nome do bean (definido por você ou atribuído pelo Spring) e BeanDefinition
o próprio objeto. Depois que tudo BeanDefinition
estiver criado, um novo herói aparece no palco - BeanFactory
. Este objeto itera HashMap’e
s BeanDefinition
, cria beans com base neles e os coloca em um contêiner IoC. Há uma nuance aqui: quando o aplicativo é iniciado, o contêiner IoC conterá beans que possuem um escopo Singleton (definido por padrão), enquanto o restante será criado quando necessário (protótipo, solicitação, sessão). E agora uma pequena digressão, vamos conhecer outro personagem.
Conheça o BeanPostProcessor. (BPP)
O fato é que um bean não é necessariamente uma classe de lógica de negócios para sua aplicação. Um bean também é chamado de bean de infraestrutura. Resumindo, um bean de infraestrutura é uma classe que configura seus beans lógicos de negócios (sim, muitos beans). Contarei mais sobre isso a seguir, mas para deixar um pouco mais claro o que exatamente o BPP configura, darei um exemplo. Todos estão familiarizados com o resumo@Autowired
? Portanto, você AutowiredAnnotationBeanPostProcessor
é responsável por garantir que todas as suas aulas estejam integradas umas nas outras.
Vamos voltar ao BeanFactory
Conhecendo agora o BPP, você precisa esclarecer que, ao iterar sobreHashMap
's, BeanDefinition
todos os' são primeiro criados e colocados separadamente (não no contêiner IoC) BeanPostProcessor
. Depois disso, os beans regulares da nossa lógica de negócios são criados, colocados em um contêiner IoC e sua configuração começa usando BPPs diferidos separadamente. E é assim que acontece, cada BPP possui 2 métodos:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Itera em nossas caixas duas vezes. Na primeira vez o método é chamado postProcessorBeforeInitialization
e na segunda vez o método é chamado postProcessorAfterInitialization
. Certamente surgiu a questão de por que dois métodos são necessários, deixe-me explicar. O fato é que para processar algumas anotações (como @Transactional
, por exemplo), nosso bean é substituído por uma classe proxy. Para entender por que isso é feito, você precisa saber como funciona @Transactional
e é assim que funciona. Você precisa adicionar mais algumas linhas de código ao método marcado com esta anotação imediatamente. Como fazer isso? Isso mesmo, criando uma classe proxy, dentro da qual o código necessário será adicionado ao método requerido. Agora imagine esta situação, temos uma classe:
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
Esta classe tem 2 anotações @Autowired
e @Transactional
. Ambas as anotações são processadas por diferentes BPPs. Se funcionar primeiro AutowiredBPP
, tudo ficará bem, mas se não, encontraremos esse problema. O fato é que quando a classe proxy é criada, todas as metainformações são perdidas. Ou seja, não haverá informações sobre a anotação @Autowired
na classe proxy e, portanto AutowiredBPP
, não funcionará, o que significa que nosso campo someClass
terá o valor null
, o que provavelmente levará a um NPE. Também vale a pena saber que entre chamadas de métodos, o -method postProcessorBeforeInitialization
é postProcessorAfterInitialization
chamado init
, se houver. Este é basicamente o segundo construtor, mas a diferença é que neste momento todas as nossas dependências já estão embutidas na classe e podemos acessá-las a partir init
do método -. Então, mais uma vez o algoritmo de inicialização de contexto:
XmlBeanDefinitionReader
verifica nosso arquivo de configuração xml.- Cria
BeanDefinition
e os coloca emHashMap
. - Vem
BeanFactory
e dissoHashMap
soma separadamente todos osBeanPostProcessor
's. - Cria
BeanDefinition
beans a partir de e os coloca em um contêiner IoC. - Aqui os BPPs vêm e configuram esses beans usando 2 métodos.
- Preparar.
GO TO FULL VERSION