Archives Wild Code School
Java

CDI - One container to rule them all 2/2

Quête 2 sur 43

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 signature void saveGithuber(Githuber githuber);

  • Implémente-la dans DumbGithuberDAO en lançant une UnsupportedOperationException;

  • Importe la dernière version de jackson-databind et jackson-core dans ton pom.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 @JsonCreator et @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 MemoryGithuberDAO qui implémente elle-aussi GithuberDAO;

  • 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 du ObjectMapper Jackson et le retourne (sinon: null);

  • Elle a une méthode private void annotée @PostConstruct qui appelle la méthode ci-dessus pour les mêmes 5 GitHubers que DumbGithuberDAO et les ajoute à la map à l'aide de la méthode saveGithuber. Logge si besoin. Vue la spec de @PostConstruct tu 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!

↗
Get a single Github user
developer.github.com
↗
Github REST API introduction
developer.github.com
↗
@PostConstruct spec
docs.oracle.com
↗
@JsonIgnoreProperties
fasterxml.github.io
↗
@JsonCreator/@JsonProperty on StackOverflow
stackoverflow.com
↗
Jackson on Mvnrepository
mvnrepository.com
↗
Map#values
docs.oracle.com

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 ObjectMapperProducer annotée @ApplicationScoped (on n'en a besoin que d'une par application` et y mettre une méthode:
java
@Produces  
public ObjectMapper produceObjectMapper() {  
    return new ObjectMapper()  
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);  
}

  • Tu vas enlever @JsonIgnoreProperties de la classe Githubers

  • Tu vas @Inject private ObjectMapper om; dans la classe MemoryGithuberDAO

  • Et tu vas vérifier que l'application tourne toujours correctement!

↗
Producer Methods @Oracle
docs.oracle.com
↗
Producer methods @Jboss
docs.jboss.org

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.

↗
CDI scopes
docs.oracle.com

Traquons un Githuber

C'est parti:

  • Créée une nouvelle Servlet TrackServlet qui écoute sur sur "/track" et qui, pour simplifier, injecte directement un MemoryGithuberDAO;

  • 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 TODO et ajouté dans MemoryGithuberDAO, puis la servlet fait un resp.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.

↗
RequestDispatcher.forward vs HttpServletResponse.sendRedirect
stackoverflow.com

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 appelles InMemory.

  • Un qualifier se présente très souvent sous la forme suivante; reproduis la avec InMemory:
java
@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 MemoryGithuberDAO et annote-la @InMemory;

  • Puis rends-toi à tous les endroits où tu as utilisé MemoryGithuberDAO et 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 classe GithubUtils...que tu vas bien sûr @injecter dans MemoryGithuberDAO (à la place de ObjectMapper) et dans TrackServlet :)

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 .

↗
@Sterotype
docs.oracle.com
↗
@Named & @Qualifier
dzone.com
↗
Define a qualifier
dzone.com
↗
Using Qualifiers @ Oracle
docs.oracle.com

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 @InMemory sont utilisées;
  • Le nouveau formulaire de tracking est implémenté
  • L'application fonctionne, que ce soit sur /track.jsp ou /githubers