Quali classi devono essere autowired da Spring (quando utilizzare l'iniezione di dipendenza)?

28

Da tempo utilizzo Dependency Injection in primavera, e capisco come funziona e quali sono alcuni pro e contro del suo utilizzo. Tuttavia, quando sto creando una nuova classe, mi chiedo spesso: questa classe dovrebbe essere gestita da Spring IOC Container?

E non voglio parlare di differenze tra annotazione @Autowired, configurazione XML, iniezione setter, iniezione costruttore, ecc. La mia domanda è generale.

Supponiamo di avere un servizio con un convertitore:

@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
    }
}

Chiaramente, il convertitore non ha alcuna dipendenza, quindi non è necessario per essere autowired. Ma per me sembra meglio autowired. Il codice è più pulito e facile da testare. Se scrivo questo codice senza DI, il servizio sarà simile a questo:

@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);
    }
}

Ora è molto più difficile testarlo. Inoltre, verrà creato un nuovo convertitore per ogni operazione di conversione, anche se è sempre nello stesso stato, che sembra un sovraccarico.

Ci sono alcuni modelli ben noti in Spring MVC: controllori che utilizzano servizi e servizi che utilizzano i repository. Quindi, se il repository è autowired (che di solito è), anche il servizio deve essere autowired. E questo è abbastanza chiaro. Ma quando usiamo l'annotazione @Component? Se hai alcune classi di utilizzo statiche (come convertitori, mapper), le autowire?

Cerchi di rendere tutte le classi autowired? Quindi tutte le dipendenze di classe sono facili da iniettare (ancora una volta, facili da capire e facili da testare). O cerchi di autowire solo quando è assolutamente necessario?

Ho trascorso un po 'di tempo a cercare alcune regole generali su quando utilizzare l'autowiring, ma non sono riuscito a trovare suggerimenti specifici. Di solito, la gente parla di "usi DI? (Sì / no)" o "che tipo di dipendenza da iniezione preferisci", che non risponde alla mia domanda.

Sarei grato per eventuali suggerimenti su questo argomento!

    
posta Kacper86 10.03.2014 - 13:03
fonte

6 risposte

7

Accetta il commento di @ ericW e aggiungi semplicemente ricordati che puoi utilizzare gli inizializzatori per mantenere il tuo codice compatto:

@Autowired
private Converter converter;

o

private Converter converter = new Converter();

o, se la classe non ha davvero alcuno stato

private static final Converter CONVERTER = new Converter();

Uno dei criteri chiave per stabilire se Spring debba creare un'istanza e iniettare un bean, è che il bean è così complicato da essere preso in giro durante il test? Se è così, quindi iniettarlo. Ad esempio, se il convertitore effettua un viaggio di andata e ritorno su un qualsiasi sistema esterno, rendilo invece un componente. Oppure se il valore di ritorno ha un grande albero decisionale con dozzine di possibili variazioni basate sull'input, quindi prendilo in giro. E così via.

Hai già fatto un buon lavoro nel recuperare questa funzionalità e incapsularla, quindi ora è solo una questione se sia abbastanza complessa da essere considerata una "unità" separata per i test.

    
risposta data 12.04.2014 - 17:00
fonte
5

Non penso che tu debba @Autowired tutte le tue classi, dovrebbe dipendere dal reale utilizzo, per i tuoi scenari dovrebbe essere meglio usare il metodo statico invece di @Autowired. Non ho visto alcun beneficio usare @Autowired per la classe dei semplici utils, e questo sicuramente aumenterà il costo del contenitore Spring se non lo utilizzi correttamente.

    
risposta data 12.03.2014 - 03:22
fonte
2

La mia regola empirica si basa su qualcosa che hai già detto: testabilità. Chiediti " Posso testarlo facilmente? ". Se la risposta è sì allora, in assenza di qualsiasi altra ragione, sarei d'accordo. Quindi, se sviluppi il test unitario nello stesso momento in cui sviluppi, risparmierai molto dolore.

L'unico problema potenziale è che se il convertitore fallisce, anche il test di servizio fallirà. Alcune persone direbbero che dovresti prendere in giro i risultati del convertitore in test unitari. In questo modo sarai in grado di identificare i fast-fast degli errori. Ma ha un prezzo: devi prendere in giro tutti i risultati dei convertitori quando il vero convertitore avrebbe potuto fare il lavoro.

Suppongo anche che non ci sia alcuna ragione per utilizzare diversi convertitori dto.

    
risposta data 26.08.2016 - 16:20
fonte
0

TL; DR: un approccio ibrido di autowiring per DI e l'inoltro del costruttore per DI può semplificare il codice che hai presentato.

Ho esaminato domande simili a causa di alcuni weblogic con errori / complicazioni di avvio del framework spring che coinvolgono le dipendenze di inizializzazione dei bean @autowired. Ho iniziato a mescolare in un altro approccio DI: l'inoltro del costruttore. Richiede condizioni come la tua attuale ("Chiaramente, il convertitore non ha alcuna dipendenza, quindi non è necessario per essere autowired."). Tuttavia, mi piace molto per la flessibilità ed è ancora abbastanza semplice.

@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);
    }
}

o anche come un rif, la risposta di 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);
    }
}

Potrebbe non richiedere un cambio di interfaccia pubblico, ma lo farei. Ancora il

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

potrebbe essere reso protetto o privato verso il basso solo per scopi di test / estensione.

Il punto chiave è il DI è facoltativo. Viene fornito un valore predefinito, che può essere ignorato in modo diretto. Ha il suo punto debole quando aumenta il numero di campi, ma per 1, forse 2 campi, preferirei questo nelle seguenti condizioni:

  • Numero di campi molto piccolo
  • L'impostazione predefinita è quasi sempre utilizzata (DI è una cucitura di prova) o l'impostazione predefinita non viene quasi mai utilizzata (stop gap) perché mi piace la coerenza, quindi questo è parte del mio albero decisionale, non un vero vincolo progettuale

Ultimo punto (un po 'OT, ma relativo a come decidiamo cosa / dove @autowired): la classe del convertitore, come presentato, è un metodo di utilità (nessun campo, nessun costruttore, potrebbe essere statico). Forse la soluzione dovrebbe avere una sorta di mapToDto () metodo nella classe Cars? Cioè, spingi l'iniezione di conversione alla definizione di Cars, dove è probabilmente già intimamente legata:

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
    
risposta data 23.08.2018 - 20:00
fonte
-1

Penso che un buon esempio di questo è qualcosa come SecurityUtils o UserUtils. Con qualsiasi app Spring che gestisca gli utenti, avrai bisogno di una classe Util con un sacco di metodi statici come:

GetCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (autorità di autorità)

ecc.

e non li ho mai autowired. JHipster (che io uso come un buon giudice delle migliori pratiche) non lo fa.

    
risposta data 21.11.2016 - 14:54
fonte
-2

Possiamo separare le classi sulla base della funzione di quella classe in modo tale che controllori, servizio, repository, entità. Potremmo usare altre classi solo per la nostra logica, non per scopi di controllori, servizi, ecc., In quell'occasione potremmo annotare quelle classi con @component. Questo registrerà automaticamente quella classe nel contenitore della molla. Se è registrato, sarà gestito da un contenitore a molla. Il concetto di dipendenza da iniezione e inversione di controllo può essere fornito a quella classe annotata.

    
risposta data 13.03.2014 - 13:22
fonte

Leggi altre domande sui tag