On retrouve les principes SOLID :
L'idée clée est de pouvoir faire évoluer l'application sans modifier une seule ligne du code déjà écrit mais en rajoutant éventuellement de nouvelles classes.
@FunctionalInterface public interface PasswordChecker { public boolean check(String pwd); } public RegularPasswordChecker implements PasswordChecker{ public boolean check(String pwd){ return pwd.length>=8; } } public SecurePasswordChecker implements PasswordChecker{ public boolean check(String pwd){ return pwd.length>=12; } }
public class Application { ... String password = ... PasswordChecker pc = new RegularPasswordChecker(); if (pc.check(password)){ .... } }
Le new implique que si l'on veut changer de PasswordChecker
, il faudra aller modifier le code de Application
.
Solution: on délègue la création à un objet appelé conteneur qui va lire les information dans un fichier XML ou dans une classe de configuration.
Le fichier MyConfig.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="verificateur" class="fr.uge.RegularChecker"/> </beans> </xml>
L'application en Spring avec IoC
public class Application { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("MyConfig.xml"); PasswordChecker pc = applicationContext.getBean("verificateur",fr.uge.PasswordChecker.class); String password = ... if (pc.check(password)){ .... } }
Un bean est un objet qui est instancié, assemblé ou géré par le conteneur IoC.
<bean id="verificateur" class="fr.uge.RegularChecker"/>
Un composant logiciel est une interface + les différents beans qui fournissent cette interface.
Dans notre exemple, PasswordChecker
+ (RegularPasswordChecker
et SecurePasswordChecker
)
UserChecker
on a besoin d'un PasswordChecker
.
Par défaut, les beans sont, pour l'instant, crées en appelant leur constructeur sans paramètre.
public class UGEPasswordChecker implements PasswordChecker { private int minSize; public UGEPasswordChecker(){ this.minSize=8; } public UGEPasswordChecker(int minSize){ this.minSize=minSize; } ... }
On peut fournir des paramètres pour le constructeur:
<bean id="verificateur" class="fr.uge.jee.UGEPasswordChecker"> <constructor-arg value="12"/> </bean>
applicationContext.getBean(String name) : Object
☠☠☠ \`A ne pas utiliser car oblige à faire un cast ! ☠☠☠
applicationContext.getBean(String name,Class<T>) : T
applicationContext.getBean(Class<T>) : T
Le conteneur IoC peut injecter dans les beans qu'il construit:
Ces injections peuvent se faire:
public class Address { private final int number; private final String street; public Address(int number, String street) { this.number = number; this.street = street; } public String toString() {...}; }
On crée un bean addressBob en injectant des valeurs par le constructeur.
<bean id="addressBob" class="fr.uge.jee.injections.Address"> <constructor-arg value="5"/> <constructor-arg value="Rue des Lilas"/> </bean>
public class Person { private final String name; private final Address address; public Person(Address address, String name) { this.address = address; this.name = name; } public String toString() {...}; }
On crée un bean boss en injectant une valeur et le bean addressBob.
<bean id="boss" class="fr.uge.jee.injections.Person"> <constructor-arg value="Bob"/> <constructor-arg ref="addressBob"/> </bean>
public class Client { private final String name; private Address address; public Client(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public String toString() {...}; }
On veut créer un bean lea pour une cliente nommée Léa et qui habite au 53 Bvd Copernic.
☠☠☠ A comprendre mais à ne pas utiliser ! ☠☠☠
On crée un bean addressLea pour son adresse.
<bean id="addressLea" class="fr.uge.jee.injections.Address"> <constructor-arg value="53"/> <constructor-arg value="Bvd Copernic"/> </bean>
On injecte le nom par le constructeur et l'adresse par le setter:
<bean id="lea" class="fr.uge.jee.injections.Client"> <constructor-arg value="Léa"/> <property name="address" ref="addressLea"/> </bean>
☠☠☠ A comprendre mais à ne pas utiliser ! ☠☠☠
<property name="address" ref="addressLea"/>
Client
setAddress
et lui passer le bean addressLeaLe nom du setter est dérivé du nom du champ : s'il y a un typo dans le nom du setter, il y a aura une exception!
☠☠☠ A comprendre mais à ne pas utiliser ! ☠☠☠
L'injection par setter implique que le champ concerné est mutable. Souvent cela va à l'encontre de tout ce que l'on vous à appris !
Il faut savoir lire/comprendre du code avec de l'injection par setter mais il faut priviligier l'injection par constructeur.
☠☠☠ A comprendre mais à ne pas utiliser ! ☠☠☠
Spring fournit un moyen pour injecter des collections Java.
public class Client { private final String name; private Address address; private List<String> emails; public Client(String name) { this.name = name; } public void setEmails(List<String> emails) { this.emails = List.copyOf(emails); } ... }
<bean id="lea" class="fr.uge.jee.injections.Client"> <constructor-arg value="Léa"/> <property name="address" ref="addressLea"/> <property name="emails"> <list> <value>toto@gmail.com</value> <value>titi@gmail.com</value> </list> </property> </bean>
On peut utiliser une syntaxe similaire pour Set
et Map
.
On peut injecter des collections de beans et pas seulement des valeurs.
public class Book { private String title; private long ref; public Book(String title,long ref){...}; } public class Library { private Set<Book> books; public Library(Set<Book> books) {...}; ... }
<bean id="book1" class="Book"> <constructor-arg value="Le livre de la jungle"/> <constructor-arg value="55453"/> </bean> <bean id="book2" class="Book"> <constructor-arg value="Le lion"/> <constructor-arg value="554545"/> </bean> <bean id="library" class="Library"> <constructor-arg> <set> <ref bean="book1"/> <ref bean="book2"/> </set> </constructor-arg> </bean>
Pour l'instant, nous dépendons des id des beans pour déterminer les beans à injecter.
Avec Autowire, Spring offre plusieurs moyens pour inférer les beans à injecter.
<bean id="addressLea" class="fr.uge.jee.injections.Address"> <constructor-arg value="53"/> <constructor-arg value="Bvd Copernic"/> </bean> <bean id="lea" class="fr.uge.jee.injections.Client"> <property name="address" ref="addressLea"/> </bean>
A comprendre mais à ne pas utiliser !
<bean id="address" class="fr.uge.jee.injections.Address"> <constructor-arg value="53"/> <constructor-arg value="Bvd Copernic"/> </bean> <bean id="lea" class="fr.uge.jee.injections.Client"> <property name="address" ref="address"/> </bean>
L'id du bean est égal au nom du champ de Client
.
<bean id="lea" class="fr.uge.jee.injections.Client" autowire="ByName"/>
A comprendre mais à ne pas utiliser !
<bean id="addressLea" class="fr.uge.jee.injections.Address"> <constructor-arg value="53"/> <constructor-arg value="Bvd Copernic"/> </bean> <bean id="lea" class="fr.uge.jee.injections.Client"> <property name="address" ref="addressLea"/> </bean>
Si le bean addressLea est le seul bean du type fr.uge.jee.injections.Address
alors:
<bean id="lea" class="fr.uge.jee.injections.Client" autowire="ByType"/>
A comprendre mais à ne pas utiliser !
Le conteur IoC lit dans fichier XML la description d'un certain nombre de beans.
En utilisant la réflexion ou la réécriture de bytecode, il fait les appels à new.
PasswordChecker
.Nous sommes en train de coder du Java dans un fichier XML, est-ce que c'est vraiment mieux ?
Il existe une autre approche basée sur les annotations Java qui évite ce problème et que nous allons utiliser dans le reste du cours.
On commence avec la configuration XML car: