Quais classes devem ser instaladas automaticamente pelo Spring (quando usar a injeção de dependência)?

32

Uso a injeção de dependência na primavera há algum tempo e compreendo como funciona e quais são os prós e os contras de usá-la. No entanto, quando estou criando uma nova classe, eu sempre me pergunto - essa classe deve ser gerenciada pelo Spring IOC Container?

E não quero falar sobre diferenças entre anotação @Autowired, configuração XML, injeção de setter, injeção de construtor, etc. Minha pergunta é geral.

Digamos que temos um serviço com um conversor:

@Service
public class Service {

    @Autowired
    private Repository repository;

    @Autowired
    private Converter converter;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
}

@Component
public class Converter {

    public CarDto mapToDto(List<Car> cars) {
        return new ArrayList<CarDto>(); // do the mapping here
    }
}

Claramente, o conversor não possui dependências, portanto, não é necessário que ele seja conectado automaticamente. Mas, para mim, parece melhor como conectado automaticamente. O código é mais limpo e fácil de testar. Se eu escrever esse código sem DI, o serviço será semelhante a:

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        Converter converter = new Converter();
        return converter.mapToDto(cars);
    }
}

Agora é muito mais difícil testá-lo. Além disso, um novo conversor será criado para cada operação de conversão, mesmo que esteja sempre no mesmo estado, o que parece uma sobrecarga.

Existem alguns padrões bem conhecidos no Spring MVC: Controladores usando Serviços e Serviços usando Repositórios. Então, se o Repositório estiver conectado automaticamente (o que geralmente é), o Serviço também deverá ser conectado automaticamente. E isso é bem claro. Mas quando usamos a anotação @Component? Se você tem algumas classes utilitárias estáticas (como conversores, mapeadores) - você as utiliza automaticamente?

Você tenta fazer com que todas as classes sejam conectadas automaticamente? Todas as dependências de classe são fáceis de injetar (mais uma vez, fáceis de entender e fáceis de testar). Ou você tenta se conectar automaticamente apenas quando é absolutamente necessário?

Passei algum tempo procurando algumas regras gerais sobre quando usar a fiação automática, mas não consegui encontrar dicas específicas. Geralmente, as pessoas falam sobre "você usa DI? (Sim / não)" ou "que tipo de injeção de dependência você prefere", o que não responde à minha pergunta.

Ficaria muito grato por qualquer dica sobre esse tópico!

Kacper86
fonte
3
+1 para essencialmente "Quando está indo longe demais?"
Andy Hunt
Confira esta pergunta e este artigo
Laiv

Respostas:

8

Concorde com o comentário de @ ericW e queira adicionar, lembre-se de que você pode usar inicializadores para manter seu código compacto:

@Autowired
private Converter converter;

ou

private Converter converter = new Converter();

ou, se a classe realmente não tem nenhum estado

private static final Converter CONVERTER = new Converter();

Um dos principais critérios para determinar se o Spring deve instanciar e injetar um bean é: esse bean é tão complicado que você deseja zombar dele no teste? Se sim, injete-o. Por exemplo, se o conversor fizer uma ida e volta para qualquer sistema externo, faça-o um componente. Ou se o valor de retorno tiver uma grande árvore de decisão com dezenas de variações possíveis com base na entrada, faça uma simulação. E assim por diante.

Você já fez um bom trabalho em reunir essa funcionalidade e encapsulá-la. Agora, é apenas uma questão de saber se é complexa o suficiente para ser considerada uma "unidade" separada para teste.

Roubar
fonte
5

Eu não acho que você precise usar @Autowired todas as suas classes, isso depende do uso real, para os seus cenários deve ser melhor usar o método estático em vez do @Autowired. Não vejo nenhum benefício em usar o @Autowired para essas classes simples de utilitários, e isso aumentará absolutamente o custo do contêiner do Spring se não for usado adequadamente.

ericW
fonte
2

Minha regra de ouro é baseada em algo que você já disse: testabilidade. Pergunte a si mesmo " Posso testar a unidade facilmente? ". Se a resposta for sim, na ausência de qualquer outro motivo, eu ficaria bem com ela. Portanto, se você desenvolver o teste de unidade ao mesmo tempo em que está desenvolvendo, economizará muita dor.

O único problema em potencial é que, se o conversor falhar, seu teste de serviço também falhará. Algumas pessoas diriam que você deve zombar dos resultados do conversor em testes de unidade. Dessa forma, você seria capaz de identificar erros rapidamente. Mas tem um preço: você precisa zombar de todos os resultados dos conversores quando o conversor real poderia ter feito o trabalho.

Também suponho que não há nenhuma razão para usar Conversores dto diferentes.

Borjab
fonte
0

TL; DR: uma abordagem híbrida de ligação automática para DI e encaminhamento de construtor para DI pode simplificar o código que você apresentou.

Eu estive analisando perguntas semelhantes devido a alguns erros de weblogic com inicialização da estrutura de primavera / complicações que envolvem dependências de inicialização do bean @autowired. Comecei a misturar outra abordagem de DI: encaminhamento de construtor. Requer condições como as que você apresenta ("Claramente, o conversor não possui nenhuma dependência, portanto, não é necessário que ele seja conectado automaticamente"). No entanto, gosto muito da flexibilidade e ainda é bastante direta.

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

ou mesmo como um rif da resposta de Rob

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

Pode não exigir uma alteração na interface pública, mas eu exigiria. Ainda o

public List<CarDto> getAllCars(Converter converter) { ... }

pode ser protegido ou privado para reduzir o escopo apenas para fins de teste / extensão.

O ponto principal é que o DI é opcional. É fornecido um padrão, que pode ser substituído de maneira direta. Tem sua fraqueza à medida que o número de campos aumenta, mas para 1, talvez 2 campos, eu preferiria isso nas seguintes condições:

  • Número muito pequeno de campos
  • O padrão quase sempre é usado (DI é uma costura de teste) ou o padrão quase nunca é usado (stop gap) porque eu gosto de consistência; portanto, isso faz parte da minha árvore de decisão, não uma verdadeira restrição de design.

Último ponto (um pouco OT, mas relacionado a como decidimos o que / onde @autowired): a classe do conversor, como apresentada, é o método utilitário (nenhum campo, nenhum construtor, pode ser estático). Talvez a solução tenha algum tipo de método mapToDto () na classe Cars? Ou seja, envie a injeção de conversão para a definição Cars, onde provavelmente já está intimamente ligado:

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
Kristian H
fonte
-1

Eu acho que um bom exemplo disso é algo como SecurityUtils ou UserUtils. Com qualquer aplicativo Spring que gerencia usuários, você precisará de alguma classe Util com vários métodos estáticos, como:

getCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (autoridade de autoridade)

etc.

e eu nunca os escrevi automaticamente. O JHipster (que eu uso como um bom juiz de boas práticas) não.

Janome314
fonte
-2

Podemos separar as classes com base na função dessa classe, de modo que controladores, serviço, repositório, entidade. Podemos usar outras classes para nossa lógica apenas não para o propósito de controladores, serviços etc., nessa ocasião, poderíamos anotar essas classes com @component. Isso registrará automaticamente essa classe no contêiner de mola. Se estiver registrado, será gerenciado por um contêiner de mola. O conceito de injeção de dependência e inversão de controle pode ser fornecido para essa classe anotada.

SathishSakthi
fonte
3
é difícil ler este post (parede de texto). Você se importaria de editá -lo em uma forma melhor?
Gnat #