Gestion des sessions

Manipulation des données dans les sessions

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Les sessions permettent de transmettre une information entre les requêtes. Leur gestion dans Spring n'est pas très différente de la gestion dans les applications écrites nativement avec des servlets. Cependant, elle mérite d'être expliquée. Les sessions sont une partie indissociable de chaque application web. On présentera l'exemple de leur vraie implémentation dans l'article consacré à la sécurité dans Spring.

L'interface HttpSession et la transmission des données

Elle fait partie de la du package javax.servlet.http. Grâce aux implémentations de cette interface on peut facilement identifier l'utilisateur à travers les requêtes. L'identification se fait grâce au paramètre unique associé à chaque session, JSESSIONID.

Le servlet container crée la session entre le client et le serveur HTTP. Elle persiste entre chaque requête pendant un temps déterminé. Une seule session peut correspondre à un seul utilisateur pendant ses plusieurs visites sur le site. Cette maintenance de la session peut se faire soit via un paramètre spécifié dans l'URL, soit grâce aux cookies.

On peut transmettre les instances des objets dans la session grâce à ses setters (entre autres setAttribute()) et ses getters (principalement getAttribute()).

Les attributs des sessions ne doivent pas implémenter l'interface Serializable. Pour preuve, voici un bout de code :

class Serials implements Serializable {
  public Serials() {
  
  }
  public String toString() {
    return "Serials ========";
  }
}

class Plains {
  public Plains() {
  
  }
  public String toString() {
    return "Plains ";
  }
}

// put it into your controller
HttpSession session = request.getSession();
session.setAttribute("serializable", new Serials());
session.setAttribute("plain", new Plains());
logger.info("serializable = " + session.getAttribute("serializable"));
logger.info("plain = " + session.getAttribute("plain"));

Les types primitifs sont également admis dans setAttribute().

Utiliser une session dans Spring

Comment alors utiliser les sessions dans Spring ? Plusieurs approches permettent de le faire. Certaines sont plus compliquées que les autres. Dans l'application qu'on développe, on utilisera la première méthode qui de loin paraît la plus simple.

  1. Utiliser l'instance HttpSession

    Il s'agit de passer l'instance HttpSession directement dans la liste des paramètres de la méthode. Tout comme on faisait cela déjà pour BindingResult ou @ModelAttribute. Cette solution est avantagée par sa simplicité de mise en place. Pourtant, elle a aussi des inconvénients. Elle nous oblige de vérifier si des éléments retournés par getAttribute() ne sont pas null. En outre, les tests unitaires se compliquent légèrement. Désormais, on doit "mocker" HttpSession.

    Notre application utilise les sessions pour gérer la validité des tokens CSRF :

    public class CSRFProtector {
        // ...
        public boolean checkSessionTokenValidity(HttpSession session) {
            return (session.getAttribute(TOKEN_VALIDITY_KEY) != null && (new Date().getTime() < (Long)session.getAttribute(TOKEN_VALIDITY_KEY)));
        }
    }
    
  2. Utiliser l'annotation @Scope

    Une autre approche consiste à utiliser l'annotation @Scope associée à la session (@Scope("session")) directement après avoir spécifié que la classe publique est un contrôleur :

    @Controller
    @Scope("session")
    public class BookingManagementController {
        
        private Booking booking = new Booking();
        
        @RequestMapping("/book")
        public String book() {
            // Booking's instance will be available as an object stored in session scope
        }
    }
    

    Cependant, cette solution a une inconvénient de taille. Elle peut poser des problèmes de performance de l'application car pour chaque session on crée un nouveau contrôleur. Le point positif est la clarté du code.

  3. Utiliser @Scope pour les objets

    L'avant-dernière solution consiste à annoter les objets qui doivent être placés dans la session avec @Scope("session"). Dans ce cas, le contrôleur qui l'utilise doit être annoté avec @Scope("request"). Regardons l'exemple précédent adapté à cette approche :

    @Scope("session")
    public class Booking {
    }
    
    @Controller
    @Scope("request")
    public class BookigManagementController {
    
        @Autowired
        private Booking booking;
        
        @RequestMapping("/book")
        public String book() {
            // Booking's instance will be available as an object stored in session scope
        }
    }
    

    Cependant, comme dans la deuxième solution, pour chaque requête une nouvelle instance du contrôleur est créée et placée dans la session. Les problèmes de performance peuvent alors arriver très vite.

  4. Utiliser l'AOP pour gérer la session

    La dernière méthode se base sur Aspect-Oriented Programming (AOP) qu'on a présenté dans l'article consacré à des programmation orientée aspect. Dans un premier temps on doit définir un bean qui va représenter l'objet qui doit être placé dans la session. Il est important de lui rajouter le tag <scoped-proxy /> (on verra plus loin de quoi il s'agit). Grâce à cela, dans le contrôleur, on peut injecter le bean avec @Autowired et puis l'utiliser comme s'il s'agissait d'un attribut normal de la classe. Voici l'exemple :

       <bean id="booking" class="library.model.entity.Booking" scope="session">
          <aop:scoped-proxy/>
       </bean>
    

    Et le contrôleur :

    @Controller
    public class BookigManagementController {
    
        @Autowired
        private Booking booking;
        
        @RequestMapping("/book")
        public String book() {
            // Booking's instance will be available as an object stored in session scope
        }
    }
    

    Comment fonctionne-t-elle ? La balise <aop:scoped-proxy /> permet d'injecter intelligemment un bean portée dans session (@Scope("session") dans l'exemple précédent) ou dans requête (@Scope("request")), dans un autre bean. Pour pouvoir le faire, cette balise doit être présente dans la définition du bean qui doit être accessible depuis un autre bean.

    Qu'est-ce que cela veut dire, intelligemment ? Le bean booking est un singleton. Spring crée donc une seule instance de cette classe. Ce bean sera aussi injecté exactement une fois dans ses dépendances. Cela signifie donc qu'on va opérer uniquement sur un seul objet Booking, ce qui n'est pas évident dans le cas d'une application qui censée de gérer les emprunts de plusieurs personnes à la fois... Ce qu'on veut, c'est une instance Booking spécifique par session.

    Pour accomplir cette tâche, on doit créer une sorte d'objet intelligent qui expose la même interface que Booking et qui sait détecter tout seul quelles données récupérer et quand "mourir" (ne plus être disponible). Cet objet intelligent est représenté par le proxy et la balise <aop:scoped-proxy />. Le contrôleur BookigManagementController à qui on injecte le bean booking, va ignorer qu'on lui soumet un proxy. Cependant, quand ce contrôleur va invoquer une méthode de Booking, réellement c'est la même méthode chez proxy qui va s'exécuter. Le dessin ci-dessous expliquera mieux ce mécanisme :

    Il s'agit d'une couche qui englobe le bean. Cette couche reprend la même interface ou, idéalement, le même objet de la classe utilisée que le bean.

    <bean id="booking />

    getBookingDate()

    Le contrôleur indique qu'il veut appeler la méthode Booking.getBookingDate(). Cependant, cette méthode ne se réfère pas à l'instance de la classe Booking, placée directement dans la définition du <bean />

    La méthode appelée pointe vers l'objet intelligent créé et géré par proxy.

       @Controller
       public class BookigManagementController {
          
          @Autowired
          private Booking booking;
          
          @RequestMapping("/book")
          public String book() {
            booking.getBookingDate()
          }
       }
    

Malgré la multitude des méthodes, le principe KISS nous impose le choix de la solution la plus simple. Dans le code de notre application on utilisera donc la première méthode, celle avec HttpSession dans la liste des paramètres.

Bartosz KONIECZNY Sessions

Une question ? Une remarque ?

*

*

Un conseil CSS

Comment centrer verticalement les inputs type radio et checkbox ?

L'alignement vertical des inputs radio et checkbox peut se faire avec la propriété CSS, vertical-align. Par exemple :

<input type="radio" name="typeUser" id="pro-1" style="vertical-align:top;"/><label for="pro-1">Professionnel</label>