Ce site met a disposition le build journalier de la traduction francaise du Maven: The Definitive Guide
Consultez :
  • Les documents de reference sur le projet original
  • Les sources de la traduction fr sur GitHub
  • maven


    7.6. Le module simple-webapp

    L'application Web est définie dans le module simple-webapp. Ce projet va définir deux contrôleurs Spring MVC : WeatherController et HistoryController. Ces deux contrôleurs vont référencer les composants des modules simple-weather et simple-persist. Le conteneur Spring est configuré dans le fichier web.xml de l'application web, celui-ci réfère le fichier applicationContext-weather.xml du module simple-weather et le fichier applicationContext-persist.xml du module simple-persist. L'architecture des composants de cette application web simple est montrée sur la Figure 7.3, « Contrôleurs Spring MVC référençant les modules simple-weather et simple-persist. ».

    Contrôleurs Spring MVC référençant les modules simple-weather et simple-persist.

    Figure 7.3. Contrôleurs Spring MVC référençant les modules simple-weather et simple-persist.


    Le POM du module simple-webapp est affiché dans l'Exemple 7.12, « POM du module simple-webapp ».

    Exemple 7.12. POM du module simple-webapp

    <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>
      <parent>
        <groupId>org.sonatype.mavenbook.multispring</groupId>
        <artifactId>simple-parent</artifactId>
        <version>1.0</version>
      </parent>
    
      <artifactId>simple-webapp</artifactId>
      <packaging>war</packaging>
      <name>Simple Web Application</name>
      <dependencies>
        <dependency> 1
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
          <version>2.4</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.sonatype.mavenbook.multispring</groupId>
          <artifactId>simple-weather</artifactId>
          <version>1.0</version>
        </dependency>
        <dependency>
          <groupId>org.sonatype.mavenbook.multispring</groupId>
          <artifactId>simple-persist</artifactId>
          <version>1.0</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.0.7</version>
        </dependency>
        <dependency>
          <groupId>org.apache.velocity</groupId>
          <artifactId>velocity</artifactId>
          <version>1.5</version>
        </dependency>
      </dependencies>
      <build>
        <finalName>simple-webapp</finalName>
        <plugins>
          <plugin> 2
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            <dependencies>3
              <dependency>
                <groupId>hsqldb</groupId>
                <artifactId>hsqldb</artifactId>
                <version>1.8.0.7</version>
              </dependency>
            </dependencies>        
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId> 4
            <artifactId>hibernate3-maven-plugin</artifactId>
            <version>2.0</version>
            <configuration>
              <components>
                <component>
                  <name>hbm2ddl</name>
                  <implementation>annotationconfiguration</implementation> 5
                </component>
              </components>
            </configuration>
            <dependencies>
              <dependency>
                <groupId>hsqldb</groupId>
                <artifactId>hsqldb</artifactId>
                <version>1.8.0.7</version>
              </dependency>
            </dependencies>        
          </plugin>
        </plugins>
      </build>
    </project>
    

    Au fur et à mesure que nous avançons dans ce livre, les exemples deviennent de plus en plus substantiels. Vous avez d'ailleurs probablement noté que le fichier pom.xml commence à devenir volumineux. Dans ce POM, nous configurons quatre dépendances et deux plugins. Regardons ce POM en détail et étendons-nous sur certains des points de configuration importants :

    1

    Ce projet simple-webapp définit quatre dépendances : les spécifications Servlet 2.4, la bibliothèque de services simple-weather, la bibliothèque de persistance simple-persist et l'intégralité du Spring Framework 2.0.7.

    2

    Le plugin Maven Jetty est utilisé de la manière la plus simple qui soit dans ce projet. Nous ajoutons simplement la balise plugin qui fait référence aux bons groupId et artifactId. Le fait que ce plugin soit facile à configurer signifie que les développeurs du plugin ont fait du bon travail en fournissant les valeurs par défaut adéquates pour la plupart des cas. Si vous avez besoin de surcharger cette configuration, vous pouvez le faire en ajoutant une balise configuration.

    3

    Dans notre configuration de build, nous allons configurer le plugin Maven Hibernate3 pour qu'il utilise une instance de la base de données embarquée HSQLDB. Pour que le plugin Maven Hibernate3 arrive à se connecter à la base, il doit référencer le driver JDBC d'HSQLDB dans son classpath. Pour rendre cette dépendance disponible au plugin, nous ajoutons sa déclaration directement dans la déclaration du plugin. Ici, nous référençons hsqldb:hsqldb:1.8.0.7. Le plugin Hibernate a également besoin des drivers JDBC pour créer cette base de données, nous avons donc ajouté cette dépendance dans sa configuration.

    4

    C'est à partir de l'utilisation du plugin Maven Hibernate3 que ce POM devient le plus intéressant. Dans la section suivante, nous allons exécuter le goal hbm2ddl pour générer une base de données HSQLDB. Nous avons inclus dans ce pom.xml une référence vers la version 2.0 du hibernate3-maven-plugin récupérable à partir du dépôt Codehaus Mojo.

    5

    Le plugin Maven Hibernate3 dispose de plusieurs moyens pour récupérer le mapping Hibernate en fonction du cas d'utilisation. Si vous utilisez des fichiers XML pour le Mapping Hibernate (.hbm.xml), et que vous voulez générer les classes de votre modèle par l'intermédiaire du goal hbm2java, vous devez indiquer votre implémentation à la configuration. Si vous utilisez le plugin Hibernate3 en reverse engineering pour générer les fichiers .hbm.xml et les objets du modèle à partir d'une base de données existante, vous voudrez utiliser l'implémentation jdbcconfiguration. Dans notre exemple, nous utilisons simplement les objets annotés du modèle pour générer la base de données. En d'autres termes, nous avons notre mapping Hibernate, mais nous n'avons pas encore de base de données. Pour ce scénario, annotationconfiguration est l'implémentation la plus appropriée. Le plugin Maven Hibernate3 sera plus largement détaillé dans la Section 7.7, « Exécution de l'application web ».

    Note

    Une erreur classique consiste à utiliser la balise extensions de la configuration pour ajouter des dépendances nécessaires à un plugin. Il est fortement découragé de procéder de la sorte car les extensions peuvent polluer le classpath de votre projet, et provoquer des effets de bord désagréables. De plus, le comportement des extensions a été modifié depuis la version 2.1 de Maven. Le seul usage correct de la balise extensions est l'ajout d'implémentations complémentaires.

    Regardons maintenant les deux contrôleurs Spring MVC, chacun d'entre eux référence des beans déclarés dans les simple-weather et simple-persist.

    Exemple 7.13. WeatherController du module simple-webapp

    package org.sonatype.mavenbook.web;
    
    import org.sonatype.mavenbook.weather.model.Weather;
    import org.sonatype.mavenbook.weather.persist.WeatherDAO;
    import org.sonatype.mavenbook.weather.WeatherService;
    import javax.servlet.http.*;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    
    public class WeatherController implements Controller {
    
      private WeatherService weatherService;
      private WeatherDAO weatherDAO;
    
      public ModelAndView handleRequest(HttpServletRequest request,
          HttpServletResponse response) throws Exception {
    
        String zip = request.getParameter("zip");
        Weather weather = weatherService.retrieveForecast(zip);
        weatherDAO.save(weather);
        return new ModelAndView("weather", "weather", weather);
      }
    
      public WeatherService getWeatherService() {
        return weatherService;
      }
    
      public void setWeatherService(WeatherService weatherService) {
        this.weatherService = weatherService;
      }
    
      public WeatherDAO getWeatherDAO() {
        return weatherDAO;
      }
    
      public void setWeatherDAO(WeatherDAO weatherDAO) {
        this.weatherDAO = weatherDAO;
      }
    }

    WeatherController implémente l'interface Controller qui assure la présence d'une méthode handleRequest() dont la signature est montrée dans cet exemple. Si vous regardez le corps de cette méthode, vous constaterez que celle-ci invoque la méthode retrieveForecast() du service weatherService. Contrairement au chapitre précédent, dans lequel une servlet instancie la classe WeatherService, le WeatherController est un bean contenant une propriété weatherService. Le conteneur Spring IoC a la responsabilité de charger le service weatherService dans le contrôleur. Notez également que nous n'utilisons pas le WeatherFormatter dans ce contrôleur. Au lieu de cela, nous passons l'objet Weather retourné par la méthode retrieveForecast() au constructeur de la classe ModelAndView. Cette classe ModelAndView est utilisée pour effectuer le rendu du template Velocity. Ce template disposera d'une référence sur la variable ${weather} contenant ce même objet. Le template weather.vm se trouve dans le répertoire src/main/webapp/WEB-INF/vm, celui-ci est affiché dans l'Exemple 7.14, « Modèle weather.vm interprété par le WeatherController ».

    Dans ce WeatherController, avant d'effectuer le rendu des prévisions météo, nous passons l'objet Weather retourné par le service WeatherService à la méthode save() de la classe WeatherDAO. Cet objet est sauvegardé par Hibernate en base de données. Plus tard, dans le contrôleur HistoryController, nous verrons comment récupérer l'historique de ces prévisions météorologiques.

    Exemple 7.14. Modèle weather.vm interprété par le WeatherController

    <b>Current Weather Conditions for:
      ${weather.location.city}, ${weather.location.region}, 
      ${weather.location.country}</b><br/>
      
    <ul>
      <li>Temperature: ${weather.condition.temp}</li>
      <li>Condition: ${weather.condition.text}</li>
      <li>Humidity: ${weather.atmosphere.humidity}</li>
      <li>Wind Chill: ${weather.wind.chill}</li>
      <li>Date: ${weather.date}</li>
    </ul>
    

    La syntaxe de ce modèle Velocity est rapide à expliquer, les variables sont référencées par l'intermédiaire de la notation ${}. L'expression entre les accolades fait référence à une propriété, ou à une propriété d'une propriété de la variable weather passée à ce modèle par le WeatherController.

    Le contrôleur HistoryController est utilisé pour récupérer la liste des prévisions météorologiques les plus récentes parmi celles demandées par le WeatherController. Chaque fois que nous récupérons une prévision dans le WeatherController, celui-ci enregistre l'objet Weather récupéré dans la base de données via le WeatherDAO. Ce DAO utilise ensuite Hibernate pour transformer l'objet Weather en une série de lignes dans un ensemble de tables de la base de données. Le contrôleur HistoryController est affiché dans l'Exemple 7.15, « HistoryController du module simple-web ».

    Exemple 7.15. HistoryController du module simple-web

    package org.sonatype.mavenbook.web;
    
    import java.util.*;
    import javax.servlet.http.*;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    import org.sonatype.mavenbook.weather.model.*;
    import org.sonatype.mavenbook.weather.persist.*;
    
    public class HistoryController implements Controller {
    
      private LocationDAO locationDAO;
      private WeatherDAO weatherDAO;
    
      public ModelAndView handleRequest(HttpServletRequest request,
          HttpServletResponse response) throws Exception {
        String zip = request.getParameter("zip");
        Location location = locationDAO.findByZip(zip);
        List<Weather> weathers = weatherDAO.recentForLocation( location );
    
        Map<String,Object> model = new HashMap<String,Object>();
        model.put( "location", location );
        model.put( "weathers", weathers );
    
        return new ModelAndView("history", model);
      }
    
      public WeatherDAO getWeatherDAO() {
        return weatherDAO;
      }
    
      public void setWeatherDAO(WeatherDAO weatherDAO) {
        this.weatherDAO = weatherDAO;
      }
    
      public LocationDAO getLocationDAO() {
        return locationDAO;
      }
    
      public void setLocationDAO(LocationDAO locationDAO) {
        this.locationDAO = locationDAO;
      }
    }

    Le contrôleur HistoryController est chargé avec les deux DAOs du module simple-persist. Les instances de ces deux DAOs sont portées par les propriétés WeatherDAO et LocationDAO. Le but de l'HistoryController est de récupérer une List d'objets Weather en fonction du paramètre zip. Quand le WeatherDAO enregistre un objet Weather dans la base de données, il ne se contente pas de stocker le code postal, il enregistre un objet Location qui est lié à l'objet Weather du module simple-model. Pour récupérer la List des objets Weather, l'HistoryController commence par récupérer l'objet Location qui correspond au paramètre zip représentant le code postal. Pour cela, on invoque la méthode findByZip() du DAO LocationDAO.

    Une fois cet objet Location récupéré, l'HistoryController essayera de récupérer les objets Weather les plus récents associés à cette Location. Une fois la List<Weather> récupérée, une HashMap est créée contenant les deux variables du modèle Velocity history.vm de l'Exemple 7.16, « Modèle history.vm utilisé par l'HistoryController ».

    Exemple 7.16. Modèle history.vm utilisé par l'HistoryController

    <b>
    Weather History for: ${location.city}, ${location.region}, ${location.country}
    </b>
    <br/>
      
    #foreach( $weather in $weathers )
      <ul>
        <li>Temperature: $weather.condition.temp</li>
        <li>Condition: $weather.condition.text</li>
        <li>Humidity: $weather.atmosphere.humidity</li>
        <li>Wind Chill: $weather.wind.chill</li>
        <li>Date: $weather.date</li>
      </ul>
    #end
    

    Le modèle history.vm du dossier src/main/webapp/WEB-INF/vm référence la variable location pour afficher les prévisions météo provenant du WeatherDAO. Ce template utilise une structure de contrôle Velocity, #foreach, pour itérer sur chaque élément de la variable weathers. Chaque élément de la liste weathers est assigné à une variable nommée weather. Le cœur de la boucle, entre #foreach et #end, permet d'afficher les informations de chaque prévision.

    Nous venons de voir les implémentations de nos deux Controller. Nous venons également de voir comment ils référencent les beans des modules simple-weather et simple-persist. Ces Controller répondent aux requêtes HTTP par l'intermédiaire de mystérieux systèmes qui savent comment effectuer le rendu de templates Velocity. Toute la magie est configurée dans l'ApplicationContext Spring du fichier src/main/webapp/WEB-INF/weather-servlet.xml. Ce XML configure les contrôleurs et référence d'autres beans managés par Spring. Il est chargé par un ServletContextListener qui est configuré pour charger également les fichiers applicationContext-weather.xml et applicationContext-persist.xml à partir du classpath. Regardons de plus près le fichier weather-servlet.xml de l'Exemple 7.17, « Configuration des contrôleurs Spring du fichier weather-servlet.xml ».

    Exemple 7.17. Configuration des contrôleurs Spring du fichier weather-servlet.xml

    <beans>  
         <bean id="weatherController" 1
               class="org.sonatype.mavenbook.web.WeatherController">
           <property name="weatherService" ref="weatherService"/>
           <property name="weatherDAO" ref="weatherDAO"/>
         </bean>
    
         <bean id="historyController" 
               class="org.sonatype.mavenbook.web.HistoryController">
           <property name="weatherDAO" ref="weatherDAO"/>
           <property name="locationDAO" ref="locationDAO"/>
         </bean>
    
         <!-- you can have more than one handler defined -->
         <bean id="urlMapping" 
         class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
              <property name="urlMap">
                   <map>
                        <entry key="/weather.x"> 2
                             <ref bean="weatherController" />
                        </entry>
                        <entry key="/history.x">
                             <ref bean="historyController" />
                        </entry>
                   </map>
              </property>
         </bean>
    
    
         <bean id="velocityConfig" 3
       class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
           <property name="resourceLoaderPath" value="/WEB-INF/vm/"/>
         </bean>
    
         <bean id="viewResolver" 4
       class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
           <property name="cache" value="true"/>
           <property name="prefix" value=""/>
           <property name="suffix" value=".vm"/>
           <property name="exposeSpringMacroHelpers" value="true"/>
         </bean>
    </beans>
    

    1

    Le fichier weather-servlet.xml définit deux contrôleurs Spring. weatherController a deux propriétés qui référencent weatherService et weatherDAO. historyController référence les beans weatherDAO et locationDAO. Quand l'ApplicationContext est créé, il est créé dans un environnement qui donne accès aux ApplicationContexts définit dans les fichiers simple-persist et simple-weather. Dans l'Exemple 7.18, « web.xml du module simple-webapp » vous pouvez voir comment Spring est configuré pour fusionner plusieurs composants contenant des fichiers de configuration Spring différents.

    2

    Le bean urlMapping définit les formats des URL pouvant invoquer les contrôleurs WeatherController et HistoryController. Dans cet exemple, nous utilisons le SimpleUrlHandlerMapping et associons /weather.x au WeatherController et /history.x à l'HistoryController.

    3

    Comme nous utilisons le moteur de template Velocity, nous avons besoin de fournir quelques options de configuration spécifiques. Dans le bean velocityConfig, nous demandons à Velocity de rechercher toutes les modèles présents dans le répertoire /WEB-INF/vm.

    4

    Enfin, le viewResolver est configuré avec la classe VelocityViewResolver. Il existe un bon nombre d'implémentations du ViewResolver dans Spring, du standard ViewResolver pour afficher une JSP ou JSTL au ViewResolver capable d'effectuer le rendu de modèles Freemarker. Dans cet exemple, nous configurons le moteur de template Velocity et modifions les préfixes et suffixes par défaut pour modifier automatiquement les noms des modèles passés aux objets ModelAndView.

    Pour finir, le projet simple-webapp possède un fichier web.xml qui fournit la configuration de base à l'application web. Le fichier web.xml est affiché dans l'Exemple 7.18, « web.xml du module simple-webapp ».

    Exemple 7.18. web.xml du module simple-webapp

    <web-app id="simple-webapp" version="2.4" 
         xmlns="http://java.sun.com/xml/ns/j2ee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
      <display-name>Simple Web Application</display-name>
      
      <context-param> 1
        <param-name>contextConfigLocation</param-name>
        <param-value>
          classpath:applicationContext-weather.xml
          classpath:applicationContext-persist.xml
        </param-value>
      </context-param>
      
      <context-param> 2
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/log4j.properties</param-value>
      </context-param>
      
      <listener> 3
        <listener-class>
          org.springframework.web.util.Log4jConfigListener
        </listener-class>
      </listener>
      
      <listener>
        <listener-class> 4
         org.springframework.web.context.ContextLoaderListener
        </listener-class>
      </listener>
      
      <servlet> 5
        <servlet-name>weather</servlet-name>
        <servlet-class>
          org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      
      <servlet-mapping> 6
        <servlet-name>weather</servlet-name>
        <url-pattern>*.x</url-pattern>
      </servlet-mapping>
    </web-app>
    

    1

    Voici un peu de magie qui nous permet la réutilisation des fichiers applicationContext-weather.xml et applicationContext-persist.xml dans ce projet. Le contextConfigLocation est utilisé par ContextLoaderListener pour créer un ApplicationContext. Quand la servlet weather est créée, le fichier weather-servlet.xml de l'Exemple 7.17, « Configuration des contrôleurs Spring du fichier weather-servlet.xml » évalue l'ApplicationContext créé à partir du contextConfigLocation. Par ce moyen, vous pouvez définir un ensemble de beans dans un autre projet et les référencer par l'intermédiaire du classpath. Comme les JARs des modules simple-persist et simple-weather seront placés dans WEB-INF/lib, tout ce que nous avons à faire est d'utiliser le préfixe classpath: pour référencer ces fichiers. (Une autre option consisterait à copier ces fichiers dans /WEB-INF et de les référencer avec quelque chose ressemblant à /WEB-INF/applicationContext-persist.xml).

    2

    Le paramètre log4jConfigLocation est utilisé par le Log4JConfigListener pour savoir où chercher le fichier de configuration de logging Log4J. Dans cet exemple, nous demandons à Log4J de regarder dans le fichier /WEB-INF/log4j.properties.

    3

    Cela assure que le système Log4J est configuré lors du démarrage de l'application. Il est important de mettre ce Log4JConfigListener avant le ContextLoaderListener. Dans le cas contraire, vous pourriez manquer d'importants messages indiquant un éventuel problème survenu lors du démarrage d'application. Si vous avez un ensemble particulièrement grand de beans gérés par Spring et que l'un d'entre eux n'arrive pas à s'initialiser au démarrage de l'application, votre application ne se lancera pas. Si vous avez initialisé votre système de log avant le démarrage de Spring, vous aurez la chance de récupérer une alerte ou un message d'erreur. Au contraire, si vous n'avez pas initialisé le système de log avant le démarrage de Spring, préparez vous à naviguer dans le noir pour comprendre pourquoi votre application refuse de démarrer.

    4

    Le ContextLoaderListener est essentiel pour le conteneur Spring. Quand l'application démarre, ce listener construit un ApplicationContext grâce au paramètre contextConfigLocation.

    5

    Nous définissons un DispatcherServlet de Spring MVC nommé weather. Cela forcera Spring à regarder dans le fichier /WEB-INF/weather-servlet.xml. Vous pouvez avoir autant de DispatcherServlet que vous le désirez, une DispatcherServlet peut contenir un ou plusieurs Controller Spring MVC.

    6

    Toutes les requêtes terminant par .x seront routées vers la servlet weather. Notez que l'extension .x n'a pas de signification particulière, c'est un choix arbitraire, il vous est possible de choisir n'importe quel format pour vos URLs.