Implementando a API do Spring Framework a partir do zero. Passo a passo para iniciantes. Parte 1



O Spring Framework é um dos mais complicados para entender e aprender. A maioria dos desenvolvedores aprende devagar, através de tarefas práticas e do google. Essa abordagem não é eficaz, pois não fornece uma imagem completa e, ao mesmo tempo, é cara.

Gostaria de oferecer a você uma abordagem fundamentalmente nova para o estudo da primavera. Consiste no fato de que uma pessoa passa por uma série de tutoriais especialmente preparados e implementa independentemente o funcionamento da primavera. A peculiaridade dessa abordagem é que, além de uma compreensão 100% dos aspectos estudados do Spring, também oferece um grande aumento no Java Core (anotações, reflexão, arquivos, genéricos).

O artigo fornecerá uma experiência inesquecível e fará você se sentir como um desenvolvedor Pivotal. Passo a passo, você fará suas aulas se tornarem melhores e organizará seu ciclo de vida (o mesmo que em uma primavera real). As classes que você implementará são BeanFactory , Component , Service , BeanPostProcessor , BeanNameAware , BeanFactoryAware , InitializingBean , PostConstruct , PreDestroy , DisposableBean , ApplicationContext , ApplicationListener , ContextClosedEvent .

Um pouco sobre você


Meu nome é Yaroslav e sou desenvolvedor Java com 4 anos de experiência. No momento, trabalho na EPAM Systems (SPB) e me aprofundo nas tecnologias que usamos. Muitas vezes tenho que lidar com a primavera, e vejo nela um meio-termo no qual você pode crescer (Java todo mundo sabe tão bem, e ferramentas e tecnologias específicas demais podem ir e vir).

Há alguns meses, passei na certificação Spring Professional v5.0 (sem fazer cursos). Depois disso, pensei em como ensinar outras pessoas a saltar. Infelizmente, no momento não existe uma metodologia de ensino eficaz. A maioria dos desenvolvedores tem uma idéia muito superficial da estrutura e seus recursos. Depurar as fontes da primavera é muito difícil e absolutamente não é eficaz do ponto de vista do treinamento (eu gostava disso). Faça 10 projetos? Sim, você pode aprofundar seu conhecimento em algum lugar e obter muita experiência prática, mas muito do que está “escondido” nunca será aberto antes de você. Leia Primavera em ação? Legal, mas caro no esforço. Eu trabalhei 40% (durante a preparação para a certificação), mas não foi fácil.

A única maneira de entender algo até o fim é desenvolvê-lo você mesmo. Recentemente, tive a ideia de que você pode liderar uma pessoa através de um tutorial interessante que supervisionará o desenvolvimento de sua estrutura de DI. Sua principal característica será que a API coincidirá com a API que está sendo estudada. A grandiosidade dessa abordagem é que, além de uma compreensão profunda (sem espaços) da primavera, uma pessoa terá uma enorme quantidade de experiência no Java Core. Francamente, eu próprio aprendi muitas coisas novas durante a preparação do artigo, tanto no Spring quanto no Java Core. Vamos começar a desenvolver!

Projeto do zero


Portanto, a primeira coisa a fazer é abrir seu IDE favorito e criar um projeto a partir do zero. Não conectaremos nenhum Maven ou qualquer biblioteca de terceiros. Nós nem vamos conectar as dependências do Spring. Nosso objetivo é desenvolver uma API que seja mais semelhante à API Spring e implementá-la.

Em um projeto limpo, crie 2 pacotes principais. O primeiro pacote é seu aplicativo ( com.kciray ) e a classe Main.java dentro dele. O segundo pacote é org.springframework. Sim, duplicaremos a estrutura do pacote da mola original, o nome de suas classes e seus métodos. Há um efeito tão interessante - quando você cria algo próprio, esse começa a parecer simples e compreensível. Então, quando você trabalha em grandes projetos, parece que tudo é criado lá com base na sua peça de trabalho. Essa abordagem pode ter um efeito muito positivo na compreensão do sistema como um todo, aprimorando-o, corrigindo bugs, resolvendo problemas e assim por diante.

Se você tiver algum problema, pode fazer um projeto em funcionamento aqui .

Crie um container


Para começar, defina a tarefa. Suponha que tenhamos 2 classes - ProductFacade e PromotionService . Agora imagine que você deseja conectar essas classes entre si, mas para que as próprias classes não se conheçam (Padrão DI). Precisamos de uma classe separada que gerencie todas essas classes e determine as dependências entre elas. Vamos chamar de contêiner. Vamos criar a classe Container ... Embora não, espere! O Spring não possui uma única classe de contêiner. Temos muitas implementações de contêineres, e todas essas implementações podem ser divididas em 2 tipos - fábricas e contextos. A fábrica de caixas cria beans e os vincula (injeção de dependência, DI), e o contexto faz o mesmo, além de adicionar alguns recursos adicionais (por exemplo, internacionalizar mensagens). Mas como não precisamos dessas funções adicionais agora, trabalharemos com a fábrica de lixeiras.

Crie uma nova classe BeanFactory e coloque-a no pacote org.springframework.beans.factory . Deixe os Map<String, Object> singletons armazenados dentro dessa classe, na qual o id compartimento é mapeado para o próprio compartimento. Adicione a ele o Object getBean(String beanName) , que extrai os beans pelo identificador.

 public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } } 

Observe que BeanFactory e FactoryBean são duas coisas diferentes. A primeira é a fábrica de lixeiras (contêiner) e a segunda é a fábrica de lixeiras, que fica dentro do contêiner e também produz caixas. Fábrica dentro da fábrica. Se você estiver confuso entre essas definições, lembre-se de que em inglês o segundo substantivo é o principal e o primeiro é algo como um adjetivo. Na Fábrica de Feijão , a palavra principal é a fábrica, e na Fábrica de Feijão , o feijão.

Agora, crie as classes ProductService e PromotionsService . ProductService retornará o produto do banco de dados, mas antes disso você precisará verificar se há descontos (Promoções) aplicáveis ​​a este produto. No comércio eletrônico, o trabalho com desconto geralmente é alocado para uma classe de serviço separada (e às vezes para um serviço da web de terceiros).

 public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } } 

Agora precisamos fazer nosso contêiner ( BeanFactory ) detectar nossas classes, criá-las para nós e injetar uma na outra. Operações como o new ProductService() devem estar localizadas dentro do contêiner e feitas pelo desenvolvedor. Vamos usar a abordagem mais moderna (digitalização de classe e anotações). Para fazer isso, precisamos criar uma anotação @Component com as @Component ( org.springframework.beans.factory.stereotype ).

 @Retention(RetentionPolicy.RUNTIME) public @interface Component { } 

Por padrão, as anotações não são carregadas na memória enquanto o programa está em execução ( RetentionPolicy.CLASS ). Alteramos esse comportamento por meio de uma nova política de retenção ( RetentionPolicy.RUNTIME ).

Agora adicione @Component antes das classes ProductService e antes do PromotionService .

 @Component public class ProductService { //... } @Component public class PromotionService { //... } 


Precisamos do BeanFactory escanear nosso pacote ( com.kciray ) e encontrar classes nele anotadas por @Component . Esta tarefa está longe de ser trivial. Não existe uma solução pronta no Java Core, e teremos que fazer uma muleta. Milhares de aplicações de molas usam a digitalização de componentes nessa muleta. Você aprendeu a terrível verdade. Você terá que extrair os nomes de ClassLoader do ClassLoader e verificar ClassLoader eles terminam com ".class" ou não, e então criar seu nome completo e extrair objetos de classe!

Quero avisar imediatamente que haverá muitas exceções verificadas, portanto, esteja preparado para quebrá-las. Mas primeiro, vamos decidir o que queremos. Queremos adicionar um método especial ao BeanFactory e chamá-lo em Main :

 //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); 

Em seguida, precisamos obter o ClassLoader . Ele é responsável pelo carregamento de classes e é extraído de maneira simples:

 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 

Você provavelmente já percebeu que os pacotes são separados por um ponto e os arquivos por uma barra. Precisamos converter o caminho do lote para o caminho da pasta e obter algo como List<URL> (os caminhos no seu sistema de arquivos onde você pode procurar por arquivos de classe).

 String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path); 

Então espere um momento! Enumeration<URL> não é uma List<URL> . O que é isso tudo? Ah, horror, esse é o antigo progenitor do Iterator , disponível desde o Java 1.0. É com esse legado que devemos lidar. Se for possível percorrer o Iterable usando for (todas as coleções o implementam), no caso de Enumeration você precisará fazer um desvio de identificador, através de while(resources.hasMoreElements()) e nextElement() . E, no entanto, não há como remover itens da coleção. Apenas 1996, apenas hardcore. Ah, sim, no Java 9 eles adicionaram o método Enumeration.asIterator() , para que você possa trabalhar com ele.

Vamos mais longe. Precisamos extrair as pastas e trabalhar com o conteúdo de cada uma delas. Converta o URL em um arquivo e obtenha seu nome. Deve-se notar aqui que não verificaremos pacotes aninhados para não complicar o código. Você pode complicar sua tarefa e fazer uma recursão, se desejar.

 while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } } 

Em seguida, precisamos obter o nome do arquivo sem a extensão. No estaleiro em 2018, o Java desenvolveu o File I / O (NIO 2) por muitos anos, mas ainda não pode separar a extensão do nome do arquivo. Eu tenho que criar minha própria bicicleta, porque decidimos não usar bibliotecas de terceiros como o Apache Commons. Vamos usar o antigo avô como lastIndexOf(".") :

 if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); } 

Em seguida, podemos obter o objeto de classe usando o nome completo da classe (para isso chamamos de classe da classe Class ):

 Class classObject = Class.forName(basePackage + "." + className); 

Ok, agora nossas aulas estão em nossas mãos. Além disso, resta apenas destacar entre eles aqueles que possuem a anotação @Component :

 if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); } 

Corra e verifique. O console deve ser algo como isto:

 Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService 

Agora precisamos criar nosso bean. Você precisa fazer algo como o new ProductService() , mas para cada bean, temos nossa própria classe. A reflexão em Java nos fornece uma solução universal (o construtor padrão é chamado):

 Object instance = classObject.newInstance();//=new CustomClass() 

Em seguida, precisamos colocar esse bean nos Map<String, Object> singletons . Para fazer isso, selecione o nome do bean (seu ID). Em Java, chamamos variáveis ​​como classes (apenas a primeira letra é minúscula). Essa abordagem também pode ser aplicada aos beans, porque o Spring é uma estrutura Java! Converta o nome da lixeira para que a primeira letra seja pequena e adicione-a ao mapa:

 String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance); 

Agora verifique se tudo funciona. O contêiner deve criar beans e eles devem ser recuperados pelo nome. Observe que o nome do seu método instantiate() e o nome do método classObject.newInstance(); tem uma raiz comum. Além disso, instantiate() faz parte do ciclo de vida do feijão. Em Java, tudo está interconectado!

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612 


Tente também implementar a anotação org.springframework.beans.factory.stereotype.Service . Ele executa exatamente a mesma função que o @Component , mas é chamado de maneira diferente. O ponto principal está no nome - você demonstra que a classe é um serviço, não apenas um componente. Isso é algo como digitação conceitual. Na certificação da primavera, houve a pergunta "Quais anotações são estereotipadas?" (dos listados). ” Portanto, anotações estereotipadas são aquelas que estão no pacote de stereotype .

Preencha as propriedades


Veja o diagrama abaixo, que mostra o início do ciclo de vida do feijão. O que fizemos antes disso é o Instantiate (criando beans através de newInstance() ). O próximo passo é a injeção cruzada de feijão (injeção de dependência, é também a inversão de controle (IoC)). Você precisa examinar as propriedades dos beans e entender quais propriedades você precisa injetar. Se você chamar productService.getPromotionsService() , será null porque dependência ainda não adicionada.



Primeiro, crie o pacote org.springframework.beans.factory.annotation e adicione a anotação @Autowired . A idéia é sinalizar campos que são dependências com esta anotação.

 @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 

Em seguida, adicione-o à propriedade:

 @Component public class ProductService { @Autowired PromotionsService promotionsService; //... } 

Agora precisamos ensinar nosso BeanFactory encontrar essas anotações e injetar dependências nelas. Adicione um método separado para isso e chame-o de Main :

 public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } } 

Em seguida, precisamos apenas examinar todos os nossos compartimentos no mapa de singletons e, para cada compartimento, todos os seus campos ( object.getClass().getDeclaredFields() retorna todos os campos, incluindo os privados). E verifique se o campo possui uma anotação @Autowired :

 for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } } 

Em seguida, precisamos examinar todas as caixas mais uma vez e ver seu tipo - de repente, esse é o tipo que nossa lixeira quer usar por si mesma. Sim, temos um ciclo tridimensional!

 for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } } 

Além disso, quando encontramos o vício, precisamos injetá-lo. A primeira coisa que você pode pensar é escrever o campo promotionsService usando a reflexão diretamente. Mas a primavera não funciona assim. Afinal, se o campo tiver um modificador private , primeiro precisaremos defini-lo como public , depois escreva nosso valor e, em seguida, defina- private novamente como private (para manter a integridade). Parece uma muleta grande. Em vez de uma muleta grande, vamos fazer uma muleta pequena (formaremos o nome do levantador e o chamaremos):

 String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency); 

Agora execute seu projeto e certifique-se de que, ao chamar productService.getPromotionsService() vez de null , nosso bean seja retornado.

O que implementamos é a injeção por tipo. Também há uma injeção pelo nome (anotação javax.annotation.Resource ). É diferente porque, em vez do tipo do campo, seu nome será extraído e, de acordo com ele - a dependência do mapa. Tudo é semelhante aqui, mesmo em algo mais simples. Eu recomendo que você experimente e crie seu próprio bean, injete-o com @Resource e estenda o método populateProperties() .

Apoiamos beans que sabem sobre seu nome




Há momentos em que você precisa colocar o nome dele dentro da lixeira. Essa necessidade não surge frequentemente, porque caixas, em essência, não devem saber um sobre o outro e que são caixas. Nas primeiras versões da primavera, foi assumido que o bean é um POJO (Plain Old Java Objec, o bom e antigo objeto Java) e toda a configuração é renderizada em arquivos XML e separada da implementação. Mas implementamos essa funcionalidade, pois a injeção de nome faz parte do ciclo de vida da lixeira.

Como sabemos qual bean quer saber qual é o nome dele e o que ele não quer? A primeira coisa que vem à mente é fazer uma nova anotação do tipo @InjectName e esculpir em campos do tipo String. Mas essa solução será muito geral e permitirá que você se atire várias vezes (coloque essa anotação em campos de tipos inapropriados (não String) ou tente inserir um nome em vários campos da mesma classe). Existe outra solução, mais precisa - para criar uma interface especial com um método de incubação. Todos os compartimentos que o implementam recebem seu nome. Crie a classe BeanNameAware no pacote org.springframework.beans.factory :

 public interface BeanNameAware { void setBeanName(String name); } 

Em seguida, permita que o nosso PromotionsService implemente:

 @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } } 

E, finalmente, adicione um novo método à fábrica de feijão. Tudo é simples aqui - examinamos nosso bin-singleton, verificamos se o bin implementa nossa interface e chamamos o setter:

 public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } } 

Execute e verifique se tudo funciona:

 BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName()); 

Note-se que na primavera existem outras interfaces semelhantes. Eu recomendo que você implemente a interface BeanFactoryAware , que permite que os beans recebam um link para a fábrica de beans. É implementado de maneira semelhante.

Inicializar Beans




Imagine que você tem uma situação em que precisa executar algum código após as dependências serem injetadas (as propriedades da bandeja são definidas). Em termos simples, precisamos dar à lixeira a capacidade de se inicializar. Como alternativa, podemos criar uma interface InitializingBean e colocar a assinatura do método void afterPropertiesSet() nela. A implementação desse mecanismo é exatamente a mesma apresentada para a interface BeanNameAware , portanto, a solução está sob o spoiler. Pratique e faça você mesmo em um minuto:

Solução de Inicialização do Bean
 //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans(); 



Adicionando pós-processadores


Imagine-se no lugar dos primeiros desenvolvedores da primavera. Sua estrutura está crescendo e é muito popular entre os desenvolvedores; cartas são enviadas diariamente para o correio com solicitações para adicionar um ou outro recurso útil. Se, para cada um desses recursos, você adicionar sua própria interface e verificar o ciclo de vida do bean, ele (o ciclo de vida) ficará entupido com informações desnecessárias. Em vez disso, podemos criar uma interface universal que nos permita adicionar alguma lógica (absolutamente qualquer, esteja ela verificando anotações, substituindo a bandeja por outra, definindo algumas propriedades especiais e assim por diante).

Vamos pensar sobre o que é essa interface. Ele precisa fazer algum pós-processamento dos beans, portanto, pode ser chamado de BeanPostProcessor. Mas estamos diante de uma pergunta difícil - quando a lógica deve ser seguida? Afinal, podemos executá-lo antes da inicialização, mas podemos executá-lo depois. Para algumas tarefas, a primeira opção é melhor, para outras - a segunda ... Como ser?

Podemos ativar as duas opções ao mesmo tempo. Deixe um pós-processador transportar duas lógicas, dois métodos. Um é executado antes da inicialização (antes do método afterPropertiesSet() ) e o outro depois. Agora, vamos pensar nos próprios métodos - quais parâmetros eles devem ter? Obviamente, o próprio Object bean ( Object bean ) deve estar lá. Por conveniência, além do bean, você pode passar o nome desse bean. Você se lembra que o próprio compartimento não sabe sobre o seu nome. E não queremos forçar todos os beans a implementar a interface BeanNameAware. Mas, no nível pós-processador, o nome do bean pode ser muito útil. Portanto, nós o adicionamos como o segundo parâmetro.

E o que o método deve retornar ao pós-processamento do bean? Vamos fazer com que ele devolva a lixeira. Isso nos dá uma super flexibilidade, porque em vez de uma lixeira, você pode colocar um objeto proxy que encerra suas chamadas (e adiciona segurança). Ou você pode retornar completamente outro objeto recriando a bandeja novamente. Os desenvolvedores têm uma grande liberdade de ação. Abaixo está a versão final da interface projetada:

 package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } 

Em seguida, precisamos adicionar uma lista de processadores simples à nossa fábrica de beans e a capacidade de adicionar novos. Sim, este é um ArrayList regular.

 //BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); } 

Agora mude o método initializeBeans para levar em conta os pós-processadores:

 public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } } 

Vamos criar um pequeno pós-processador que simplesmente rastreia as chamadas para o console e adicione-o à nossa fábrica de feijão:

 public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } } 

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor()); 


Agora execute e verifique se tudo funciona. Como tarefa de treinamento, crie um pós-processador que fornecerá a anotação @PostConstruct (javax.annotation.PostConstruct) . Ele fornece uma maneira alternativa de inicializar (enraizada em Java, não na primavera). Sua essência é que você coloca a anotação em algum método, e esse método será chamado ANTES da inicialização padrão da primavera (InitializingBean).

Certifique-se de criar todas as anotações e pacotes (mesmo javax.annotation) manualmente, não conecte as dependências! Isso ajudará você a ver a diferença entre o núcleo da mola e suas extensões (suporte a javax), e lembre-se disso. Isso manterá um estilo no futuro.

Você estará interessado no fato de que, em uma primavera real, a anotação @PostConstructé implementada dessa maneira, por meio do CommonAnnotationBeanPostProcessor do pós-processador. Mas não espie lá, escreva sua implementação.

Por fim, recomendo que você adicione um método void close()à classe BeanFactorye elabore mais dois mecanismos. A primeira é uma anotação @PreDestroy (javax.annotation.PreDestroy), destinada a métodos que devem ser chamados quando o contêiner é fechado. O segundo é a interface org.springframework.beans.factory.DisposableBeanque contém o método void destroy(). Todos os compartimentos executando esta interface terão a capacidade de se destruir (liberar recursos, por exemplo).

@PreDestroy + DisposableBean
 //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } } 



Ciclo de vida completo do feijão


Por isso, implementamos o ciclo de vida completo da lixeira, em sua forma moderna. Espero que essa abordagem ajude você a se lembrar dela.

Nosso contexto favorito


Os programadores costumam usar o termo contexto, mas nem todos entendem o que realmente significa. Agora vamos colocar tudo em ordem. Como observei no início do artigo, o contexto é a implementação do contêiner, assim como BeanFactory. Mas, além das funções básicas (DI), ele ainda adiciona alguns recursos interessantes. Um desses recursos é o envio e o processamento de eventos entre os compartimentos.

O artigo acabou sendo muito grande e o conteúdo começou a ser cortado, então coloquei as informações de contexto embaixo do spoiler.

Percebemos o contexto
. org.springframework.context , ApplicationContext . BeanFactory . , close() .

 public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } } 


Main , , :

 ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); 

, . close() , « » - . , :

 package org.springframework.context.event; public class ContextClosedEvent { } 

ApplicationListener , . , ( ApplicationListener<E> ). , Java-, . , , :

 package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); } 

ApplicationContext . close() , , . ApplicationListener<ContextClosedEvent> , onApplicationEvent(ContextClosedEvent) . , ?

 public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } } 

Mas não. . bean instanceof ApplicationListener<ContextClosedEvent> . Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent> , ?

, , . , , , , :

 for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } } 

, , , — . , :

 Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); } 

ApplicationListener:

 @Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } } 

, Main , , :

 //Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); } 


Conclusão


Inicialmente, planejei este artigo para o Baeldung em inglês, mas depois pensei que o público do Habré poderia avaliar positivamente essa abordagem do treinamento. Se você gostou das minhas idéias, não deixe de apoiar o artigo. Se ela obtiver uma classificação superior a 30, prometo continuar. Ao escrever o artigo, tentei mostrar exatamente o conhecimento do Spring Core, que é mais frequentemente usado, e também com base no Guia de Estudo de Certificação do Core Spring 5.0 . No futuro, com a ajuda desses tutoriais, você poderá cobrir toda a certificação e tornar a primavera mais acessível para os desenvolvedores Java.

Atualização 05/10/2018


Cartas constantemente me chegam com perguntas "e quando a continuação estamos esperando por ele". Mas não há tempo, e outros projetos pessoais são uma prioridade. No entanto, se um de vocês realmente gostou da ideia, pode estudar a seção estreita da primavera e escrever um artigo de continuação. Se você não possui uma conta habr, posso publicar um artigo da minha conta ou ajudá-lo a receber um convite.

Distribuição de tópicos:
Spring Container - [nome de usuário]
Spring AOP - [nome de usuário]
Spring Web - [nome de usuário]
Spring Cloud - [nome de usuário]

Source: https://habr.com/ru/post/pt419679/


All Articles