Usando Condicional no Spring

Neste artigo, quero descrever uma anotação condicional muito útil e frequentemente usada e a interface Condition .


O contexto da primavera é um enorme contêiner de vários feijões, tanto a própria primavera quanto os personalizados. Você sempre deseja ter ferramentas de gerenciamento flexíveis para esse zoológico de bin. A anotação @Conditional é criada apenas para isso.


A maneira mais comum de gerenciar o contexto da primavera é através de perfis. Eles permitem controlar rápida e facilmente a criação de beans. Mas, às vezes, mais ajustes podem ser necessários.


Por exemplo, durante o teste, surge um problema: um teste de unidade na máquina do desenvolvedor requer uma bandeja do tipo X para o seu trabalho, ao executar o mesmo teste no servidor de construção, a bandeja Y é necessária e a bandeja Z é necessária na produção. a decisão. Assim como ocorre com freqüência em equipes não sincronizadas, alguém não tem tempo para concluir sua revisão dentro do prazo, e sua funcionalidade está pronta. É necessário se adaptar a essas condições e mudar o comportamento. Ou seja, adicione a capacidade de alterar o contexto do aplicativo sem recompilar, por exemplo, alterando apenas um parâmetro na configuração.


Considere esta anotação em mais detalhes. Acima de cada posição no código-fonte, podemos adicionar @Conditional e, quando criada, a mola verifica automaticamente as condições especificadas nesta anotação.


Na documentação oficial, é declarado assim:


@Target(value={TYPE,METHOD}) @Retention(value=RUNTIME) @Documented public @interface Conditional 

Ao mesmo tempo, você precisa transferir um conjunto de condições para ele:


 Class<? extends Condition>[] 

Onde Condicional é a interface funcional que contém o método


 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) 

Vamos verificar como isso funciona na prática, usando um exemplo vivo. Nosso aplicativo possui interfaces na forma de sabão / descanso - serviços e na forma de JMS. Mas os administradores não tiveram tempo para preparar a infraestrutura apropriada a tempo - não podemos usar o JMS.
Há alguma configuração java para JMS em nosso projeto:


 @Configuration public class JmsConfig { ... } 

O Spring encontra essa configuração e começa a inicializá-la. Em seguida, todos os outros beans dependentes são exibidos, por exemplo, lendo da fila. Para desativar a criação dessa configuração, usaremos a anotação derivada condicional - ConditioanalOnProperty


 @ConditionalOnProperty( value="project.mq.enabled", matchIfMissing = false) @Configuration public class JmsConfig { ... } 

Aqui, informamos o contexto ao construtor que criamos essa configuração apenas se houver um valor positivo da constante project.mq.enabled no arquivo de configurações.
Agora vamos passar para os beans dependentes e marcá-los com a anotação ConditioanalOnBean , que impedirá a primavera de criar beans que dependem de nossa configuração.


 @ConditionalOnBean(JmsConfig.class) @Component public class JmsConsumer { ... } 

Assim, com um único parâmetro, podemos desativar os componentes do aplicativo que não precisamos e adicioná-los ao contexto, alterando a configuração.


Juntamente com a estrutura, existe um grande número de anotações prontas, cobrindo 99% das necessidades do desenvolvedor (que serão descritas mais adiante neste artigo). Mas e se você precisar lidar com alguma situação específica. Para fazer isso, você pode adicionar sua própria lógica personalizada à primavera.


Suponha que tenhamos algum bean - SuperDBLogger , que queremos criar apenas se houver uma anotação @Loggable sobre qualquer uma de nossas caixas . Como ficará no código:


 @Component @ConditionalOnLoggableAnnotation public class SuperDBLogger 

Considere como a anotação @ConditionalOnLoggableAnnotation funciona :


 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(OnLoggableAnnotation.class) public @interface ConditionalOnLoggableAnnotation { } 

Não precisamos de mais parâmetros, agora vamos para a própria lógica - o conteúdo da classe OnLoggableAnnotation . Nele, redefinimos o método de correspondências , no qual implementamos a pesquisa de beans marcados em nosso pacote.


 public class OnLoggableAnnotation implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassPathScanner scanner = new ClassPathScanner(); scanner.addIncludeFilter(new AnnotationTypeFilter(Loggable.class)); Set<BeanDefinition> bd = scanner.findInPackage("ru.habr.mybeans"); if (!bd.isEmpty()) return true; return false; } } 

Assim, criamos uma regra segundo a qual o Spring agora cria o SuperDBLogger . Para os devotos do SpringBoot, os criadores da estrutura criaram o SpringBootCondition , que é o sucessor da Condição . Difere na assinatura do método redefinido:


 public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata); 

Ou seja, além da resposta, precisamos de uma lixeira ou não, você pode adicionar uma mensagem, que pode ser vista nos logs da primavera, entendendo por que a lixeira foi criada ou não.


Várias condições podem ser combinadas para criar condições mais complexas; esses mecanismos fornecem as classes AnyNestedCondition, AllNestedConditions e NoneNestedConditions . Suponha que desejemos criar duas condições para que, quando uma delas for executada, nosso bean seja criado. Para fazer isso, crie sua própria classe e herde-a de AnyNestedCondition .


 public class AnnotationAndPropertyCondition extends AnyNestedCondition { public AnnotationAndPropertyCondition() { super(REGISTER_BEAN); } @ConditionalOnProperty(value = "db.superLogger") static class Condition1 {} @ConditionalOnLoggableAnnotation static class Condition2 {} } 

A classe não precisa ser marcada adicionalmente com nenhuma anotação; a própria mola a encontrará e a processará corretamente. O usuário precisa apenas indicar em que estágio da configuração as condições são atendidas: ConfigurationPhase .REGISTER_BEAN - ao criar beans regulares, ConfigurationPhase .PARSE_CONFIGURATION - ao trabalhar com configurações (ou seja, para posições marcadas com anotação @Configuration ).


Da mesma forma, para as classes AllNestedConditions e NoneNestedConditions , a primeira monitora todas as condições, a segunda garante que nenhuma condição seja atendida.


Além disso, para verificar várias condições, você pode passar várias classes com condições para @Conditional . Por exemplo, @Conditional ({OnLoggableAnnotation.class, AnnotationAndPropertyCondition.class}) . Ambos devem retornar true para que a condição seja satisfeita e o bean seja criado.


Como mencionei acima, já existem muitas soluções prontas com mola, as quais são apresentadas na tabela abaixo.


AnotaçãoDescrição do produto
ConditionalOnBeanA condição será atendida se o bean desejado estiver presente no BeanFactory.
ConditionalOnClassA condição é satisfeita se a classe necessária estiver no caminho de classe.
ConditionalOnCloudPlatformA condição é atendida quando uma plataforma específica está ativa.
ConditionalOnExpressionA condição é verdadeira quando a expressão SpEL retorna um valor positivo.
ConditionalOnJavaA condição é atendida quando o aplicativo é iniciado com uma versão específica da JVM.
ConditionalOnJndiA condição será satisfeita apenas se um recurso específico estiver disponível através do JNDI.
ConditionalOnMissingBeanA condição será atendida se o bean desejado estiver ausente no BeanFactory.
ConditionalOnMissingClassA condição é verdadeira se a classe necessária não estiver no caminho de classe.
ConditionalOnNotWebApplicationA condição é verdadeira se o contexto do aplicativo não for um contexto da web.
ConditionalOnPropertyA condição é satisfeita se os parâmetros necessários forem especificados no arquivo de configurações.
ConditionalOnResourceA condição é satisfeita se o recurso desejado estiver presente no caminho de classe.
ConditionalOnSingleCandidateA condição será atendida se o bean da classe especificada já estiver contido no BeanFactory e for o único.
ConditionalOnWebApplicationA condição é verdadeira se o contexto do aplicativo for um contexto da web.

Todos eles podem ser aplicados juntos em uma definição de bean.


Portanto, o @Conditional é uma ferramenta de configuração de contexto bastante poderosa, tornando os aplicativos ainda mais flexíveis. Mas vale a pena considerar o fato de que você precisa usar essa anotação com cuidado, pois o comportamento do contexto não se torna tão óbvio quanto ao usar perfis - com um grande número de posições configuradas, você pode rapidamente se confundir. É aconselhável documentar com cuidado e registrar sua aplicação no seu projeto, caso contrário, o suporte ao código causará dificuldades.

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


All Articles