Há alguns meses, uma publicação detalhada sobre o carregamento de classes na JVM foi publicada em meu perfil . Após essa palestra, meus colegas fizeram uma boa pergunta: qual mecanismo o Spring usa para analisar configurações e como ele carrega classes do contexto?
Após muitas horas de depuração da fonte da primavera, meu colega experimentou a verdade muito simples e compreensível.Pouco de teoria
Determine imediatamente que ApplicationContext é a interface principal em um aplicativo Spring que fornece informações de configuração do aplicativo.Antes de prosseguir diretamente para a demonstração, vamos dar uma olhada nas etapas envolvidas na criação de um ApplicationContext :
nesta postagem, analisaremos a primeira etapa, pois estamos interessados em ler configurações e criar BeanDefinition.BeanDefinition é uma interface que descreve um bean, suas propriedades, argumentos de construtor e outras meta-informações.Em relação à configuração dos próprios beans, o Spring possui 4 métodos de configuração:- Configuração de XML - ClassPathXmlApplicationContext (”context.xml”);
- Configuração do Groovy - GenericGroovyApplicationContext (”context.groovy”);
- Configuração através de anotações indicando o pacote para verificação - AnnotationConfigApplicationContext (”package.name”);
- JavaConfig - configuração por meio de anotações indicando a classe (ou matriz de classes) marcada com @Configuration - AnnotationConfigApplicationContext (JavaConfig.class).
Configuração XML
Tomamos como base um projeto simples:public class SpringContextTest{
private static String classFilter = "film.";
public static void main(String[] args){
printLoadedClasses(classFilter);
Aqui você deve explicar um pouco quais métodos e quais são usados:- printLoadedClasses (String ... filter) - o método imprime no console o nome do carregador e as classes JVM carregadas do pacote passado como parâmetro. Além disso, há informações sobre o número de todas as classes carregadas;
- doSomething (Object o) é um método que faz um trabalho primitivo, mas não permite excluir as classes mencionadas durante a otimização durante a compilação.
Nós nos conectamos ao projeto Spring (a seguir, o Spring 4 atua como o assunto do teste):11 public class SpringContextTest{
12 private static String calssFilter = "film.";
13
14 public static void main(String[] args){
15
16 printLoadedClasses(classFilter);
17
A linha 25 é a declaração e a inicialização do ApplicationContext através da configuração do XML .O arquivo xml de configuração é o seguinte:<beans xmlns = "http://www.spingframework.org/schema/beans" xmlns:xsi = "http..."
<bean id = "villain" class = "film.Villain" lazy-init= "true">
<property name = "name" value = "Vasily"/>
</bean>
</beans>
Ao configurar o bean, especificamos uma classe realmente existente. Preste atenção à propriedade especificada lazy-init = ”true” : nesse caso, a lixeira será criada somente após a solicitação do contexto.Vemos como o Spring, ao elevar o contexto, esclarecerá a situação com as classes declaradas no arquivo de configuração:public class SpringContextTest {
private static String classFilter = "film.";
public static void main(String[] args) {
printLoadedClasses(classFilter);
Vamos examinar os detalhes da configuração do Xml:- A leitura do arquivo de configuração foi da classe XmlBeanDefinitionReader , que implementa a interface BeanDefinitionReader ;- O XmlBeanDefinitionReader na entrada recebe um InputStream e carrega o documento através do DefaultDocumentLoader :Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
- Depois disso, cada elemento deste documento é processado e, se for um compartimento, o BeanDefinition é criado com base nos dados preenchidos (ID, nome, classe, alias, método init, método destroy, etc.):} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
- Cada BeanDefinition é colocado em um mapa, que é armazenado na classe DefaultListableBeanFactory:this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
No código, o Mapa fica assim:
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
Agora no mesmo arquivo de configuração, adicione outra declaração de bean com a classe film.BadVillain :<beans xmlns = "http://www.spingframework.org/schema/beans" xmlns:xsi = "http..."
<bean id = "goodVillain" class = "film.Villain" lazy-init= "true">
<property name = "name" value = "Good Vasily"/>
</bean>
<bean id = "badVillain" class = "film.BadVillain" lazy-init= "true">
<property name = "name" value = "Bad Vasily"/>
</bean>
Vamos ver o que acontece se você imprimir uma lista de BeanDefenitionNames criados e classes carregadas:ApplicationContext context = new ClassPathXmlApplicationContext(
configLocation: "applicationContext.xml");
System.out.println(Arrays.asList(context.getBeanDefinitionNames()));
printLoadedClasses(calssFilter);
Apesar do fato de a classe film.BadVillain especificada no arquivo de configuração não existir, o Spring funciona sem erros:ApplicationContext context = new ClassPathXmlApplicationContext(
configLocation: "applicationContext.xml");
System.out.println(Arrays.asList(context.getBeanDefinitionNames()));
printLoadedClasses(calssFilter);
A lista BeanDefenitionNames contém 2 elementos; isto é, aqueles 2BeanDefinition configurados em nosso arquivo foram criados.As configurações dos dois compartimentos são essencialmente as mesmas. Mas, enquanto a classe existente é carregada, não há problemas. A partir do qual podemos concluir que houve também uma tentativa de carregar uma classe inexistente, mas uma tentativa fracassada não quebrou nada.Vamos tentar obter os próprios grãos por seus nomes:ApplicationContext context = new ClassPathXmlApplicationContext(
configLocation: "applicationContext.xml");
System.out.println(Arrays.asList(context.getBeanDefinitionNames()));
System.out.println(context.getBean( name: "goodVillain"));
System.out.println(context.getBean( name: "badVillain"));
Temos o seguinte:
Se no primeiro caso um bean válido foi recebido, no segundo caso a exceção chegou.Preste atenção ao rastreamento de pilha: carregamento diferido das classes trabalhadas. Todos os carregadores de classes são rastreados na tentativa de encontrar a classe que estão procurando entre os carregados anteriormente. E depois que a classe desejada não foi encontrada, chamando o método Utils.forName , é feita uma tentativa de encontrar uma classe inexistente por nome, o que levou a um erro natural.Ao elevar o contexto, apenas uma classe foi carregada, enquanto uma tentativa de carregar um arquivo inexistente não levou a um erro. Por que isso aconteceu?Isso porque registramos lazy-init: truee proibiu o Spring de criar uma instância do bean, onde a exceção recebida anteriormente é gerada. Se você remover esta propriedade da configuração ou alterar seu valor lazy-init: false , o erro descrito acima também falhará, mas o aplicativo não será ignorado. No nosso caso, o contexto foi inicializado, mas não foi possível criar uma instância do bean, porque A classe especificada não foi encontrada.Configuração Groovy
Ao configurar um contexto usando um arquivo Groovy, você precisa gerar um GenericGroovyApplicationContext , que recebe uma string com a configuração de contexto como entrada. Nesse caso, a classe GroovyBeanDefinitionReader está envolvida na leitura do contexto . Essa configuração funciona essencialmente da mesma forma que o Xml, apenas com arquivos Groovy. Além disso, GroovyApplicationContext funciona bem com o arquivo Xml.Um exemplo de um arquivo de configuração simples do Groovy:beans {
goodOperator(film.Operator){bean - >
bean.lazyInit = 'true' >
name = 'Good Oleg'
}
badOperator(film.BadOperator){bean - >
bean.lazyInit = 'true' >
name = 'Bad Oleg' / >
}
}
Tentamos fazer o mesmo que com o Xml: O
erro trava imediatamente: o Groovy, como o Xml, cria as BeanDefenitions, mas nesse caso o pós-processador imediatamente dá um erro.Configuração via anotações indicando pacote para varredura ou JavaConfig
Essa configuração é diferente das duas anteriores. A configuração através de anotações usa 2 opções: JavaConfig e anotação sobre classes.O mesmo contexto é usado aqui: AnnotationConfigApplicationContext ("package" /JavaConfig.class) . Funciona dependendo do que foi passado ao construtor.No contexto de AnnotationConfigApplicationContext, existem 2 campos particulares:- leitor final privado AnnotatedBeanDefinitionReader (funciona com JavaConfig);
- private final ClassPathBeanDefinitionScanner scanne r (varre o pacote).
A peculiaridade do AnnotatedBeanDefinitionReader é que ele funciona em várias etapas:- Registro de todos os arquivos @Configuration para análise posterior;
- Registre um BeanFactoryPostProcesso r especial , ou seja , BeanDefinitionRegistryPostProcessor , que, usando a classe ConfigurationClassParser , analisa JavaConfig e cria uma BeanDefinition .
Considere um exemplo simples:@Configuration
public class JavaConfig {
@Bean
@Lazy
public MainCharacter mainCharacter(){
MainCharacter mainCharacter = new MainCharacter();
mainCharacter.name = "Patric";
return mainCharacter;
}
}
public static void main(String[] args) {
ApplicationContext javaConfigContext =
new AnnotationConfigApplicationContext(JavaConfig.class);
for (String str : javaConfigContext.getBeanDefinitionNames()){
System.out.println(str);
}
printLoadedClasses(classFilter);
Criamos um arquivo de configuração com a lixeira mais simples possível. Examinamos o que será carregado:
se, no caso de Xml e Groovy, foram carregados tantos BeanDefinition quanto anunciados, nesse caso, tanto o BeanDefinition declarado quanto o adicional serão carregados no processo de elevar o contexto. No caso de implementação por meio do JavaConfig, todas as classes são carregadas imediatamente, incluindo a classe do próprio JavaConfig, pois ele próprio é um bean.Outro ponto: no caso das configurações Xml e Groovy, foram carregados 343 arquivos, aqui ocorreu uma carga mais "pesada" de 631 arquivos adicionais. Etapas detrabalho do ClassPathBeanDefinitionScanner :- O pacote especificado determina a lista de arquivos para verificação. Todos os arquivos caem em diretórios;
- , InputStream org.springframework.asm.ClassReader.class;
- 3- , org.springframework.core.type.filter.AnnotationTypeFilter. Spring , Component , Component;
- , BeanDefinition.
Toda a “mágica” de trabalhar com anotações, como é o caso com XML e Groovy, reside precisamente no ClassReader.class classe do springframework.asm pacote . A especificidade deste leitor é que ele pode trabalhar com bytecode. Ou seja, o leitor pega um InputStream do bytecode, o varre e procura anotações nele.Considere o scanner usando um exemplo simples.Crie sua própria anotação para procurar as classes correspondentes:import org.springframework.stereotype.Component
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyBeanLoader{
String value() default "";
Criamos 2 classes: uma com a anotação de componente padrão e a segunda com a anotação personalizada:@Component
public class MainCharacter {
public static int num = 1;
@Value("Silence")
public String name;
public MainCharacter() { }
MyBeanLoader("makerFilm")
@Lazy
public class FilmMaker {
public static int staticInt = 1;
@Value("Silence")
public String filmName;
public FilmMaker(){}
Como resultado, obtemos o BeanDefinition gerado para essas classes e carregadas com êxito.ApplicationContext annotationConfigContext =
new AnnotationConfigApplicationContext(...basePackages: "film");
for (String str : annotationConfigContext.getBeanDefinitionNames()){
System.out.println(str);
}
printLoadedClasses(classFilter);

Conclusão
Pelo exposto, as perguntas colocadas podem ser respondidas da seguinte forma:- Spring ?
, . BeanDefinition, : , BeanDefinition’ . BeanDefinition, .. - Spring ?
Java: , , , .
PS Espero que, neste post, eu tenha sido capaz de "abrir o véu do segredo" e mostrar em detalhes como a formação do contexto da primavera ocorre na primeira etapa. Acontece que nem tudo é tão "assustador". Mas isso é apenas uma pequena parte de uma grande estrutura, o que significa que ainda há muitas novidades novas e interessantes pela frente.