Intégration du framework SSH : Configuration et exemple avec la gestion de livres

Le framework SSH est une intégration des technologies Struts, Spring et Hibernate pour le développement d'applications web Java. Il ne doit pas être confondu avec le protocole Secure Shell. Cette architecture répartit les responsabilités en quatre couches : présentation, logique métier, persistacne des données et objets du domaine. Struts fournit le squelette MVC et gère le routage des actions, Hibernate s'occupe de la persistance, tandis que Spring orchestre et injecte les dépendances entre les composants.

L'adoption d'un tel framework standardisé permet de structurer le code, d'accélérer le développement et de faciliter la maintenance des applications complexes. Il offre des solutions préconçues aux problèmes courants, permettant aux développeurs de se concentrer sur la logique métier.

Étapes d'intégration

1. Dépendances Maven (pom.xml)

La configuration des dépendances est la première étape. Voici un exemple de fichier POM configuré pour les versions respectives des bibliothèques.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo-ssh</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <hibernate.version>5.4.30.Final</hibernate.version>
        <mysql.version>8.0.23</mysql.version>
        <spring.version>5.3.8</spring.version>
        <struts.version>2.5.26</struts.version>
        <!-- Autres propriétés de version... -->
    </properties>
    <dependencies>
        <!-- Dépendances Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <!-- Dépendances Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Dépendances Struts2 -->
        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-spring-plugin</artifactId>
            <version>${struts.version}</version>
        </dependency>
        <!-- Connecteur JDBC, Tests, Servlet API, JSTL, etc. -->
        ...
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2. Fichiers de configuration

web.xml

Ce fichier initialise le contexte Spring, le filtre Struts2 et le filtre d'encodage des caractères.

<web-app>
    <!-- Chargement du contexte Spring -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Filtre Struts2 -->
    <filter>
        <filter-name>actionFilter</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>actionFilter</filter-name>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

    <!-- Filtre d'encodage -->
    <filter>
        <filter-name>charsetFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>charsetFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

applicationContext.xml (Spring)

Point d'entrée principal de la configuration Spring, qui importe d'autres modules de configuration.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:spring-persistence.xml"/>
    <import resource="classpath:spring-mvc.xml"/>
</beans>

struts.xml et struts-base.xml

Configuration de Struts2 pour définir les paquets d'actions de base.

<!-- struts-base.xml -->
<struts>
    <constant name="struts.devMode" value="false"/>
    <constant name="struts.i18n.encoding" value="UTF-8"/>

    <package name="default-package" extends="struts-default" abstract="true">
        <global-allowed-methods>regex:.*</global-allowed-methods>
    </package>
</struts>

<!-- struts.xml -->
<struts>
    <include file="struts-default.xml"/>
    <include file="struts-base.xml"/>
    <include file="struts-catalog.xml"/> <!-- Fichier spécifique au module -->
</struts>

spring-persistence.xml

Configure la source de données, la SessionFactory d'Hibernate et la gestion des transactions.

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <!-- Lecture des propriétés de la base de données -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!-- Pool de connexions C3P0 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- Autres propriétés du pool -->
    </bean>

    <!-- SessionFactory d'Hibernate -->
    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
        <!-- Emplacement des mappings -->
        <property name="packagesToScan" value="com.example.domain"/>
    </bean>

    <!-- Gestionnaire de transactions -->
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <!-- Support des annotations @Transactional -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

Exemple : Application de gestion de livres

Couche Domaine (Entité et Mapping)

L'entité représentant un livre.

package com.example.domain;

import javax.persistence.*;

@Entity
@Table(name = "t_book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column
    private String author;

    @Column
    private Double unitPrice;

    // Constructeurs, Getters, Setters...
    public Book() {}

    public Book(String title, String author, Double unitPrice) {
        this.title = title;
        this.author = author;
        this.unitPrice = unitPrice;
    }
    // ... getters et setters ...
}

Mapping Hibernate avec annotations (alternatif au fichier .hbm.xml).

Couche DAO (Accès aux données)

Interface et implémentation pour les opérations sur les livres.

package com.example.dao;

import com.example.domain.Book;
import java.util.List;

public interface BookDao {
    void save(Book entity);
    Book findById(Long id);
    List<Book> findAll();
    // ... autres méthodes CRUD ...
}

package com.example.dao.impl;

import com.example.dao.BookDao;
import com.example.domain.Book;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public void save(Book entity) {
        sessionFactory.getCurrentSession().persist(entity);
    }

    @Override
    public Book findById(Long id) {
        return sessionFactory.getCurrentSession().get(Book.class, id);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Book> findAll() {
        return sessionFactory.getCurrentSession()
                .createQuery("FROM Book b ORDER BY b.title")
                .list();
    }
}

Couche Service (Logique métier)

Interface et implémentation qui utilise le DAO. La transaction est gérée via l'annotation @Transactional.

package com.example.service;

import com.example.domain.Book;
import java.util.List;

public interface CatalogService {
    void addBook(Book book);
    List<Book> getAllBooks();
    Book getBookDetails(Long bookId);
}

package com.example.service.impl;

import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.CatalogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional
public class CatalogServiceImpl implements CatalogService {

    @Autowired
    private BookDao bookDao;

    @Override
    public void addBook(Book book) {
        bookDao.save(book);
    }

    @Override
    @Transactional(readOnly = true)
    public List<Book> getAllBooks() {
        return bookDao.findAll();
    }

    @Override
    @Transactional(readOnly = true)
    public Book getBookDetails(Long bookId) {
        return bookDao.findById(bookId);
    }
}

Couche Contrôleur (Action Struts2)

L'action qui reçoit les requêtes HTTP et appelle le service.

package com.example.web;

import com.example.domain.Book;
import com.example.service.CatalogService;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import org.apache.struts2.interceptor.ServletRequestAware;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

public class CatalogAction extends ActionSupport implements ModelDriven<Book>, ServletRequestAware {

    private CatalogService catalogService;
    private Book bookModel = new Book();
    private List<Book> bookList;
    private HttpServletRequest request;

    // Setters pour l'injection de dépendance (Spring)
    public void setCatalogService(CatalogService catalogService) {
        this.catalogService = catalogService;
    }

    public String list() {
        this.bookList = catalogService.getAllBooks();
        return SUCCESS;
    }

    public String add() {
        catalogService.addBook(bookModel);
        // Redirige vers la liste après ajout
        return "redirectToList";
    }

    // ... getters pour bookList, etc.

    @Override
    public Book getModel() {
        return this.bookModel;
    }

    @Override
    public void setServletRequest(HttpServletRequest request) {
        this.request = request;
    }
}

Configuration du module (Spring & Struts)

Configuration des beans pour le module "catalogue".

<!-- spring-mvc.xml (ou fichier spécifique au module) -->
<beans ...>
    <bean id="bookDao" class="com.example.dao.impl.BookDaoImpl"/>
    <bean id="catalogService" class="com.example.service.impl.CatalogServiceImpl"/>
    <bean id="catalogAction" class="com.example.web.CatalogAction" scope="prototype">
        <property name="catalogService" ref="catalogService"/>
    </bean>
</beans>

<!-- struts-catalog.xml -->
<struts>
    <package name="catalog" namespace="/catalog" extends="default-package">
        <action name="list" class="catalogAction" method="list">
            <result name="success">/WEB-INF/views/bookList.jsp</result>
        </action>
        <action name="add" class="catalogAction" method="add">
            <result name="redirectToList" type="redirect">/catalog/list.do</result>
        </action>
    </package>
</struts>

Exemple d'interface (JSP)

Une page JSP simple pour afficher la liste des livres, utilisant les balises JSTL.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Catalogue de Livres</title>
</head>
<body>
    <h1>Liste des Livres</h1>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>Titre</th>
            <th>Auteur</th>
            <th>Prix</th>
        </tr>
        <c:forEach var="book" items="${bookList}">
            <tr>
                <td>${book.id}</td>
                <td>${book.title}</td>
                <td>${book.author}</td>
                <td>${book.unitPrice}</td>
            </tr>
        </c:forEach>
    </table>
    <a href="add.do">Ajouter un nouveau livre</a>
</body>
</html>

Pour ajouter un livre, un formulaire similaire collecterait les données et soumettrait une requête POST vers l'action add.do.

Étiquettes: Java SSH struts2 Spring Hibernate

Publié le 1 juillet à 19h29