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.