O que exatamente é injeção de campo e como evitá-la?

130

Li em alguns posts sobre o Spring MVC e Portlets que a injeção de campo não é recomendada. Pelo que entendi, a injeção de campo é quando você injeta um Bean @Autowiredassim:

@Component
public class MyComponent {
    @Autowired
    private Cart cart;
}

Durante minha pesquisa, eu também li sobre injeção de construtores :

@Component
public class MyComponent {
    private final Cart cart;

    @Autowired
    public MyComponent(Cart cart){
       this.cart = cart;
    }
}

Quais são as vantagens e as desvantagens de ambos os tipos de injeção?


EDIT 1: Como esta questão é marcado como duplicata de esta pergunta eu verifiquei. Porque não há exemplos de código nem na pergunta nem nas respostas que não está claro para mim se estou correto com meu palpite sobre o tipo de injeção que estou usando.

T. Jung
fonte
3
Se a injeção de campo é tão ruim quanto você descreve, por que o Spring permite? A injeção de campo tem seus próprios benefícios, ao tornar o código mais legível e menos detalhado. Se você é disciplinado o suficiente em sua codificação, pode ter certeza de que as coisas não quebrariam, mesmo se você usar injeção de campo.
cinzas
@ashes Porque era um recurso interessante na época, e as implicações não foram totalmente pensadas. A mesma razão que Date(int,int,int)existe.
você precisa saber é o seguinte

Respostas:

225

Tipos de injeção

Existem três opções para como as dependências podem ser injetadas em um bean:

  1. Através de um construtor
  2. Através de setters ou outros métodos
  3. Através da reflexão, diretamente nos campos

Você está usando a opção 3. É isso que está acontecendo quando você usa @Autowireddiretamente em seu campo.


Diretrizes de injeção

Uma diretriz geral, recomendada pela Spring (consulte as seções sobre DI baseada em construtor ou DI baseada em setter ) é a seguinte:

  • Para dependências obrigatórias ou ao apontar para imutabilidade, use injeção de construtor
  • Para dependências opcionais ou mutáveis, use injeção de setter
  • Evite injeção de campo na maioria dos casos

Desvantagens da injeção em campo

As razões pelas quais a injeção de campo é mal vista são as seguintes:

  • Você não pode criar objetos imutáveis, como na injeção de construtor
  • Suas aulas têm um acoplamento estreito com seu contêiner DI e não podem ser usadas fora dele
  • Suas aulas não podem ser instanciadas (por exemplo, em testes de unidade) sem reflexão. Você precisa do contêiner de DI para instancia-los, o que torna seus testes mais parecidos com testes de integração
  • Suas dependências reais estão ocultas do lado de fora e não são refletidas na sua interface (construtores ou métodos)
  • É realmente fácil ter dez dependências. Se você estivesse usando injeção de construtor, teria um construtor com dez argumentos, o que indicaria que algo é suspeito. Mas você pode adicionar campos injetados usando a injeção de campo indefinidamente. Ter muitas dependências é uma bandeira vermelha de que a classe geralmente faz mais de uma coisa e que pode violar o Princípio de Responsabilidade Única.

Conclusão

Dependendo de suas necessidades, você deve usar principalmente a injeção de construtor ou alguma mistura de injeção de construtor e setter. A injeção em campo tem muitas desvantagens e deve ser evitada. A única vantagem da injeção de campo é que é mais conveniente escrever, o que não supera todos os contras.


Leitura adicional

Eu escrevi um artigo de blog sobre o motivo pelo qual a injeção de campo geralmente não é recomendada: Injeção de dependência de campo considerada prejudicial .

Vojtech Ruzicka
fonte
12
Geralmente, não é uma boa ideia e não é bom dizer ao mundo "a injeção de campo deve ser evitada". Mostre prós e contras e deixe que os outros se decidam;) Muitas pessoas têm outras experiências e têm uma maneira própria de encarar as coisas.
Dieter #
6
Pode ser o caso aqui, mas há outros casos em que a comunidade chegou a um consenso geral para desencorajar algo. Tomemos, por exemplo, notação húngara.
Jannik
Você dá alguns pontos positivos como visibilidade de testabilidade e dependência, mas não estou de acordo com todos. A injeção do construtor não tem desvantagens? Ter 5 ou 6 campos para injetar na classe que executa composições reais de chamada pode ser desejável. Também discordo de você com imutabilidade. Ter campos finais não é obrigatório para ter uma classe imutável. É preferível. O que é muito diferente.
Davidxxx
Eu acho que você quis dizer "Para dependências obrigatórias ou quando se aponta para a imutabilidade "
Alex Terreaux
1
Eu estava me referindo ao link no início da resposta que inclui links para os documentos primavera
Vojtech Ruzicka
47

Essa é uma das discussões sem fim no desenvolvimento de software, mas os principais influenciadores do setor estão ficando mais opinativos sobre o assunto e começaram a sugerir a injeção de construtores como a melhor opção.

Injeção de construtor

Prós:

  • Melhor testabilidade . Você não precisa de nenhuma biblioteca de simulação ou um contexto do Spring em testes de unidade. Você pode criar um objeto que deseja testar com a nova palavra-chave. Tais testes são sempre mais rápidos porque não dependem do mecanismo de reflexão. ( Esta pergunta foi feita 30 minutos depois. Se o autor tivesse usado injeção de construtor, não teria aparecido).
  • Imutabilidade . Depois que as dependências são definidas, elas não podem ser alteradas.
  • Código mais seguro . Após a execução de um construtor, seu objeto está pronto para uso, pois você pode validar tudo o que foi passado como parâmetro. O objeto pode estar pronto ou não, não há estado intermediário. Com a injeção de campo, você introduz uma etapa intermediária quando o objeto é frágil.
  • Expressão mais limpa de dependências obrigatórias . A injeção de campo é ambígua nesse assunto.
  • Faz os desenvolvedores pensarem no design . dit escreveu sobre um construtor com 8 parâmetros, que na verdade é o sinal de um projeto ruim e o antipadrão de objetos de Deus . Não importa se uma classe tem 8 dependências em seu construtor ou em campos, ela está sempre errada. As pessoas são mais relutantes em adicionar mais dependências a um construtor do que através de campos. Funciona como um sinal para o seu cérebro de que você deve parar um pouco e pensar na sua estrutura de código.

Contras:

  • Mais código (mas os IDEs modernos aliviam a dor).

Basicamente, a injeção de campo é o oposto.

Daniel Olszewski
fonte
1
testabilidade, sim, foi um pesadelo para mim zombar do feijão injetado no campo. Uma vez, usei a injeção de contsructor, não preciso zombar desnecessariamente
kenobiwan
25

Questão de gosto. É sua decisão.

Mas posso explicar por que nunca uso injeção de construtor .

  1. Eu não quero implementar um construtor para todos os meus @Service, @Repositorye @Controllerfeijão. Quero dizer, existem cerca de 40 a 50 grãos ou mais. Toda vez que eu adicionasse um novo campo, eu teria que estender o construtor. Não. Eu não quero e não preciso.

  2. E se o seu Bean (Serviço ou Controlador) exigir que muitos outros beans sejam injetados? Um construtor com 4 ou mais parâmetros é muito feio.

  3. Se eu estiver usando CDI, o construtor não me preocupa.


EDIT # 1 : Vojtech Ruzicka disse:

classe tem muitas dependências e provavelmente está violando o princípio de responsabilidade única e deve ser refatorada

Sim. Teoria e realidade. Aqui está um exemplo:DashboardController mapeado para o caminho único *:8080/dashboard.

Minhas DashboardController coleta muitas informações de outros serviços para exibi-las em uma página de visão geral do painel / sistema. Eu preciso desse controlador único. Portanto, tenho que proteger apenas esse caminho (autenticação básica ou filtro de função de usuário).

EDIT # 2 : Como todos estão focados nos 8 parâmetros do construtor ... Este foi um exemplo do mundo real - um código legado de clientes. Eu mudei isso. A mesma argumentação se aplica a mim para mais de 4 parâmetros.

É tudo sobre injeção de código, não construção de instância.

dieter
fonte
34
Um construtor muito feio com 8 dependências é realmente impressionante, pois é uma bandeira vermelha que algo está errado, a classe tem muitas dependências e provavelmente está violando o princípio de responsabilidade única e deve ser refatorada. É realmente uma coisa boa.
Vojtech Ruzicka
6
@VojtechRuzicka não é legal, com certeza, mas às vezes você não pode evitá-lo.
Dieter #
4
Eu diria que uma regra prática 3, muito menos 40-50, dependências para qualquer classe deve ser um sinal de que você precisa refatorar. Não há como uma classe com 40 dependências se ater ao diretor de responsabilidade única ou ao diretor de abertura / fechamento.
Amin J
4
@AminJ A regra é ótima, mas a realidade é diferente. A empresa em que trabalho trabalha há mais de 20 anos e temos muito código legado. Refatorar é uma boa idéia, mas custa dinheiro. Também não sei por que estão dizendo isso, mas eu não quis dizer 40-50 dependências, quero dizer 40-50 feijão, componentes, módulos ...
dieter
7
@dit, sua situação é claramente aquela em que a dívida técnica está fazendo com que você faça escolhas abaixo do ideal. Com suas próprias palavras, você está em uma situação em que sua tomada de decisão é significativamente influenciada pelo código legado com mais de 20 anos. Ao iniciar um novo projeto, você ainda recomendaria a injeção em campo, em vez da injeção por construtor? Talvez você deva colocar uma advertência em sua resposta para indicar em quais casos você escolheria a injeção em campo.
Umar Farooq Khawaja
0

Mais um comentário - Vojtech Ruzicka afirmou que o Spring injeta feijão de três maneiras (a resposta com o maior número de pontos):

  1. Através de um construtor
  2. Através de setters ou outros métodos
  3. Através da reflexão, diretamente nos campos

Esta resposta é ERRADA - porque PARA CADA TIPO DE MOLA DE INJEÇÃO USAM REFLEXÃO! Use o IDE, defina o ponto de interrupção no configurador / construtor e verifique.

Isso pode ser uma questão de gosto, mas também pode ser uma questão de CASO. O @dieter forneceu um excelente caso quando a injeção no campo é melhor. Se você estiver usando injeção de campo em testes de integração que estão configurando o contexto Spring - o argumento com testabilidade da classe também é inválido - a menos que você queira gravar posteriormente os testes nos testes de integração;)

wujek.oczko
fonte