Le conteneur IoC instantie/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 instantiés au moment de la création du conteneur IoC.
Comme toutes les vérifications sur l'assemblage sont faites à l'exécution, ce comportement permet détecter au démarrage les problèmes d'assemblage.
Nous allons voir une alternative à la configuration par fichier XML.
Pour voir comment, on reprend l'exemple du cours précédent.
@FunctionalInterface public interface PasswordChecker { public boolean check(String pwd); } public RegularPasswordChecker implements PasswordChecker { public boolean check(String pwd){ return pwd.length>=8; } }
Le fichier MyConfig.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="verificateur" class="fr.uge.RegularChecker"/> </beans> </xml>
peut être remplacé par:
@Configuration public class Config { @Bean PasswordChecker verificateur(){ return new RegularChecker(); } }
@Configuration
remplace la balise beans
@Bean
remplace la balise bean
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(Config.class); var checker = applicationContext.getBean("verificateur",PasswordChecker.class); }
public class ConfigurablePasswordChecker implements PasswordChecker { private int minSize; private boolean requireUppercase=false; public ConfigurablePasswordChecker(int minSize) { this.minSize = minSize; } public void setRequireUppercase(boolean requireUppercase) { this.requireUppercase = requireUppercase; } @Override public boolean checkPassword(String password) { if (requireUppercase){ var containsUpper=password.chars().mapToObj(c -> (char) c).anyMatch(Character::isUpperCase); if (!containsUpper){ return false; } } return password.length()>=minSize; } }
Une classe mutable c'est pas bien mais c'est pour l'exemple.
@Configuration public class Config { @Bean PasswordChecker regularPasswordChecker(){ return new RegularPasswordChecker(); } @Bean PasswordChecker configurablePasswordChecker(){ var bean = new ConfigurablePasswordChecker(12); bean.setRequireUppercase(true); return bean; } }
Essentiellement trivial !
@Configuration public class Config { @Bean Book jungleBook(){ return new Book("Le livre de la jungle","Rudyard Kipling"); } @Bean Book montecristoBook(){ return new Book("Le comte de Monte-Cristo","Alexandra Dumas"); } @Bean Book globaliaBook() { return new Book("Globalia", "Jean-Christophe Rufin"); } @Bean Library library(){ return new Library(Set.of(jungleBook(), montecristoBook())); } }
Par défaut, les beans sont des singletons. Donc dans cet exemple, le bean library
contiendra les deux beans de type Book
.
@Configuration public class Config { @Bean Book jungleBook(){ return new Book("Le livre de la jungle","Rudyard Kipling"); } ... @Bean Book globaliaBook() { return new Book("Globalia", "Jean-Christophe Rufin"); } @Bean Library libraryFull(Set<Book> allBooks){ return new Library(allBooks); } }
Les paramètres des méthodes qui définissent les beans sont injectés par leur type.
Dans notre cas, comme allBooks
est une collection de Book
, le conteneur va rassembler tous les beans de type Book
dans un Set qu'il va passer en argument libraryFull
.
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.injections")
Reprenons notre exemple en configurant l'assemblage en utilisant @Component
La classe de configuration indique simplement à Spring de rechercher les classes annotées par @Component
.
@Configuration @ComponentScan class Config{ // vide }
@Component public class Address { ... public Address(@Value("5") int streetNumber, @Value("Rue des lilas") String street, @Value("France") String country) { ... } }
L'annotation @Value
permet d'injecter des valeurs. On verra dans les exercices que @Value
peut injecter des valeurs prises dans des fichiers de configuration ou dans l'environmment d'exécution.
@Component public class Address { ... public Address(@Value("5") int streetNumber, @Value("Rue des lilas") String street, @Value("France") String country) { ... } } @Component public class Client { private final String name; private final Address address; public Client(@Value("Bob") String name, Address address) { this.name = name; this.address = address; } }
Les paramétres manquants à la construction d'un Component
sont injectés par type. En d'autres termes, le
le conteneur IoC va injecter le bean Address
. S'il y avait plusieurs beans avec le type Address
, on aurait une erreur.
@Configuration @ComponentScan public class Config { @Bean Book jungleBook(){ return new Book("Le livre de la jungle","Rudyard Kipling"); } ... @Bean Book globaliaBook() { return new Book("Globalia", "Jean-Christophe Rufin"); } } @Component public class Library { private final Set<Book> books; public Library(Set<Book> books){ this.books=Set.copyOf(books); } }
Dans un Component
, si le constructeur prend une collection (Set
,List
,...), le conteneur IoC va rassembler tous les beans ayant ce type dans la bonne collection.
@Component public class Library { @Autowire private Set<Book> books; public Library(){ } }
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 javax.inject.