CDI - One container to rule them all 2/2
Cette quête va t'emmener encore plus loin dans le royaume conceptuel de CDI.

Objectifs
- Comprendre le concepts d'injection de dépendances
- Comprendre le cycle de vie des beans
- Rendre ton code plus simple et modulable
L'injection c'est magique. Allez, connectons-nous à Github!
Si tu as suivi la précédente quête, tu t'es peut-être demandé comment diable CDI a fait pour savoir qu'il fallait injecter une instance de DumbGithuberDAO dans GithubersService alors que tu n'a référencé que l'interface GithuberDAO. Tout simplement parce que CDI analyse toutes les classes de l'application, et voit ici qu'il n'existe qu'une seule implémentation de GithuberDAO: DumbGithuberDAO. Alors c'est elle qu'il instancie.
Maintenant nous allons directement aller chercher les profils des Githubers sur Github.com:
-
Tout dabord, tu vas tester que tu peux facilement récupérer les données de ton profil github depuis Postman ou même ton navigateur, avec l'API Github v3 (voir ressources).
-
Dans
GithuberDAO, créée une nouvelle signaturevoid saveGithuber(Githuber githuber); -
Implémente-la dans
DumbGithuberDAOen lançant uneUnsupportedOperationException; -
Importe la dernière version de
jackson-databindetjackson-coredans tonpom.xml -
Comme tu as constaté que l'API retourne beaucoup plus de données qu'il n'y en a dans la classe
Githuber, tu vas l'annoter@JsonIgnoreProperties(ignoreUnknown = true). Assure-toi aussi que cette classe utilise@JsonCreatoret@JsonProperty(des annotations de Jackson) sur son constructeur pour n'importer que les propriétés qui t'intéressent. -
Créée une classe
@ApplicationScoped MemoryGithuberDAOqui implémente elle-aussiGithuberDAO; -
Elle contient une
(Hash)Map<String, GitHuber>mappant les Githubers à leurs logins; -
Elle implémente les deux méthodes correctement;
-
Elle a une méthode
public Githuber parseGithuber(String login)qui va chercher directement un profil sur l'API Github, le désérialize à l'aide duObjectMapperJackson et le retourne (sinon: null); -
Elle a une méthode
private voidannotée@PostConstructqui appelle la méthode ci-dessus pour les mêmes 5 GitHubers queDumbGithuberDAOet les ajoute à la map à l'aide de la méthodesaveGithuber. Logge si besoin. Vue la spec de@PostConstructtu devras catcher les exceptions éventuelles... -
NB: depuis le JDK9, @PostConstruct est dans une bibliothèque à part (normal c'est du EE), tu devras donc rajouter cette dépendance dans ton
pom.xml...
Mais si c'est facile, et les resources contiennent tout ce qu'il faut!
...sauf que quand tu lances, tu te prends une exception peu claire AS-WEB-CORE-00108 ou NCLS-CORE-00026
Ton intuition (et Google) te font deviner que le problème vient peut-être du fait qu'il y a maintenant deux implémentations à GithuberDAO et que CDI ne sait plus laquelle choisir. Aussi tu modifies le constructeur de GithubersService et tu passes explicitement un MemoryGithuberDAO.
Verifie que l'application tourne toujours correctement!
Producers
En fouillant sur Google tu vois que tu peux configurer ObjectMapper pour qu'il prenne en compte les dates ou des fonctionnalités Java 8, ou pour qu'il ignore les propriétés inconnues à la désérialisation sans avoir à annoter @JsonIgnoreProperties la classe Githuber.
CDI construit des objets qui ont un constructeur vide, ou ceux qui ont un constructuer annoté @Inject. Mais tu ne peux pas aller modifier le source de ObjectMapper. Donc tu vas dire à CDI comment s'y prendre pour configurer l'objet.
Pour cela il y a les Producers. De simples méthodes qui retournent une instance configurée par tes soins.
- Tu vas créer une classe
ObjectMapperProducerannotée@ApplicationScoped(on n'en a besoin que d'une par application` et y mettre une méthode:
@Produces
public ObjectMapper produceObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
-
Tu vas enlever
@JsonIgnorePropertiesde la classeGithubers -
Tu vas
@Inject private ObjectMapper om;dans la classeMemoryGithuberDAO -
Et tu vas vérifier que l'application tourne toujours correctement!
Les Scopes
Tu as déjà utilisé les scopes @Dependent et @ApplicationScoped. Sache qu'il existe aussi les scopes @RequestScoped, @SessionScoped, @ConversationScoped, @ViewScoped, très orientés "web", chacun permettant de spécifier la durée de vie de l'instance sur laquelle il est apposé. C'est un des concepts les plus importants de CDI mais nous verrons cela en pratique avec JSF.
Tu peux aussi exiger de CDI qu'il ne créée qu'une et une seule instance d'une classe avec @Singleton.
Traquons un Githuber
C'est parti:
-
Créée une nouvelle Servlet
TrackServletqui écoute sur sur"/track"et qui, pour simplifier, injecte directement unMemoryGithuberDAO; -
Inspire toi de la quête sur les servlets pour créer une JSP contenant un formulaire avec un seul champ: le login d'un user Github, et un bouton de type "submit" bien sûr;
-
A la soumission, ce githuber est récupéré, passé à travers le service où tu avais laissé un gros
TODOet ajouté dansMemoryGithuberDAO, puis la servlet fait unresp.sendRedirect(pas un forward) vers la liste des githubers afin de finir sur un GET avec la bonne URL dans la barre d'adresse;
Teste en ajoutant d'abord un Githuber, puis en les listant comme dans toutes les étapes précédentes.
Qualifiers
A présent tu injectes MemoryGithuberDAO à deux endroits de l'application. Tu aimerais pouvoir le découpler de GithubersService et de TrackServlet, ne garder que des références vers l'interface GithuberDAO.
Tu peux faire appel à @Produces bien sûr, mais pour aller plus loin avec CDI nous allons utiliser un Qualifier:
- Dans le package
fr.wildcodeschool.githubtracker.dao, créée une nouvelle classe Java de type "Annotation", que tu appellesInMemory.

- Un qualifier se présente très souvent sous la forme suivante; reproduis la avec
InMemory:
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MyQualifier {}
Maintenant tu disposes d'une nouvelle annotation @InMemory qui te permet d'attacher une sorte d'étiquette à un type, alors:
-
Rouvre la classe
MemoryGithuberDAOet annote-la@InMemory; -
Puis rends-toi à tous les endroits où tu as utilisé
MemoryGithuberDAOet tape à la place@InMemory GithuberDAO. Profites-en pour optimiser les imports (Ctrl+Alt+O); -
Comme GithuberDAO ne dispose pas de la méthode
parseGithuber, ton code ne compile plus, alors extrais cette méthode dans une nouvelle classeGithubUtils...que tu vas bien sûr @injecter dansMemoryGithuberDAO(à la place de ObjectMapper) et dansTrackServlet:)
Vérifie que l'application tourne toujours correctement!
Sache qu'il existe aussi un qualifier un peu particulier: @Named. Il permet de donner un alias @Named("myBean") à un bean CDI que tu pourras directement appeler dans une page JSF avec EL: #{myBean.someProperty}. Nous verrons cela dans la quête JSF.
Sache enfin qu'il existe un qualifier encore plus particulier @Stereotype qui permet de regrouper plusieurs Qualifiers et Scope d'un coup .
Challenge
Utilise CDI dans l'application GithubTracker
Suis courageusement cette quête jusqu'au bout!
Il y a encore beaucoup de choses dans CDI, mais tu es à présent armé pour 95% des cas de figure.
Critères de validation
- Toutes les classes et les annotations demandées au long de cette quête sont présentes;
@Inject,@Dependent,@ApplicationScoped,@Produces,@PostConstruct, et l'annnotation custom@InMemorysont utilisées;- Le nouveau formulaire de tracking est implémenté
- L'application fonctionne, que ce soit sur
/track.jspou/githubers