Inicie você mesmo, a primavera está chegando (Parte 1)

Evgeny EvgenyBorisov Borisov (NAYA Technologies) e Kirill tolkkv Tolkachev (Cyan.Finance, Twitter ) falam sobre os momentos mais importantes e interessantes do Spring Boot no exemplo de um iniciante para um imaginário Iron Bank.



O artigo é baseado no relatório de Eugene e Cyril da nossa conferência Joker 2017. Abaixo do corte, está a transcrição em vídeo e texto do relatório.


A conferência Joker é patrocinada por muitos bancos, então vamos imaginar que o aplicativo no qual estudaremos o trabalho da bota Spring e o iniciador que criamos esteja conectado ao banco.



Portanto, suponha que um pedido seja recebido para um pedido do Banco de Ferro de Bravos. Um banco comum simplesmente transfere dinheiro para frente e para trás. Por exemplo, assim (temos uma API para isso):

http://localhost:8080/credit\?name\=Targarian\&amount\=100

E no Iron Bank, antes de transferir dinheiro, é necessário que a API do banco calcule se uma pessoa pode devolvê-lo. Talvez ele não sobreviva ao inverno e não haja ninguém para voltar. Portanto, é fornecido um serviço que verifica a confiabilidade.

Por exemplo, se tentarmos transferir dinheiro para Targaryen, a operação será aprovada:



Mas se Stark, então não:



Não é de admirar: os Starks morrem com muita frequência. Por que transferir dinheiro se uma pessoa não sobrevive ao inverno?

Vamos ver como fica por dentro.

 @RestController @RequiredArgsConstructor public class IronBankController { private final TransferMoneyService transferMoney; private final MoneyDao moneyDao; @GetMapping("/credit") public String credit(@RequestParam String name, @RequestParam long amount) {   long resultedDeposit = transferMoney.transfer(name, amount);   if (resultedDeposit == -1) {     return "Rejected<br/>" + name + " <b>will`t</b> survive this winter";   }   return format(       "<i>Credit approved for %s</i> <br/>Current  bank balance: <b>%s</b>",       name,       resultedDeposit   ); } @GetMapping("/state") public long currentState() {   return moneyDao.findAll().get(0).getTotalAmount(); } } 

Este é um controlador de string comum.

Quem é responsável pela lógica da escolha, a quem conceder um empréstimo e a quem - não? Linha simples: se seu nome é Stark, certamente não o traímos. Em outros casos - que sorte. Banco comum.

 @Service public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) {   return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } } 

Tudo o resto não é tão interessante. Estas são algumas anotações que fazem todo o trabalho para nós. Tudo é muito rápido.

Onde estão todas as principais configurações? Existe apenas um controlador. No Dao, geralmente é uma interface vazia.

 public interface MoneyDao extends JpaRepository<Bank, String> { } 

Em serviços - apenas serviços de tradução e previsão para quem você pode emitir. Não há diretório Conf. De fato, temos apenas application.yml (uma lista daqueles que pagam dívidas). E main é o mais comum:

 @SpringBootApplication @EnableConfigurationProperties(ProphetProperties.class) public class MoneyRavenApplication { public static void main(String[] args) {   SpringApplication.run(MoneyRavenApplication.class, args); } } 

Então, onde está toda a mágica escondida?

O fato é que os desenvolvedores não gostam de pensar em dependências, configurar configurações, especialmente se forem configurações XML, e pensar em como o aplicativo é iniciado. Portanto, o Spring Boot resolve esses problemas para nós. Nós só precisamos escrever um aplicativo.

Dependências


O primeiro problema que sempre tivemos é um conflito de versão. Sempre que conectamos bibliotecas diferentes que fazem referência a outras bibliotecas, aparecem conflitos de dependência. Toda vez que leio na Internet que preciso adicionar algum gerente de entidade, surge uma pergunta e qual versão devo adicionar para que não quebre nada?

O Spring Boot resolve o problema dos conflitos de versão.

Como geralmente obtemos um projeto Spring Boot (se não chegamos a algum lugar onde ele já existe)?

  • ou vá para start.spring.io , marque as caixas de seleção que Josh Long nos ensinou a configurar, clique no Download Project e abra o projeto onde tudo já está lá;
  • ou use o IntelliJ, onde, graças à opção exibida, as caixas de seleção no Spring Initializer podem ser definidas a partir daí.

Se trabalharmos com o Maven, o projeto terá o pom.xml, onde existe um pai Spring Boot chamado spring-boot-dependencies . Haverá um enorme bloco de gerenciamento de dependências.

Não vou entrar nos detalhes do Maven agora. Apenas duas palavras.

O bloco de gerenciamento de dependências não registra dependências. Este é um bloco com o qual você pode especificar versões caso essas dependências sejam necessárias. E quando você indica algum tipo de dependência no bloco de gerenciamento de dependências sem especificar a versão, o Maven começa a procurar se existe um bloco de gerenciamento de dependências no qual pai esta versão está escrita no pom pai ou em outro lugar. I.e. no meu projeto, adicionando uma nova dependência, não indicarei mais a versão na esperança de que seja indicada em algum lugar no pai. E se não for especificado no pai, certamente não criará nenhum conflito com ninguém. Em nosso gerenciamento de dependências, boas quinhentas dependências são indicadas e todas são consistentes entre si.

Mas qual é o problema? O problema é que, na minha empresa, por exemplo, tenho meu próprio pai. Se eu quiser usar o Spring, o que devo fazer com o meu pom pai?



Não temos herança múltipla. Queremos usar nosso pom e obter o bloco de gerenciamento de dependências de fora.



Isso pode ser feito. É suficiente registrar a importação da lista técnica do bloco de gerenciamento de dependências.

 <dependencyManagement> <dependencies>    <dependency>       <groupId>io.spring.platform</groupId>       <artifactId>platform-bom</artifactId>       <version>Brussels-SR2</version>       <type>pom</type>       <scope>import</scope>    </dependency> </dependencies> </dependencyManagement> 

Quem quer saber mais sobre o bom - veja o relatório " Maven vs. Gradle ". Aí tudo isso foi explicado em detalhes.

Hoje, tornou-se bastante popular entre as grandes empresas escrever blocos tão grandes de gerenciamento de dependências, onde indicam todas as versões de seus produtos e todas as versões de produtos que usam seus produtos e que não conflitam entre si. E isso se chama bom. Essa coisa pode ser importada para o seu bloco de gerenciamento de dependências sem herança.

E é assim que é feito em Gradle (como sempre, a mesma coisa, só que mais fácil):

 dependencyManagement { imports {   mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE' } } 

Agora vamos falar sobre as próprias dependências.

O que vamos escrever no aplicativo? O gerenciamento de dependências é bom, mas queremos que o aplicativo tenha certas habilidades, por exemplo, responder via HTTP, ter um banco de dados ou suporte à JPA. Portanto, tudo o que precisamos agora é obter três dependências.
Costumava ser assim. Eu quero trabalhar com o banco de dados e ele começa: é necessário algum tipo de gerenciador de transações; portanto, o módulo spring-tx é necessário. Eu preciso de um pouco de hibernação, então é necessário o EntityManager, o hibernate-core ou qualquer outra coisa. Eu configuro tudo através do Spring, então preciso do core da primavera. Ou seja, por uma coisa simples, você tinha que pensar em uma dúzia de dependências.

Hoje temos entradas. A idéia de um iniciante é que nós dependemos disso. Para começar, ele agrega as dependências necessárias para o mundo de onde ele veio. Por exemplo, se for um iniciador de segurança, você não pensa em quais dependências são necessárias, elas chegam imediatamente na forma de dependências transitivas ao iniciador. Ou, se você estiver trabalhando com o Spring Data Jpa, coloque uma dependência no iniciador e ele trará todos os módulos necessários para trabalhar com o Spring Data Jpa.

I.e. Nosso pom tem a seguinte aparência: contém apenas as 3-5 dependências de que precisamos:

 'org.springframework.boot:spring-boot-starter-web' 'org.springframework.boot:spring-boot-starter-data-jpa' 'com.h2database:h2' 

Com as dependências resolvidas, tudo ficou mais fácil. Precisamos pensar menos agora. Não há conflito e o número de dependências diminuiu.

Configuração de contexto


Vamos falar sobre a próxima dor que sempre tivemos - definindo o contexto. Sempre que começamos a escrever um aplicativo do zero, leva muito tempo para configurar toda a infraestrutura. Registramos na configuração xml ou java muitos dos chamados beans de infraestrutura. Se trabalhamos com o hibernate, precisamos do bean EntityManagerFactory. Muitos beans de infraestrutura - gerenciador de transações, fonte de dados etc. - Era necessário ajustar com as mãos. Naturalmente, todos eles entraram em contexto.

Durante o relatório do Spring Ripper , criamos o contexto principal e, se fosse o contexto xml, estava inicialmente vazio. Se construímos o contexto por meio de AnnotationConfigApplicationContext , havia alguns processadores de beanpost que poderiam configurar os beans de acordo com as anotações, mas o contexto também estava quase vazio.
E agora, principalmente, existe o SpringApplication.run e nenhum contexto é visível:

 @SpringBootApplilcation class App { public static void main(String[] args) {   SpringApplication.run(App.class,args); } } 

Mas, na verdade, temos um contexto. SpringApplication.run nos retorna algum contexto.


Este é um caso completamente atípico. Costumava haver duas opções:

  • se esse for um aplicativo de desktop, diretamente no principal, você precisará escrever um novo com as mãos, selecionar ClassPathXmlApplicationContext , etc.
  • se trabalhávamos com o Tomcat, havia um gerenciador de servlets que, por algumas convenções, procurava XML e, por padrão, criava um contexto a partir dele.

Em outras palavras, o contexto era de alguma forma. E ainda passamos algumas classes de configuração para a entrada. Em geral, escolhemos o tipo de contexto. Agora só temos SpringApplication.run , ele aceita configurações como argumentos e constrói um contexto

Enigma: o que podemos passar por lá?


Dado:

RipperApplication.class
 public… main(String[] args) {  SpringApplication.run(?,args); } 

Pergunta: o que mais pode ser transferido para lá?

Opções:
  1. RipperApplication.class
  2. String.class
  3. "context.xml"
  4. new ClassPathResource("context.xml")
  5. Package.getPackage("conference.spring.boot.ripper")

A resposta
A resposta é:
A documentação diz que qualquer coisa pode ser transferida para lá. No mínimo, isso será compilado e funcionará de alguma forma.



I.e. de fato, todas as respostas estão corretas. Qualquer um deles pode ser feito para funcionar, mesmo String.class , e em algumas condições você não precisa fazer nada para fazê-lo funcionar. Mas esta é uma história diferente.

A única coisa que não é mencionada na documentação é de que forma nos enviar para lá. Mas isso já é do domínio do conhecimento secreto.

 SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= # class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off 

SpringApplication é realmente importante aqui - mais abaixo nos slides, nós o teremos com Carlson.

Nosso Carlson cria algum tipo de contexto com base nas informações que passamos a ele. Lembro que damos a ele, por exemplo, cinco opções maravilhosas que você pode fazer com que tudo funcione usando SpringApplication.run :

  • RipperApplication.class
  • String.class
  • "context.xml"
  • new ClassPathResource("context.xml")
  • Package.getPackage("conference.spring.boot.ripper")

O que o SpringApplication faz por nós?



Quando criamos o contexto de new em main por new , tivemos várias classes diferentes que implementam a interface ApplicationContext :



E que opções existem quando Carlson cria o contexto?

Ele cria apenas dois tipos de contexto: um contexto da Web ( WebApplicationContext ) ou um contexto genérico ( AnnotationConfigApplicationContext ).



A escolha do contexto é baseada na presença de duas classes no caminho de classe:



Ou seja, o número de configurações não diminuiu. Para criar um contexto, podemos especificar todas as opções de configuração. Para criar o contexto, posso passar um script ou xml groovy; Posso indicar quais pacotes verificar ou passar na classe marcada com algumas anotações. Ou seja, eu tenho todas as possibilidades.

No entanto, este é o Spring Boot. Ainda não criamos uma única lixeira, nem uma única classe, temos apenas main, e nela está o nosso Carlson - SpringApplication.run . Na entrada, ele recebe uma aula marcada com algum tipo de anotação de Spring Boot.

Se você olhar para este contexto, o que acontecerá lá?

Em nossa aplicação, depois de conectar um par de entradas, havia 436 caixas.



Quase 500 grãos apenas para começar a escrever.



A seguir, entenderemos de onde esses grãos vieram.

Mas antes de tudo, queremos fazer o mesmo.

A mágica dos iniciantes, além de resolver todos os problemas com vícios, é que conectamos apenas 3-4 iniciantes e temos 436 caixas. Nós conectaríamos 10 iniciantes, haveria mais de 1000 posições, pois cada partida, exceto as dependências, já traz configurações nas quais algumas posições necessárias são registradas. I.e. Você disse que deseja uma iniciação para a Web, portanto, precisa de um distribuidor de servlets e de InternalResourceViewResolver . Conectamos o iniciador jpa - precisamos do bean EntityManagerFactory . Todos esses beans já estão em algum lugar nas configurações iniciais e magicamente chegam ao aplicativo sem nenhuma ação de nossa parte.

Para entender como isso funciona, hoje escreveremos um iniciador, que também trará caixas de infraestrutura para todos os aplicativos que usam esse iniciador.

Lei do Ferro 1.1. Sempre envie um corvo




Vamos olhar para a exigência do cliente. O Iron Bank tem muitos aplicativos diferentes sendo executados em diferentes ramos. Os clientes desejam que um corvo seja enviado toda vez que o aplicativo é aumentado - informações que o aplicativo aumentou.

Vamos começar a escrever o código na aplicação de um banco de ferro específico (banco de ferro). Escreveremos um iniciador para que todos os aplicativos do Iron Bank que dependem desse iniciador possam enviar automaticamente um corvo. Lembramos que os iniciantes nos permitem restringir automaticamente as dependências. E o mais importante, não escrevemos quase nenhuma configuração.

Fazemos um ouvinte que escuta o contexto a ser atualizado (o último evento), após o qual ele envia um corvo. Vamos ouvir ContextRefreshEvent .

 public class IronListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(" ..."); } } 


Escrevemos ouvinte na configuração inicial. Até o momento, haverá apenas um ouvinte, mas amanhã o cliente solicitará outras partes da infraestrutura e também as escreveremos nessa configuração.

 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


Surge a pergunta: como fazer com que a configuração do nosso starter se encaixe automaticamente em todos os aplicativos que usam esse starter?

Para todas as ocasiões, existe um "ativar algo".



Realmente, se eu depender de 20 entradas, terei que colocar @Enable ? E se o acionador de partida tiver várias configurações? A principal classe de configuração será pendurada com @Enable* , como está a árvore do Ano Novo?



Na verdade, quero obter algum tipo de inversão de controle no nível de dependência. Quero conectar o motor de arranque (para que tudo funcione) e não saber nada sobre como o seu interior é chamado. Portanto, usaremos spring.factories.



Então, o que é spring.factories


A documentação diz que existem essas fontes de mola nas quais você precisa indicar a correspondência de interfaces e o que precisa carregar nelas - nossas configurações. E tudo isso aparecerá magicamente no contexto, enquanto várias condições trabalharão neles.



Assim, obtemos a inversão de controle, que precisávamos.



Vamos tentar implementar. Em vez de acessar as tripas do iniciador que eu conectei (pegue essa configuração e isso ...), tudo será exatamente o oposto. O iniciador terá um arquivo chamado spring.factories . Neste arquivo, prescrevemos qual configuração deste iniciador deve ser ativada para todos que o baixaram. Um pouco mais tarde, explicarei como exatamente isso funciona no Spring Boot - em algum momento, ele começa a verificar todos os frascos e a procurar o arquivo spring.factories.

 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ironbank.IronConfiguration 


 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


Agora, tudo o que resta fazer é conectar o iniciador do projeto.

 compile project(':iron-starter') 


No maven, da mesma forma - você precisa registrar a dependência.

Lançamos nosso aplicativo. O corvo deve decolar no momento em que sobe, embora não tenhamos feito nada no aplicativo em si. Em termos de infraestrutura, é claro que escrevemos e configuramos o iniciador. Mas do ponto de vista do desenvolvedor, acabamos de conectar a dependência e a configuração apareceu - o corvo voou. Tudo como queríamos.

Isso não é mágico. Inversão de controle não deve ser mágica. Assim como o uso do Spring não deve ser mágico. Sabemos que essa é uma estrutura principalmente para inversão de controle. Como há inversão de controle para seu código, também há inversão de controle para módulos.

@SpringBootApplication em torno da cabeça


Lembre-se do momento em que construímos o contexto principal com nossas mãos. Escrevemos o new AnnotationConfigApplicationContext e passamos algumas configurações para a entrada, que era uma classe java. Agora também escrevemos SpringApplication.run e passamos a classe para lá, que é a configuração, mas é marcada com outra anotação bastante poderosa @SpringBootApplication , que carrega o mundo inteiro.

 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { … } 

Primeiramente, dentro existe o @Configuration , ou seja, é uma configuração. Você pode escrever @Bean e, como sempre, registrar os beans.

Em segundo lugar, o @ComponentScan está acima dele. Por padrão, ele verifica absolutamente todos os pacotes e subpacotes. Portanto, se você começar a criar serviços no mesmo pacote ou em seus @Service - @Service , @RestController - eles serão verificados automaticamente, pois a configuração principal inicia o processo de verificação.

Na verdade, @SpringBootApplication não faz nada de novo. Ele simplesmente compilou todas as práticas recomendadas que estavam nos aplicativos Spring, então agora isso é algum tipo de composição de anotação, incluindo o @ComponentScan .

Além disso, ainda existem coisas que não existiam antes - @EnableAutoConfiguration . Esta é a classe que prescrevi na primavera. Fábricas.
@EnableAutoConfiguration , se você olhar, carrega @Import :

 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";  Class<?>[] exclude() default {};  String[] excludeName() default {}; } 

A principal tarefa do @EnableAutoConfiguration é fazer a importação da qual queremos nos livrar em nosso aplicativo, porque sua implementação deveria ter nos forçado a escrever o nome de alguma classe do iniciador. E só podemos descobrir a partir da documentação. Mas tudo deve ser por si só.

Você precisa prestar atenção a esta aula. Termina com ImportSelector . No Spring regular, escrevemos Import(Some Configuration.class) alguma configuração e ela carrega, como todos os seus dependentes. Este é ImportSelector , não é uma configuração. ImportSelector todos os nossos iniciantes em contexto. Ele processa a anotação @EnableAutoConfiguration de spring.factories, que seleciona quais configurações carregar, e adiciona os beans que especificamos em IronConfiguration ao contexto.



Como ele faz isso?

Primeiro, ele usa uma classe de utilidade direta, SpringFactoriesLoader, que analisa spring.factories e carrega tudo, desde o que é solicitado. Ele tem dois métodos, mas eles não são muito diferentes.



O Spring Factories Loader existia no Spring 3.2, mas ninguém o usou. Aparentemente, foi escrito como um desenvolvimento potencial da estrutura. E assim cresceu para Spring Boot, onde existem muitos mecanismos usando a convenção spring.factories. Mostraremos ainda que, além da configuração, você também pode escrever em spring.factories - ouvintes, processadores incomuns, etc.

 static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl ) static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl ) 

É assim que a inversão de controle funciona. open closed principle, - - . ( , ). , spring.factories. , . Spring Boot , , spring.factories.

. , Spring, , , , org.springframework.boot:spring-boot-autoconfigure , META-INF/spring.factories EnableAutoConfiguration , ( , , 80).

 spring-boot-autoconfigure.jar/spring.factories</b> org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\ ... 

, , Spring Boot, jar- (jar Spring Boot), spring.factories, 90 . , , CacheAutoConfiguration , — , :

 for (int i = 0; i < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; 

, - , ( spring.factories). .

 private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC,    GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE,    EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST,  HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE,     JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE,  CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS,      RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE,   CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE,     SimpleCacheConfiguration.class); mappings.put(CacheType.NONE,       NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); } 

, .



. :



. — , , , open closed principle — spring.factories, . , - .

, Spring Boot, — 90 . 30 , Spring Boot.

, . 2013 , Spring 4, , @Conditional , conditions, , true false . , . java- Spring , conditional. , , conditional false , .

. , , , - .



.

1.2.


. — , . , , .



listener, , , . .

:

 @Configuration <b>@ConditionalOnProduction</b> public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 

, ? , : « Windows , , Windows, ». conditional.

, : , : « ». condition Spring Boot.

 @Retention(RUNTIME) @Conditional(OnProductionCondition.class) public @interface ConditionalOnProduction { } 

- :

 public class OnProductionCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {   return JOptionPane.showConfirmDialog(parentComponent: null, " ?") == 0; } } 

Vamos tentar.



Nós aumentamos o serviço, clique em sim na janela e o corvo voa (um ouvinte é criado).
Começamos novamente, respondemos Não, o corvo não voa.

Portanto, a anotação @Conditional(OnProductionCondition.class)se refere à classe recém escrita, onde existe um método que deve retornar trueou false. Esse ar condicionado pode ser inventado independentemente, o que torna a aplicação muito dinâmica, permitindo que ela funcione de maneira diferente em diferentes condições.

Pazzler


Então @ConditionalOnProductionnós escrevemos. Podemos fazer várias configurações, colocá-las em condição. Suponha que tenhamos nossa própria condição e que seja popular @ConditionalOnProduction. E há, por exemplo, 15 grãos necessários apenas na produção. Marquei-os com esta anotação.

Pergunta: a lógica que descobre se é produção ou não, quantas vezes deve funcionar?
Que diferença isso funciona? Bem, talvez essa lógica seja cara, leva tempo e tempo é dinheiro.

Como ilustração, criamos um exemplo:

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

: , . - — , .

. — , , .



, ( OnProductionCondition.class , — ). . , , , - . 5 ?

— 300 400. - . , , . — .

. ( @Component , @Configuration @Service ), . , .

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } 

, .

 @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

, , . , - , , 300. , , . 400.

: ? :



, . : -, , , . , , .

1.3.


Continuamos a desenvolver nossa iniciação. De alguma forma, devemos especificar o voo do corvo.



Em que arquivo prescrevemos coisas para o iniciante? Os iniciantes trazem uma configuração na qual existem beans. Como esses beans são configurados? Onde eles obtêm a fonte de dados, o usuário etc. Naturalmente, eles têm padrões para todas as ocasiões, mas como eles permitem que isso seja redefinido? Existem duas opções: application.propertiese application.yml. Lá, você pode inserir algumas informações que ainda serão lindamente preenchidas automaticamente no IDEA.

O que torna a nossa partida pior? Quem o usa também deve saber em quais endereços o corvo está voando - precisamos fazer uma lista de destinatários. Este é o primeiro.

— , , . listener-, . I.e. , , . , , .

— , , , , .

. . - ?

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

, , . @ConditionalOnProperty . , , property property - , application.yml. @ConfigurationalProperty , .


, property . , , application.yml , .

property «». , .

 @ConfigurationProperties("") public class RavenProperties { List<String> ; } 

IDEA , - :



, ( Maven , « »). .

 subproject { dependencies { compileOnly 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot: spring-boot-starter' } } 

IDEA, .

, . , annotation processor. , - . , Lombok annotation processor, — , .

property, application properties? JSON-, IDEA . property, IDEA . , property, , IDEA , :

  • JSON ;
  • annotation processor Spring Boot, JSON- . properties , Spring Boot, , property holder. annotation processor Spring Boot , @ConfigurationalProperties , property, JSON. , , JSON .

@EnableConfigurationProperties , .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction public RavenListener ravenListener() { return new RavenListener(); } } 

, , , ( property, ).

:

  • @EnableConfigurationProperties indicando quais propriedades;
  • @ConfigurationalProperties dizendo que prefixo.

E não se deve esquecer getters e setters. Eles também são importantes, caso contrário nada funciona - a ação não sobe.

Como resultado, temos um arquivo que pode, em princípio, ser escrito manualmente. Mas ninguém gosta de escrever manualmente.

 { "hints": [], "groups": [ { "sourceType": "com.ironbank.RavenProperties", "name": "", "type": "com.ironbankRavenProperties" } ], "properties": [ { "sourceType": "com.ironbank.RavenProperties", "name": ".", "type": "java.util.List<java.lang.String>" } ] } 

Endereço para raven


Fizemos a primeira parte da tarefa - obtivemos algumas propriedades. Mas ninguém está relacionado a essas propriedades ainda. Agora eles precisam ser definidos como condição para criar nosso ouvinte.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener() { return new RavenListener(); } } 


Adicionamos mais uma condição: um corvo deve ser criado apenas com a condição de que alguém diga para onde voar.
Agora vamos escrever para onde voar em application.yml.

 spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---:   -  : -: ,   : true 

Resta prescrever em lógica que ele voe para onde foi informado.

Para fazer isso, podemos gerar um construtor. O novo Spring possui uma injeção de construtor - esta é a maneira recomendada. Eugene gosta de @Autowiredfazer tudo aparecer no aplicativo através da reflexão. Adoro seguir as convenções que a Spring oferece:

 @RequiredArgsConstructor public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

Mas não é grátis. Por um lado, você obtém um comportamento verificável, por outro, algumas hemorróidas.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Em nenhum lugar existe @Aurowired, com o Spring 4.3 você não pode instalá-lo. Se houver um único construtor, é @Aurowired. Nesse caso, é usada uma anotação @RequiredArgsConstructor, que gera um único construtor. Isso é equivalente a esse comportamento:

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

Spring Lombok. Jurgen Holler, 2002 80% Spring, @Aurowired , ( ).

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Aurowired public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

? RavenProperties Java-. @Aurowired , .

, . , , , .

1.4.




Acontece que você precisa personalizar o comportamento do iniciador. Por exemplo, nós temos nosso próprio corvo preto. E precisamos de um branco que fume, e queremos enviá-lo para que as pessoas vejam fumaça no horizonte.

Vamos passar da alegoria para a vida real. O iniciante me trouxe um monte de beans de infraestrutura, e isso é ótimo. Mas eu não gosto de como eles estão configurados. Entrei nas propriedades do aplicativo e mudei algo por lá, e agora gosto de tudo. Mas há situações em que as configurações são tão complicadas que é mais fácil registrar a fonte de dados do que tentar descobrir as propriedades do aplicativo. Ou seja, queremos registrar a fonte de dados na lixeira recebida do iniciador. O que acontecerá então?

Eu mesmo registrei algo, e o iniciante me trouxe minha fonte de dados. Eu tenho dois agora? Ou alguém esmagará um (e qual?)

, - , , , . , .

, :

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

, @ConditionalOnMissingBean , . , , , .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnMissingBean</b> public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Se você abrir a maioria dos iniciantes, verá que cada compartimento, cada configuração está pendurada com um pacote de anotações. Estamos apenas tentando fazer um análogo.

Ao tentar lançar o corvo não foi, mas o Evento apareceu, o que escrevemos em nosso novo ouvinte - MyRavenListener.



Existem dois pontos importantes aqui.
O primeiro ponto é que ficamos viciados em nosso ouvinte existente e não escrevemos nenhum ouvinte lá:

 @Component public class MyRavenListener implements ApplicationListener { public MyRavenListener(RavenProperties ravenProperties) { super(ravenProperties); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println("event = " + event); }); } } 

Segundo - fizemos com a ajuda do componente. Se o fizermos em uma configuração Java, ou seja, registraria a mesma classe que um bean de configuração, nada funcionaria para nós.

Se eu limpar extendse apenas criar algum tipo de ouvinte de aplicativo, @ConditionalOnMissingBeannão funcionará. Mas desdea classe também é chamada, quando tentamos criá-la, podemos escrever ravenListener- exatamente como tínhamos em nossa configuração. Acima, focamos no fato de que o nome do bean na configuração Java será pelo nome do método. E, neste caso, criamos uma lixeira chamada ravenListener.

? Spring Boot , . , , , . - , , . . , , , , , ( , , ). , , , , .

Além disso, um conflito de feijão é uma boa situação porque você o vê. Se especificarmos os mesmos nomes de bin, não teremos conflito. Um bean simplesmente substituirá outro. E você entenderá por um longo tempo onde está o que estava lá. Por exemplo, se criarmos algum tipo de fonte de dados @Bean, ela substituirá a fonte de dados existente @Bean.
A propósito, se o acionador de partida carrega o que você não precisa, basta fazer uma lixeira com o mesmo ID e pronto. É verdade que, se o iniciador de alguma versão alterar o nome do método, é isso, sua lixeira será novamente dois.

ConditionalOnPuzzler


Temos @ConditionalOnClass, @ConditionalOnMissingBeanpode haver escrevendo classes. Como exemplo, considere a configuração de execução.

Há sabão, há uma corda - penduramos na forca. Há uma cadeira e uma corrente - é lógico colocar uma pessoa em uma cadeira. Há uma guilhotina e um bom humor - isso significa que você precisa cortar cabeças.

 @Configuration public class  { @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new ("..."); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  c() { return new (" "); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new (" "); } } 

Como vamos executar?

Pergunta: Como as anotações de tipo podem @ConditionalOnMissingClassfuncionar em geral ?

Suponha que eu tenha um método que crie uma forca. Mas uma caixa de forca só deve ser criada se houver sabão e corda. Mas não há sabão. Como posso entender que não há sabão ou apenas corda. O que acontece se eu tentar ler anotações de um método, e essas anotações se referirem a classes que não são? Posso fazer essas anotações?

Opções de resposta:
  • ClassDefNotFound? , . - , ClassDefNotFound , reflection- , conditional as long as;
  • , . reflection . , .
  • ;
  • .

: , . reflection . exception, , — . reflection? , , , , — ClassDefNotFound .

ASM. , reflection — , Spring -, , . , , , @Conditional , . . ASM — , , . , , .

Juergen Hoeller , , , OnMissingClass , (String). , , ASM . , , .

1.5.




Precisávamos de outra propriedade - a capacidade de ativar ou desativar o corvo manualmente. Para não enviar ninguém garantido. Esta é a última condição que lhe mostramos.

Nosso titular não dá nada, exceto um corvo. Portanto, você pode perguntar: por que ser capaz de ligar / desligar, você pode simplesmente não aceitá-lo? Mas, na segunda parte, coisas úteis adicionais serão incluídas neste iniciador. Especificamente, um corvo pode não ser necessário - é caro, pode ser desligado. Ao mesmo tempo, não é muito bom remover o ponto final para onde enviá-lo - parece uma muleta.

Portanto, faremos tudo @ConditionalOnProperty(".").

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

E ele jura para nós que isso não pode ser feito: anotação duplicada. O problema é que, se tivermos uma anotação com alguns parâmetros, ela não poderá ser repetida. Não podemos fazer isso em duas propriedades.

Temos métodos para esta anotação, existe String e esta é uma matriz - você pode especificar várias propriedades lá.

 @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; } 

E tudo está bem, até você tentar personalizar o valor de cada elemento nessa matriz separadamente. Temos uma propriedade , que deve ser falsee é outra propriedade, que deve ter stringum certo valor. Mas você pode especificar apenas um valor em todas as propriedades.

Ou seja, você não pode fazer isso:

 @ConditionalOnProduction @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".",  havingValue="true") @ConditionalOnProperty(name = ".",havingValue="false") public IronBankApplicationListener applicationListener() { ... } 

 @ConditionalOnProduction @ConditionalOnProperty( name = {    ".",    ".",    "." }, havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... } 

— .

, property, , : AllNestedConditions AnyNestedCondition .



, , . . — , . . .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnRaven public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

@Conditional() - .

 @Retention(RUNTIME) @Conditional({OnRavenCondional.class}) public @interface CondionalOnRaven { } 


.

 public class OnRavenCondional implements Condition { } 

- , , :

 public class CompositeCondition extends AllNestedConditions { @ConditionalOnProperty(  name = ".",  havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty(  name = ".enabled",  havingValue = "true",  matchIfMissing = true) public static class OnRavenEnabled { } ... } 

Conditional , — AllNestedConditions , AnyNestedCondition — , . I.e.em vez disso @Condition, devemos especificar:

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } } 

Nesse caso, você precisa criar um construtor dentro.

Agora temos que criar classes estáticas aqui. Nós criamos algum tipo de classe (vamos chamá-lo de R).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } public static class R {} } 

Nós fazemos o nosso valor (deve ser exatamente true).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(".") public static class R {} @ConditionalOnProperty(value= ".", havingValue = "true") public static class C {} } 

Para repetir isso, lembre-se do nome da classe. O Spring possui boas docas Java. Você pode deixar o IDEA, ler o dock Java e entender o que precisa ser feito.

Montamos a nossa @ConditionalOnRaven. Em princípio, você pode agrupar ambos @ConditionalOnProduction, e , e @ConditionalOnMissingBean, mas agora não faremos isso. Basta ver o que aconteceu.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnRaven @ConditionalOnMissingBean public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Na ausência de um corvo não deve voar. Ele não voou.
Não quero apostar , porque precisamos primeiro fazer um preenchimento automático - esse é um dos nossos requisitos.

 @Data @ConfigurationalProperties("") public class RavenProperties { List<String> ; boolean ; } 

Só isso. false , true
application.yml:

 jpa.hibernate.ddl-auto: validate ironbank: ---: -  : : ,   : true 

, .

, repeatable. Java. .

, , .




Minuto de publicidade. 19-20 Joker 2018, « [Joker Edition]» , «Micronaut vs Spring Boot, ?» . Em geral, haverá muitos outros relatórios interessantes e dignos de nota no Joker. Os ingressos podem ser adquiridos no site oficial da conferência.

E também temos uma pequena pesquisa para você!

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


All Articles