Corso di Java Spring Framework #11 | Risolvere le ambiguità con @Primary e @Qualifier
Vediamo come risolvere le ambiguità a livello di dependency injection in Spring utilizzando delle opportune annotazioni.
Di default il framework effettua l'injection delle dipendenze by type, ovvero in corrispondenza dell'annotazione @Autowired
cerca di trovare un bean di quel tipo.
E questo indipendentemente dalla modalità di injection utilizzata.
Il problema si pone quando, in corrispondenza di quel particolare tipo, esistono due o più bean per cui Spring non sa più che pesci prendere e genera un'eccezione.
Vediamo un esempio pratico di codice.
Ipotizziamo di avere un'interfaccia Engine
con un metodo start()
e due implementazioni della stessa: DieselEngine
ed ElectricEngine
.
public interface Engine {
public String start();
}
public class DieselEngine implements Engine {
@Override
public String start() {
return "Starting DieselEngine ...";
}
}
public class ElectricEngine implements Engine {
@Override
public String start() {
return "Starting ElectricEngine ...";
}
}
In entrambi i casi il metodo si limita a stampare un messaggio per capire quale classe è stata istanziata effettivamente.
Le due classi sono annotate con @Service
quindi Spring provvederà a creare due bean all'avvio dell'applicazione.
Utilizziamo anche un semplicissimo controller in cui è presente un endpoint /engine
corrispondente ad una chiamata HTTP di tipo GET che lancia il metodo start()
di Engine
.
@RestController
public class EngineController {
@Autowired
private Engine engine;
@GetMapping("/engine")
public String getEngine() {
return engine.start();
}
}
Questa dipendenza è passata mediante l'injection sfruttando l'annotazione @Autowired
associata all'interfaccia e non ad una specifica classe di implementazione della stessa.
Eseguendo questo codice Spring genera un'eccezione in quanto sono stati individuati due bean di tipo Engine
, ovvero DieselEngine
ed ElectricEngine
che sono in entrambi i casi implementazioni della stessa interfaccia.
Di fatto il framework non sa quale dei due utilizzare in assenza di precise indicazioni.
Una prima soluzione consiste nel marcare uno dei due bean con @Primary
così da designare una scelta in presenza di ambiguità come in questo caso.
@Service
@Primary
public class ElectricEngine implements Engine {
@Override
public String start() {
return "Starting ElectricEngine ...";
}
}
L'altra possibilità consiste nell'utilizzo dell'annotazione @Qualifier
con cui possiamo dare un nome al bean.
@Service
@Qualifier("diesel-engine")
public class DieselEngine implements Engine {
@Override
public String start() {
return "Starting DieselEngine ...";
}
}
Di default, in assenza di tale annotazione, il nome del bean sarà quello della classe da cui è istanziato oppure, se utilizziamo una classe di configurazione, corrisponderà al nome del metodo annotato con @Bean
.
Se non si specifica l'annotazione @Qualifier
con il nome del bean, si considera il bean annotato con @Primary
.
Viceversa Spring farà riferimento al bean di cui è stato indicato il nome perchè @Qualifier
ha una priorità maggiore rispetto a @Primary
.
Ovviamente, dal punto di vista puramente sintattico, è possibile marcare due bean con @Primary
, ma a runtime verrà di nuovo generata un'eccezione perchè anche in questo caso si presenta un'ambiguità che Spring non è in grado di risolvere autonomamente.
Ricapitolando: di default Spring applica l'injection by type, ma in presenza di ambiguità si ricorre all'injection by name mediante l'annotazione @Qualifier
o marcando un bean con @Primary
.
[VIDEO YOUTUBE]
[LINKS]