Processo de seleção de implementações de interfaces no Spring: Entendendo e gerenciando escolhas
Tags: java, spring, solid
Published: 2023-04-26 20:03:48

O Spring conta com a feature do IOC container que nos provê várias anotações para que possamos dizer a ele qual método ou classe ele irá gerenciar, essas anotações são importantes para que possamos aplicar conceitos de Design Patterns.

Por exemplo, em alguns códigos usando Spring é comum encontrarmos uma classe em que se é instanciado uma interface e essa interface contém uma implementação. Como no exemplo abaixo onde a classe ``CurriculoService`` instancia uma interface ``GeraCurriculo``

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=CurriculoService.java %}

Mas e se acontecesse dessa interface ter duas implementações como o caso abaixo, o que aconteceria? 

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=GeraCurriculoPdfService.java %}

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=GeraCurriculoWordServiceSemPrimary.java %}

O Spring lançaria a exceção **NoUniqueBeanDefinitionException**, por quê? Porque o Spring não seria capaz de determinar qual classe utilizar, não basta dizer ao Spring o que gerenciar, em alguns cenários será necessário dizer a ele qual implementação usar. Para isso separei três maneiras das quais podemos dizer ao Spring qual implementação utilizar.

## Através da anotação Primary

O simples ato de anotar uma classe com ``@Primary`` irá dar prioridade aquela classe em caso de haver múltiplas implementações ou Beans, por exemplo caso eu queira que a classe ``GeraCurriculoWordService`` fosse chamada toda vez que eu proveja a interface ``GeraCurriculo`` basta apenas isso: 

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=GeraCurriculoWordServiceComPrimary.java %}

## Através da anotação @Qualifier

Com essa anotação podemos "nomear" as classes e ao prover uma interface fazer referencia a ela, ainda usando nosso exemplo poderíamos dar prioridade para nossa classe ``GeraCurriculoPdfService`` da seguinte maneira.

Primeiramente anotamos nossa classe com o ``@Qualifier`` e passamos como parâmetro um identificador, no caso aqui selecionei o próprio nome da classe:

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=GeraCurriculoPdfServiceComQualifier.java %}

E por fim, ao provermos nossa interface podemos dizer para usar a implementação em especifico também com a anotação ``@Qualifier`` como no exemplo abaixo:

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=CurriculoServiceInjetandoPdfService.java %}

Note que desta maneira não ficamos preso a uma implementação como na anotação ``@Primary`` com o ``@Qualifier`` podemos ter mais dinamismo ao prover nossa interface e decidir a implementação. Isso não anula a ``@Primary`` mesmo usando qualifiers ainda poderíamos deixar uma implementação como default em caso de não especificarmos nenhuma classe ao provermos a interface.

## Selecionando a implementação dinamicamente

Vamos supor agora que o usuário que está usando o sistema selecionará o formato de seu currículo, eu preciso ter uma maneira de selecionar a implementação em runtime.

Para isso podemos usar um dos recursos menos conhecidos do Spring. Como vimos no início do artigo quando temos apenas uma implementação de uma interface o Spring sabe injetar automaticamente essa implementação quando necessário. Mas o Spring também nos dá a capacidade de coletar todos os beans que são implementações de uma interface específica. Assim podemos mapear as implementações para usá-las posteriormente como no exemplo abaixo:

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=ListaImplementacaoGeraCurriculo.java %}

Veja que no construtor da classe passamos como parâmetro uma lista da interface, o Spring no entanto coleta todas as implementações existentes e popula automaticamente essa lista para nós, tornando fácil mapear da maneira como queremos, como foi feito na linha 8 do exemplo acima onde mapeamos o tipo do arquivo que a implementação ligado a própria implementação.

Para resolvermos o problema citado sobre gerar um currículo dinamicamente poderíamos fazer assim:

{% embed https://gist.github.com/EduardoCandido/90dd105c03e4851ba5f1fa5a30b4b2cc file=CurriculoServiceDinamico.java %}

Veja que agora a classe ``CurriculoService`` está injetando a classe onde mapeamos a lista de implementações da interface ``GeraCurriculo``. Então no método da linha 8 ``getCurriculo`` podemos nos preocupar em passar apenas o tipo do arquivo que queremos em vez de termos métodos para cada implementação, na linha 9 selecionamos a implementação que bate com o parâmetro informado e por fim temos acesso a nossa implementação e seus métodos.

Em resumo as anotações ``@Primary`` e ``@Qualifier`` são úteis quando temos várias implementações e precisamos selecionar uma em específico tendo a anotação ``@Qualifier`` nos permitindo maior especificidade. Vimos também que o Spring oferece uma abordagem mais dinâmica através da coleta de todas as implementações de uma interface, permitindo que você escolha qual implementação usar em tempo de execução. Isso é útil quando você precisa selecionar a implementação com base em informações do usuário ou em condições variáveis.

Caso queira ver a implementação do código para selecionar uma classe dinamicamente você pode acessar nesse link do [github](https://github.com/EduardoCandido/laboratorio-blog/tree/main/spring-injecao-dinamica)