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.
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.