Compréhension du Projet Spring Cache : Architecture et Implémentation

Dans cet article, nous allons analyser en détail une application Spring Cache destinée à illustrer les mécanismes de mise en cache avec Redis. Cette application, structurée selon l'architecture SSM (Spring + SpringMVC + MyBatis), démontre comment implémenter efficacement un système de cache. Examinons d'abord la structure globale du projet, qui comprend les répertoires config, controller, entity, mapper et la classe principale de l'application.

1. Répertoire config

Ce répertoire contient principalement des classes de configuration pour divers aspects de l'application tels que la documentation d'API, le mappage des ressources statiques et les intercepteurs.

Documentation d'API

La documentation d'API, généralement générée via Swagger2, facilite considérablement le test des fonctionnalités. Dans une architecture front-end/back-end séparée, il n'est pas nécessaire de démarrer les deux services pour valider les fonctions backend. La documentation accessible via http://127.0.0.1:8080/doc.html (par défaut) permet aux développeurs front de connaître précisément les paramètres de requête et les réponses attendues.

Par exemple, pour l'interface de connexion utilisateur, la documentation précise les paramètres à envoyer dans la requête POST. Ainsi, le développeur front peut construire son interface utilisateur tout en s'assurant que les appels respectent le contrat défini.

Mappage des ressources statiques

Le mappage des ressources statiques permet de convertir les adressses de requêtes pour les images et fichiers en chemins locaux. Par exemple, si un fichier doc.html se trouve sur le disque local à E:\resources, une requête pour /doc.html sera mappée à ce fichier local.

package com.projet.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class RessourcesConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // Mappage de /doc.html vers le fichier local E:\resources\doc.html
        registry.addResourceHandler("/doc.html")
                .addResourceLocations("file:E:/resources/");
    }
}

Configuration des intercepteurs

Les intercepteurs permettent de traiter préalablement les requêtes clientes. Les requêtes correspondant aux critères sont autorisées à poursuivre leur traitement, tandis que les autres sont rejetées. L'intercepteur JwtTokenAdminInterceptor ci-dessus intercepte toutes les requêtes sous /admin, à l'exception de /admin/employee/login. Les requêtes interceptées doivent passer par une vérification de jeton JWT dans la méthode preHandle. En cas d'échec, une réponse 401 est retournée.

@Component
@Slf4j
public class IntercepteurJwtAdmin implements HandlerInterceptor {

    @Autowired
    private ProprietesJwt proprietesJwt;

    /**
     * Vérification du JWT
     */
    public boolean preHandle(HttpServletRequest requete, HttpServletResponse reponse, Object gestionnaire) throws Exception {
        // Vérification si l'interception concerne une méthode Controller
        if (!(gestionnaire instanceof HandlerMethod)) {
            return true;
        }

        // 1. Récupération du jeton depuis l'en-tête de la requête
        String token = requete.getHeader(proprietesJwt.getNomTokenAdmin());

        // 2. Vérification du jeton
        try {
            log.info("Vérification JWT:{}", token);
            Claims claims = JwtUtil.parseJWT(proprietesJwt.getCleSecreteAdmin(), token);
            Long idEmploye = Long.valueOf(claims.get(ReclamationsJwt.ID_EMPLOYE).toString());
            log.info("ID employé actuel : {}", idEmploye);

            // Stockage de l'ID utilisateur dans ThreadLocal
            ContexteBase.setIdCourante(idEmploye);

            // 3. Autorisation de la poursuite
            return true;
        } catch (Exception ex) {
            // 4. Refus avec code 401
            reponse.setStatus(401);
            return false;
        }
    }
}

Les classes de configuration annotées avec @Configuration sont généralement chargées au démarrage de l'application, pour le package de la classe principale et ses sous-packages.

2. Controller

Dans l'architecture Spring MVC, le contrôleur est le composant central qui traît les requêtes clients et retourne des réponses. Il reçoit les requêtes HTTP, invoque la logique métier et génère soit des vues, soit des données JSON.

package com.projet.controleur;

import com.projet.entite.Utilisateur;
import com.projet.mapper.UtilisateurMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/utilisateur")
@Slf4j
public class ControleurUtilisateur {

    @Autowired
    private UtilisateurMapper utilisateurMapper;

    @PostMapping
    @CachePut(cacheNames = "cacheUtilisateur", key = "#utilisateur.id")
    public Utilisateur enregistrer(@RequestBody Utilisateur utilisateur){
        utilisateurMapper.inserer(utilisateur);
        return utilisateur;
    }

    @GetMapping
    @Cacheable(cacheNames = "cacheUtilisateur", key = "#id")
    public Utilisateur parId(Long id){
        Utilisateur utilisateur = utilisateurMapper.parId(id);
        return utilisateur;
    }

    @DeleteMapping
    @CacheEvict(cacheNames = "cacheUtilisateur", key = "#id")
    public void supprimerParId(Long id){
        utilisateurMapper.supprimerParId(id);
    }

    @DeleteMapping("/supprimerTout")
    @CacheEvict(cacheNames = "cacheUtilisateur", allEntries = true)
    public void supprimerTout(){
        utilisateurMapper.supprimerTout();
    }
}

Le contrôleur reçoit les requêtes frontales, traite les méthodes backend implémentées et retourne les résultats au front. Les méthodes spécifiques comme getAllUsers ne sont pas implémentées dans le contrôleur mais dans des classes ultérieures, justifiant le terme "pont".

@GetMapping  // Équivalent à @RequestMapping(method = RequestMethod.GET)
    public List<Utilisateur> obtenirTousLesUtilisateurs() {
        return serviceUtilisateur.obtenirTousLesUtilisateurs();
    }

L'annotation @RestController permet de recevoir toutes les requêtes clients (GET, POST, PUT, DELETE) et de convertir les résultats du contrôleur en JSON. @RequestMapping("/utilisateurs") cible les requêtes avec le préfixe URL /utilisateurs, tandis que @GetMapping et @PostMapping spécifient les méthodes HTTP.

Les annotations de cache comme @Cacheable(cacheNames = "cacheUtilisateur", key = "#id") sont essentielles à ce projet. Avant d'exécuter la méthode parId, le système vérifie d'abord dans Redis si l'information existe pour l'ID spécifique. Si trouvée, l'objet utilisateur est retourné directement, évitant l'accès à la base de données et réduisant significativement le temps de réponce.

3. Entité

Le répertoire entity contient les classes d'entités, dans ce cas la classe Utilisateur. Cette classe interagit avec MyBatis comme valeur de retour pour les requêtes de base de données et comme type de retour pour le contrôleur destiné au front.

package com.projet.entite;

import lombok.Data;
import java.io.Serializable;

@Data
public class Utilisateur implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    private String nom;

    private int age;

}

L'annotation @Data de Lombok génère automatiquement les getters, setters, toString et autres méthodes. L'interface Serializable assure la sérialisation nécessaire pour le stockage dans Redis.

4. Mapper

Le répertoire mapper contient les fichiers d'interaction avec la base de données. Dans ce projet, UtilisateurMapper gère les opérations d'insertion, de requête et de suppression. L'annotation @Mapper indique que c'est une interface Mapper de MyBatis, que Spring scanne et pour laquelle il crée automatiquement des implémentations.

package com.projet.mapper;

import com.projet.entite.Utilisateur;
import org.apache.ibatis.annotations.*;

@Mapper
public interface UtilisateurMapper{

    @Insert("insert into utilisateur(nom,age) values (#{nom},#{age})")
    @Options(useGeneratedKeys = true,keyProperty = "id")
    void inserer(Utilisateur utilisateur);

    @Delete("delete from utilisateur where id = #{id}")
    void supprimerParId(Long id);

    @Delete("delete from utilisateur")
    void supprimerTout();

    @Select("select * from utilisateur where id = #{id}")
    Utilisateur parId(Long id);
}

Pour les requêtes SQL simples, les annotations comme @Insert, @Delete et @Select suffisent. Pour des requêtes plus complexes, on utilise des fichiers de mappage XML. L'extension MyBatisX pour IDEA peut générer automatiquement ces mappages.

Conclusion

Le flux global de l'application peut être résumé ainsi : le client envoie une requête qui passe d'abord par l'intercepteur. Si ce n'est pas une requête de connexion, une vérification du jeton JWT est effectuée. Après validation, la requête est traitée par UtilisateurController. Pour une requête GET, le système vérifie d'abord dans Redis. Si les données existent, l'objet utilisateur est retourné directement ; sinon, la méthode est exécutée. La requête à la base de données peut être réalisée via des annotations SQL ou des fichiers de configuration XML. L'objet utilisateur trouvé est ensuite encapsulé en JSON et retourné au client.

Étiquettes: Spring Cache Redis MyBatis Spring MVC Swagger

Publié le 4 juillet à 22h06