Primavera: procurando contexto

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:

  1. Configuração de XML - ClassPathXmlApplicationContext (”context.xml”);
  2. Configuração do Groovy - GenericGroovyApplicationContext (”context.groovy”);
  3. Configuração através de anotações indicando o pacote para verificação - AnnotationConfigApplicationContext (”package.name”);
  4. 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);
         /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
            All - 5 : 0 - Filtered      /*
        doSomething(MainCharacter.num); doSomething(FilmMaker.class);
        printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
            All - 7 : 2 - Filtered     /*

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       /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
18           All - 5 : 0 - Filtered      /*
19        doSomething(MainCharacter.num); doSomething(FilmMaker.class);
20        printLoadedClasses(classFilter);
21        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
22               class film.MainCharacter
23               class film.FilmMaker
24               All - 7 : 2 - Filtered   /*
25        ApplicationContext context = new ClassPathXmlApplicationContext(
26                  configLocation: "applicationContext.xml");
27        printLoadedClasses(classFilter);

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);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
           All - 5 : 0 - Filtered      /*
        doSomething(MainCharacther.num); doSomething(FilmMaker.class);
        printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
            All - 7 : 2 - Filtered     /*
        ApplicationContext context = new ClassPathXmlApplicationContext(
                  configLocation: "applicationContext.xml");
        printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
               class film.Villain

            All - 343 : 3- Filtered     /*

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:

/** Map of bean definition objects, keyed by bean name */
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()));
//  [goodVillain, badVillain]
printLoadedClasses(calssFilter);
/* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
               class film.Villain
    All - 343 : 3- Filtered   /*

A lista BeanDefenitionNames contém 2 elementos; isto é, aqueles 2
BeanDefinition 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()));
//  [goodVillain, badVillain]
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:

  1. Registro de todos os arquivos @Configuration para análise posterior;
  2. 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 de

trabalho 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:

  1. Spring ?

    , . BeanDefinition, : , BeanDefinition’ . BeanDefinition, ..
  2. 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.

All Articles