Quali classi dovrebbero essere autowired da Spring (quando usare l'iniezione di dipendenza)?


32

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

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

Diciamo che abbiamo 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 dipendenze, quindi non è necessario che sia autowired. Ma per me sembra migliore come autowired. Il codice è più pulito e facile da testare. Se scrivo questo codice senza DI, il servizio sarà simile al seguente:

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

Esistono alcuni modelli ben noti in Spring MVC: Controller che utilizzano servizi e servizi utilizzando i repository. Quindi, se Repository è autowired (come in genere lo è), anche il servizio deve essere autowired. E questo è abbastanza chiaro. Ma quando utilizziamo l'annotazione @Component? Se disponi di alcune classi util statiche (come convertitori, mapper), le autorizzi?

Cerchi di rendere autowired tutte le classi? Quindi tutte le dipendenze di classe sono facili da iniettare (ancora una volta, facile da capire e facile 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 iniezione di dipendenza preferisci", che non risponde alla mia domanda.

Le sarei grato per eventuali suggerimenti su questo argomento!


3
+1 per essenzialmente "Quando sta andando troppo lontano?"
Andy Hunt,

Dai un'occhiata a questa domanda e a questo articolo
Laiv,

Risposte:


8

Sono d'accordo con il commento di @ericW e voglio solo ricordare che puoi usare gli inizializzatori per mantenere compatto il tuo codice:

@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 dovrebbe creare un'istanza e iniettare un bean è quel bean così complicato da voler deriderlo durante i test? In tal caso, iniettalo. Ad esempio, se il convertitore effettua un round trip per qualsiasi sistema esterno, rendilo invece un componente. O se il valore di ritorno ha un grande albero decisionale con dozzine di possibili variazioni basate sull'input, allora lo prendi in giro. E così via.

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


5

Non penso che devi @Autowired tutte le tue classi, dovrebbe dipendere dall'uso reale, per i tuoi scenari dovrebbe essere meglio usare il metodo statico invece di @Autowired. Non vedo alcun vantaggio usare @Autowired per quelle semplici classi di utils, e questo aumenterà assolutamente il costo del contenitore Spring se non lo si usa correttamente.


2

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

L'unico potenziale problema è che se il convertitore fallisce anche il test di servizio fallirà. Alcune persone direbbero che dovresti deridere i risultati del convertitore nei test unitari. In questo modo sarai in grado di identificare errori più veloci. 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 vi sia alcun motivo per utilizzare diversi convertitori dto.


0

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

Ho esaminato domande simili a causa di alcuni blog con errori / complicazioni di avvio del framework di primavera relativi alle dipendenze di inizializzazione del bean @autowired. Ho iniziato a missare in un altro approccio DI: l'inoltro del costruttore. Richiede condizioni come te presenti ("Chiaramente, il convertitore non ha dipendenze, quindi non è necessario che sia autowired."). Tuttavia, mi piace molto per la flessibilità ed è ancora piuttosto 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 rifiuto della 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 una modifica dell'interfaccia pubblica, ma lo farei. Ancora il

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

potrebbe essere protetto o privato allo scopo solo a scopo di test / estensione.

Il punto chiave è il DI è facoltativo. Viene fornito un valore predefinito, che può essere sostituito in modo semplice. Ha un punto debole all'aumentare del numero di campi, ma per 1, forse 2 campi, preferirei questo nelle seguenti condizioni:

  • Numero molto piccolo di campi
  • Il default è quasi sempre usato (DI è una cucitura di prova) o il Default non è quasi mai usato (stop gap) perché mi piace la coerenza, quindi questo fa parte del mio albero decisionale, non un vero vincolo di progettazione

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

@Service
public class Service {

   @Autowired
   private Repository repository;

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

-1

Penso che un buon esempio di questo sia qualcosa come SecurityUtils o UserUtils. Con qualsiasi app Spring che gestisce gli utenti, avrai bisogno di alcune classi Util con un sacco di metodi statici come:

GetCurrentUser ()

IsAuthenticated ()

isCurrentUserInRole (Autorità dell'autorità)

eccetera.

e non li ho mai autorizzati. JHipster (che uso come un buon giudice delle migliori pratiche) no.


-2

Possiamo separare le classi sulla base della funzione di quella classe in modo tale che controller, servizio, repository, entità. È possibile che vengano utilizzate altre classi per la nostra logica non solo ai fini di controller, servizi, ecc., In tale occasione potremmo annotare tali classi con @component. Ciò registrerà automaticamente quella classe nel contenitore a molla. Se è registrato, verrà gestito dal contenitore a molla. Il concetto di iniezione di dipendenza e inversione di controllo può essere fornito a quella classe annotata.


3
questo post è piuttosto difficile da leggere (wall of text). Ti dispiacerebbe modificarlo in una forma migliore?
moscerino il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.