Le conteneur IoC instancie et injecte les beans à partir d’une description dans un fichier XML.
Par défaut, tous les beans sont des singletons, mais on peut changer ce comportement.
Tous les beans sont instanciés au moment de la création du conteneur IoC.
Comme les vérifications d’assemblage sont effectuées à l’exécution, ce comportement permet de détecter les problèmes dès le démarrage.
On reprend l'exemple du cours précédent.
@FunctionalInterface
public interface PasswordChecker {
public boolean check(String pwd);
}
public class UGEPasswordChecker implements PasswordChecker {
...
public UGEPasswordChecker(int minSize){
this.minSize=minSize;
}
...
}
public class ConnectionHandler {
private final PasswordChecker passwordChecker;
public ConnectionHandler(PasswordChecker passwordChecker){
this.passwordChecker = Objects.requireNonNull(passwordChecker);
}
...
}
Pour remplacer, la création classique:
var passwordChecker = new UGEPasswordChecker(12);
var connectionHandler = new ConnectionHandler(passwordCheker);
On utilise un fichier XML qui définit les beans de notre application.
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="checker" class="fr.uge.jee.UGEPasswordChecker">
<constructor-arg value="12"/>
</bean>
<bean id="connectionHandler" class="fr.uge.jee.ConnectionHandler">
<constructor-arg ref="checker"/>
</bean>
</beans>
</xml>
Spring privilégie l'injection basée sur les types à celle beaucoup moins robuste basée sur les identifiants des beans.
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="fr.uge.jee.UGEPasswordChecker">
<constructor-arg value="12"/>
</bean>
<bean class="fr.uge.jee.ConnectionHandler">
<autowire="constructor"/>
</bean>
</beans>
</xml>
à la place de:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="checker" class="fr.uge.jee.UGEPasswordChecker">
<constructor-arg value="12"/>
</bean>
<bean id="connectionHandler" class="fr.uge.jee.ConnectionHandler">
<constructor-arg ref="checker"/>
</bean>
</beans>
</xml>
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="checker" class="fr.uge.jee.UGEPasswordChecker">
<constructor-arg value="12"/>
</bean>
</beans>
</xml>
Classe de configuration :
@Configuration
public class AppConfig {
@Bean
UGEPasswordChecker checker() {
return new UGEPasswordChecker(12);
}
}
Attention, ceci n'est pas une vraie classe:
On remplace la lecture du fichier XML par la lecture de la classe de configuration.
public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
var checker =
applicationContext.getBean(PasswordChecker.class);
}
var passwordChecker = new UGEPasswordChecker(12);
var connectionHandler = new ConnectionHandler(passwordCheker);
@Configuration
public class AppConfig {
@Bean
UGEPasswordChecker checker() {
return new UGEPasswordChecker(12);
}
@Bean
ConnectionHandler connectionHandler(PasswordChecker passwordChecker) {
return new ConnectionHandler(passwordChecker);
}
}
Les paramètres des méthodes qui definissent les beans sont injectées par Spring en se basant sur les types. Ici Spring cherche un bean implémentant l'interface PasswordChecker pour le passer à la méthode connectionHandler.
var passwordChecker = new UGEPasswordChecker(12);
var connectionHandler = new ConnectionHandler(passwordCheker);
@Configuration
public class AppConfig {
@Bean
UGEPasswordChecker checker() {
return new UGEPasswordChecker(12);
}
@Bean
ConnectionHandler connectionHandler(PasswordChecker passwordChecker) {
return new ConnectionHandler(passwordChecker);
}
}
L'approche recommandée est de donner le type concret des beans dans leur définition et d' utiliser l'interface pour l'injection.
@FunctionalInterface
public interface Criterium {
boolean satisfy(String query);
}
public class DataIntegrity implements Criterium { ... };
public class EscapeChecking implements Criterium { ... };
public class Validator {
private final Set<Criterium> criteria;
public Validator(Set<Criterium> criteria){
this.criteria = Set.copyOf(criteria);
}
public boolean check(String query){
Object.requireNonNull(query);
for(var criterium : criteria){
if (!criterium.satisfies(query)) {return false;}
}
return true;
}
}
var criterium1 = new DataIntegrity(); var criterium2 = new EscapeChecking(); var validator = new Validator(Set.of(criterium1,criterium2));
var criterium1 = new DataIntegrity(); var criterium2 = new EscapeChecking(); var validator = new Validator(List.of(criterium1,criterium2));
@Configuration
public class ValidationConfig {
@Bean
Criterium dataIntegrity() {
return new DataIntegrity();
}
@Bean
Criterium escapeChecking() {
return new EscapeChecking();
}
@Bean
Validator validator(Set<Criterium> criteria) {
return new Validator(criteria);
}
}
La classe de configuration n'est pas une vraie classe, elle remplace un fichier XML pour la description des beans.
On privilégie autant que possible l'injection par le type et on n'utilise le moins possible (voir pas du tout) les identifiants des beans.
Par défaut, les beans sont des singletons mais on peut changer le comportement.
Il est possible d'annoter une classe par @Component pour indiquer au conteneur IoC
de créer un bean pour cette classe.
En annotant la classe de configuration par @ComponentScan, Spring va parcourir le package et les sous-package de la classe de configuration à la recherche de classe annotée par @Component pour définir les beans.
On peut spécifier le package où rechercher les @Component avec:
@Configuration
@ComponentScan("fr.uge.jee")
public class AppConfig {
...
}
ConnectionHandlerReprenons notre exemple en configurant l'assemblage de Validator en utilisant @Component
La classe de configuration indique simplement à Spring de rechercher les classes annotées par @Component.
@Configuration
@ComponentScan
public class AppConfig {
@Bean
UGEPasswordChecker checker() {
return new UGEPasswordChecker(12);
}
}
@Component
public class ConnectionHandler {
private final PasswordChecker passwordChecker;
public ConnectionHandler(PasswordChecker passwordChecker){
this.passwordChecker = Objects.requireNonNull(passwordChecker);
}
...
}
Les paramétres manquants à la construction d'un Component sont injectés par type.
@Component
public class ConnectionHandler {
@Autowire
final PasswordChecker passwordChecker;
public ConnectionHandler(){
}
...
}
L'injection sur les champs oblige le champ à ne pas être final !
Il faut savoir comprendre ce code mais ne jamais l'utiliser.
@Component
Ces annotations ajoutent des fonctionnalités (cf. cours suivants) à @Component sauf pour l'instant @Service.
@Component, la configuration de l'assemblage est éclatée dans plusieurs fichiers.@Bean est nécessaire pour définir un bean à partir d'une classe dont on ne possède pas le code (i.e., libraire).La JSR 330 introduit @Inject et @Named. C'est une annotation de la norme JEE (introduite dans la version 6).
Très grossièrement, @Inject correspond à @Autowire et @Name correspond à @Component.
Les différences sont détaillées ici.
Spring reconnait ces annotations si on rajoute la dépendance jakarta.inject.