Intégration de Spring

Pour intégrer Spring dans une application Struts, plusieurs étapes sont nécéssaires :

  • Configurer Spring, via un fichier de configuration XML. Ce fichier de configuration permet de paramétrer les Services, les DAO, ... utilisés dans l'application GestCV.
  • enregistrer la configuration via un Plugin Struts. Ce plugin permet d'initialiser le conteneur Spring et de le stocker dans le contexte de la Servlet.
  • appeler les services dans les Action Struts via un Service Locator qui récuperera les services configurés dans le conteneur Spring.

Voici un exemple de code qui permettra de charger (dans une action Struts) un collaborateur :

  Integer id = ...
  // Récupération d'un service user
  IUserService userService = ServiceLocator.getUserService();
  // Appel de la méthode findColloborateurByPrimaryKey du service userService
  Collaborateur collaborateur = userService.findColloborateurByPrimaryKey(id);

Configurer Spring

La conteneur Spring se configure via un fichier de configuration XML. Chacun des composants (Session Factory Hibernate, Service, DAO, ...) est décrit dans un élement bean, identifié par l'attribut id :

<bean id="myBean" >
        ....            
</bean>

Le conteneur Spring, est une sorte de fabrique, qui permet d'instancier et de récupérer les beans décrits dans le fichier de configuration. Un bean décrit peut faire référence à un autre bean décrit. Il est ensuite possible de récupérer une instance d'un bean (identifié par son id) en JAVA.

Dans le cadre de GestCV, les beans que l'on souhaite récupérés sont les services ou plus exactement un proxy. En effet, la méthode d'un service devant être transactionelle (mécanisme par AOP), le type d'objet service récupéré du conteneur Spring est un proxy transactionelle de type org.springframework.transaction.interceptor.TransactionProxyFactoryBean, qui surveille les appels des méthodes des classes Services implementées.

Dans le fichier de configuration Spring, la définition du service collaborateur se traduit par :

<!-- ***** PROXY SERVICE *****-->
<bean id="userService" 
         class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">       
    <property name="transactionManager"><ref local="myTransactionManager"/></property>
    <property name="target"><ref local="collaborateurTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly,-CommonsErrorException</prop>
            <prop key="create*">PROPAGATION_REQUIRED,-CommonsErrorException</prop>
            <prop key="update*">PROPAGATION_REQUIRED,-CommonsErrorException</prop>                              
            <prop key="delete*">PROPAGATION_REQUIRED,-CommonsErrorException</prop>                                      
        </props>
    </property>
</bean>

TransactionProxyFactoryBean qui est un proxy capable de rendre transactionelle N'IMPORTE QUELLE instance d'une classe, attend plusieurs paramètres. Ces paramètres sont des instances d'autres type d'objet qui sont passés au proxy par le mécanisme d'Injection Of Control (IoC). Ce proxy attend :

  • une instance transaction manager qui permet d'indiquer le type de transaction géré (JDBC, Hibernate,...). Ce type d'objet contient le code à déclencher pour la gestion des transactions. Il est renseigné au proxy de la manière suivante :
      <property name="transactionManager"><ref local="myTransactionManager"/></property>    

    myTransactionManager est un bean qui est aussi définit dans le fichier XML Spring. GestCV s'appuyant sur Hibernate 3, l'objet transaction manager sera de type Hibernate 3. Vous trouverez ici sa configuration.

  • une target, autrement dit une instance d'une classe qui doit être surveillé par le proxy. Dans le cas de GestCV, ce sera une classe service implementée (eg : UserServiceImpl).

    Il est renseigné au proxy de la manière suivante :

      <property name="target"><ref local="collaborateurTarget"/></property>

    collaborateurTarget est un bean qui est aussi définit dans le fichier XML Spring. Vous trouverez ici sa configuration.

  • les attributs de la transaction, autrement dit la description des règles de gestion d'une transaction (géré par l'objet transaction manager) en fonction de la méthode du service appelé. Les règles sont :
    • pour toutes les méthodes d'un service commançant par find, ouverture d'une connection (en lecture) avant l'appel de la méthode et fermeture de la connection après l'appel de la méthode. Cette règle se traduit par :
        <prop key="find*">PROPAGATION_REQUIRED,readOnly,-CommonsErrorException</prop>      

      La propriété PROPAGATION_REQUIRED signifie que l'ouverture de la connection s'effectue que si aucune connection n'est deja ouverte. Dans le cas contraire, le service utilisera la connection ouverte (propagation de la connection).

      La propriété readOnly signifie que seule une connection doit être ouverte en lecture seule (aucune transaction n'est démarrée).

    • pour toutes les méthodes d'un service commançant par create, update ou delete, ouverture d'une connection en mode transactionnelle avant l'appel de la méthode et execution d'un commit si tout s'est bien passé et d'un rollback si l'exception CommonsErrorException est lancée. Ces trois règles se traduisent par :
          <prop key="create*">PROPAGATION_REQUIRED,-CommonsErrorException</prop>
          <prop key="update*">PROPAGATION_REQUIRED,-CommonsErrorException</prop>                              
          <prop key="delete*">PROPAGATION_REQUIRED,-CommonsErrorException</prop>            

      L'exception CommonsErrorException est lancé lorsque une règle métier n'est pas respecté au sein de la méthode du service. Elle permet alors d'annuler la transation enc cours.

Hibernate Transaction manager

Dans le cas de GestCV, le type de transaction devant être géré est Hibernate 3. Spring implémente la classe org.springframework.orm.hibernate3.HibernateTransactionManager qui permet de gérer les transactions avec Hibernate 3. La configuration de cet objet nécessite de lui indiquer la session factory Hibernate (renseigné par Injection Of Control) à utiliser :

<!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
<bean id="myTransactionManager" 
         class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory"><ref local="mySessionFactory"/></property>
</bean> 

mySessionFactory est l'objet session factory, qui doit être configuré dans le conteneur Spring. Spring met à disposition plusieurs facon de configurer cette session factory. Personnelement je préfère ce type de configuration :

<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
         <property name="configLocation">
                <value>classpath:hibernate.cfg.xml</value>
        </property>                     
</bean>

Cette configuration permet de décrire la session factory dans le fichier hibernate.cfg.xml stocké dans le classpath du projet, et permet de garder la configuration classique d'Hibernate.

Service implementation

Avant de décrire la configuration de la classe service à utiliser en tant que target, revenons à l'exemple de récupération d'un service :

  // Récupération d'un service collaborateur
  IUserService userService = ServiceLocator.getUserService();

Comme vous pouvez le remarquer, le service récupérée du conteneur est une interface et pas la classe qui implémente le service. Ceci est dù au fait de l'utilisation d'un proxy.

Ceci nécessite donc de créer une interface du service et une classe qui implémente celle-ci. L'interface IUserService ressemblerait à :

  public interface IUserService {
    public Collaborateur findColloborateurByPrimaryKey(Integer id);
  }  

L'implémentation UserServiceImpl qui fait appel à la couche DAO ressemblerait à :

  public class UserServiceImpl implements IUserService {  
    // Interfaces DAO, renseignées par IoC
    public ICollaborateurDAO collaborateurDAO;
    public void setCollaborateurDAO(ICollaborateurDAO collaborateurDAO) {
        this.collaborateurDAO = collaborateurDAO;
    }   
    ....    
    
    public Collaborateur findColloborateurByPrimaryKey(Integer id) {
            return (Collaborateur)collaborateurDAO.findByPrimaryKey(Collaborateur.class, id);
    }   
  }  

La DAO collaborateurDAO, utilisée dans le service findColloborateurByPrimaryKey est une interface ICollaborateurDAO. GestCV implementera cette interface en Hibernate. Le mécanisme d'Injection Of Control est encore utilisée pour passer l'instance DAO implementée en Hibernate. La configuration du service ressemble à :

<bean id="userTarget" class="net.sourceforge.gestcv.service.spring.UserServiceImpl">
        <property name="collaborateurDAO"><ref local="myCollaborateurDAO"/></property>
        ....
</bean>   

myCollaborateurDAO est un bean qui est aussi définit dans le fichier XML Spring. Vous trouverez ici sa configuration.

DAO implementation

Suite aux mécanismes d'IoC, utilisé dans les services, une DAO est découpée en une interface et une implémentation. Une DAO a besoin de connaîre la session factory. Elle sera renseignée aussi par le mécanisme d'Injection Of Control, d'où la nécéssité de définir un setter setSessionFactory dans l'implémentation de la DAO. Cependant Spring propose une classe de base DAO org.springframework.orm.hibernate3.support.HibernateDaoSupport qui définit ce setter et permet d'accéder à la session Hibernate simplement.

L'interface ICollaborateurDAO ressemblerait à :

  public interface ICollaborateurDAO {
    public Collaborateur findByPrimaryKey(Integer id);
  }  

L'implémentation CollaborateurHibernateDAO ressemblerait à :

  public class CollaborateurHibernateDAO extends HibernateDaoSupport implements ICollaborateurDAO {  
   
   public Collaborateur findByPrimaryKey(Integer id) {
     // Recuperation de la session (méthode mis à disposition par HibernateDaoSupport)
     Session session = getSession();
     return session.get(Collaborateur.class, id);
   }
   
  }  

La configuration de la DAO dans Spring est :

<bean id="myCollaborateurDAO" 
         class="net.sourceforge.gestcv.dao.hibernate.CollaborateurHibernateDAO">
        <property name="sessionFactory"><ref local="mySessionFactory"/></property>
</bean>

Plugin Struts

Spring propose un Plugin Struts qui permet de charger le conteneur Spring via le fichier de configuration XML souhaité :

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
        <set-property property="contextConfigLocation" value="/WEB-INF/spring-config.xml"/>
</plug-in>

Après avoir chargé la configuration, le plugin stocke l'objet WebApplicationContext qui correspond au conteneur Spring dans une Application WEB dans le contexte de la servlet. L'instance WebApplicationContext peut ensuite être ensuite accéder de la manière suivante :

  servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX);

Service Locator

Les Actions Struts sont les principales classes qui feront appels aux services de l'application. Ces services doivent être récupérés d'une manière ou d'une autre via le conteneur Spring. Spring donne la possibilité de passer ces services par Injection Of Control aux Actions Struts qui utilisent ces services. Ceci nécéssite pour chacune des actions Struts de définir un bean dans le conteneur Spring et de définir par IoC les services utilisées par l'action. Ce mécanisme semble à priori plaisant, mais lors du dévelopement d'une nouvelle action, il faut à la fois définir l'action dans le struts-config et à la fois définir l'action dans le fichier de configuration de spring, pour indiquer à l'action quelles sont les services qui doivent être renseignés par IoC.

Pour pallier à ce problème, GestCV met en place la notion de ServiceLocator. Cette classe permet de retourner un bean définit dans le conteneur Spring à partir d'un id. Le ServiceLocator de gestCV contient tous les getters sur les interfaces possibles des services utilisés dans l'application. Cette classe ressemble à :

public class ServiceLocator {

    public static IUserService getUserService() {       
      // Retourne un proxy userService
      return (IUserService)SpringUtil.getBean("userService");
    }
}

La classe SpringUtil est une classe utilitaire qui permet de retourner la définition d'un bean définit dans le conteneur Spring initialisé par le Plugin Struts. Elle nécéssite une initialisation (passage du contexte de la servlet), qui peut s'effectuer dans l'init de la Servlet Struts, comme ceci :

SpringUtil.setServletContext(getServletContext());  

La récupération d'un service, peut ensuite s'effectuer de la façon suivante :

  // Récupération d'un service collaborateur
  IUserService userService = ServiceLocator.getUserService();