Implémentez le scope Flash de JSF 2 avec Spring

Spring JavaDans son lot de nouveautés, JSF 2 nous apporte deux nouveaux scopes pour les gestions de nos beans:

Le scope Flash et le scope View.

Malheureusement pour les utilisateurs de Spring, celui ci ne gère pas ces scopes par défaut.

Il nous faut donc les créer nous même grâce aux custom scopes.

Dans mon précédant article, je vous ai présenté la façon dont nous pouvons implémenter le scope View avec Spring. Nous allons donc voir dans cet article comment implémenter le deuxième scope.

 

A quoi sert le scope Flash ?

Le scope Flash permet de faire passer des informations d’une page à une autre lors d’une redirection. Ceci permet par exemple d’afficher des messages d’erreurs ou de donner des informations sur la dernière action de l’utilisateur.

Ces informations sont disponibles seulement lors du première accès à la page. Si vous rechargez la page, les informations en scope Flash ne seront plus accessibles.

 

Implémentation dans Spring

Comme dit plus haut, Spring ne propose pas par défaut ce scope (du moins jusqu’à aujourd’hui en version 3.0.7 ou 3.1). Nous allons donc l’implémenter nous même.

Voici la classe qui permet de faire ça:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class FlashScope implements Scope {
 
    private ExternalContext getContext() {
        FacesContext currentInstance = FacesContext.getCurrentInstance();
        if (currentInstance != null)
            return currentInstance.getExternalContext();
        return null;
    }
 
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        ExternalContext context = getContext();
        if (context == null)
            return objectFactory.getObject();
        Flash flash = context.getFlash();
        Object object = flash.get(name);
        if (object == null) {
            object = objectFactory.getObject();
            flash.put(name, object);
        }
        return object;
    }
 
    @Override
    public Object remove(String name) {
        ExternalContext context = getContext();
        if (context != null)  {
            Flash flash = context.getFlash();
            if (flash != null) {
                return flash.remove(name);
            }
        }
        return null;
    }
 
    @Override
    public void registerDestructionCallback(String name, Runnable callback) { }
 
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
 
    @Override
    public String getConversationId() {
        return null;
    }
}

Voyons de plus près ce que fait cette classe:

  • Elle implémente l’interface Scope de Spring. Cette interface permet de créer notre custom scope
  • Les méthodes getConversationId, resolveContextualObject, registerDestructionCallback ne serviront pas, elles ne font donc rien
  • La méthode remove supprimera notre bean du scope si besoin est

Voici en détail ce que fait la méthode principale du scope, la méthode get. Celle ci prend en paramètre le nom de notre bean ainsi qu’une factory d’objets pour le générer grâce à Spring

  • 12/13/14: On récupère notre context JSF où l’on insèrera notre bean. S’il n’existe pas, on se contente de le retourner
  • 15: On récupère le « context » Flash de JSF. C’est cet objet qui gère spécifiquement les beans Flash pour JSF
  • 16: On tente de récupérer notre bean dans le context Flash
  • 17/18/19: Si notre bean n’existe pas, on le créé et on l’injecte dans le context Flash
  • 21: On retourne enfin le bean à notre utilisateur

 

Maintenant que notre scope est prêt, il nous faut indiquer à Spring que nous voulons l’utiliser. Pour ce faire, il suffit d’ajouter ceci dans notre applicationContext:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="flash">
                <bean class="net.blog.dev.common.FlashScope" />
            </entry>
        </map>
    </property>
</bean>

Remarquez que pour utiliser le scope, nous devrons utiliser le nom flash :)

 

Utilisation du scope au sein de Spring

Pour déclarer un bean comme étant dans le scope Flash, il suffit de faire ceci:

@Component("messageFlash")
@Scope("flash")
public class MessageFlashBean

Lorsque le bean « messageFlash » sera instancié, il sera automatiquement dans le scope Flash.

Maintenant que nous avons notre scope et que nous savons comment l’apposer sur un bean, voyons comment l’utiliser dans un autre bean (un controller par exemple).

La première idée qui vient à l’esprit (en tout cas pour moi ;) ) serait de faire comme ceci:

@Controller("personnesListController")
@Scope("view")
public class PersonnesListController {
 
@Autowired
@Qualifier("messageFlash")
private MessageFlashBean messageFlash;

Nous avons un controller en scope View dans lequel on autowired notre bean Flash pour l’utiliser.

Malheureusement, cette façon de faire ne marche pas. Voici pourquoi avec l’exemple suivant:

  • L’utilisateur arrive sur la première page
  • Il clic sur un lien pour aller sur la deuxième page
  • Il arrive sur la deuxième page où nous voulons afficher notre message

Notre controller est créé lors du premier accès à la première page, ce qui implique également la création du bean Flash. Or, comme nous l’avons vu plus haut, le bean en scope Flash ne survit que le temps entre deux requêtes.

Lors du clic sur le lien amenant à la seconde page, l’utilisateur envoi une requête au serveur (la seconde requête). Notre bean est toujours vivant et nous pouvons travailler avec.

Lors de la redirection vers la deuxième page, le context JSF est de nouveau recréé, ce qui peut être considéré comme la troisième requête. Notre bean Flash a donc été tué et celui que vous obtiendrez dans votre nouveau controller sera une nouvelle instance (donc inutilisable).

Pour pouvoir utiliser notre bean comme nous le souhaitons, il faudrait donc le créer au moment du clic sur le lien menant à la deuxième page. Voici comment faire:

1
2
3
4
5
6
7
8
9
10
11
12
@Controller("personnesListController")
@Scope("view")
public class PersonnesListController {
 
@Autowired
private ApplicationContext context;
 
public String action() {
    messageFlash = (MessageFlashBean) context.getBean("messageFlash");
    messageFlash.addMessage("Simulation de message Flash");
    return "page2";
}

Voici l’explication de ce code:

  • 5/6: On autowired le context Spring
  • 9: Dans l’action qui est appelée lors du clic de l’utilisateur, on demande au context Spring de nous fournir notre bean Flash. C’est à ce moment que celui ci est instancié

En utilisant ce code, vous pourrez récupérer votre bean Flash dans le controller de la seconde page de la même manière.

Pour accéder à un bean Flash dans une page JSF, vous pouvez faire comme suit:

<h:outputText value="#{flash.messageFlash.message}" />

 

Limitation du scope Flash

Aujourd’hui dans la version 2.1.6 de l’implémentation Mojarra de JSF, le scope Flash comporte encore de nombreux bugs (voir par ailleurs: http://java.net/jira), ce qui peut rendre son utilisation un peu aléatoire.

Un des plus ennuyeux étant le faite que le scope Flash n’est pas propagé entre des requêtes de différent niveaux de répertoires: Par exemple une requête de la page index.xhtml vers repertoire/page.xhtml ne propagera pas le scope.

Nul doute que ces bugs seront réglés dans les prochaines versions, faite toutefois attention en attendant :)

 

Voila pour cette présentation du scope Flash et de son implémentation avec Spring. Vous pouvez retrouver également la présentation de l’implémentation de l’autre nouveau scope de JSF 2 dans Spring, le scope View via cet article