Pages

mardi 17 juin 2014

Une webapp Spring sans fichier web.xml (Servlet 3.0)

Je travaille pas mal en ce moment sur des migrations techniques et la simplification des configurations des projets. Un de mes exercices favoris est de supprimer la configuration faite avec des fichiers en XML, pour passer à de la configuration Java.

Dans mon article précédent j'ai parlé des généralités dans Spring pour oublier le XML et passer à la Java Config. Je continue ici dans la même lignée et je vais parler de la suppression du bon vieux fichier web.xml qui permet de décrire comment une application web en Java doit être déployée dans un serveur d'application.

Cette suppression est possible à partir de la version 3 de la spécification Servlet (Java EE6) et pour l'utiliser vous devez avoir un serveur d'application (ou de servlet) compatible. Par exemple si vous utilisez Tomcat vous devez disposer d'une version 7 minimum. Je ne m'attarderai pas sur les autres nouveautés de la spécification mais il est intéressant de noter que vous pouvez maintenant utiliser des web fragments mais aussi des appels de servlets asynchrones.

Comment tout ça fonctionne ?
Lorsque le jar spring-web est chargé par le conteneur de servlet compatible Servlet 3.0, la classe SpringServletContainerInitializer, implémentant l'interface ServletContainerInitializer  est chargée, instanciée et la méthode onStartup est appelée.

La classe SpringServletContainerInitializer appelle ensuite tous les beans de type WebApplicationInitializer et invoque leur méthode

void onStartup(ServletContext servletContext) throws ServletException;

Pour pouvoir se passer du web.xml nous allons donc devoir écrire une ou plusieurs classes implémentant l'interface WebApplicationInitializer.

Définir le mode de chargement de votre application
Le framework Spring propose des classes qui permettent de simplifier le démarrage d'une application web. Je vais prendre en exemple une petite application web disponible sur github. Dans cette dernière j'ai créé une classe WebAppInitializer qui hérite de AbstractAnnotationConfigDispatcherServletInitializer de Spring.

public class WebAppInitializer extends   AbstractAnnotationConfigDispatcherServletInitializer {

      @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { PersistenceConfig.class, ServiceConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebMvcConfiguration.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{new SimpleCORSFilter()};
    }

}

Vous me direz c'est super simple... Je définis simplement ma configuration générale, la config de mes servlets, le mapping, les filters... Dans le cas d'une application simple vous n'aurez pas de problème mais dès que vous commencerez à avoir des particularités vous allez avoir plus de mal.

Se passer des abstractions de Spring 
Je vous conseillerai donc d'utiliser directement votre propre implémentation de l'interface WebApplicationInitializer. Pour moi il y a plusieurs avantages
  • Même si vous avez un peu plus de code à écrire, ce n'est pas compliqué ,
  • Vous utilisez directement l'API décrite par la spécification Servlet,
  • La manière d'écrire le code ressemble fortement à ce que l'on décrit dans le fichier web.xml : des listeners, des servlets, des filters...
L'exemple précédent devient

public class WebAppInitializer implements WebApplicationInitializer {

    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { PersistenceConfig.class, ServiceConfig.class };
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic registration = null;

        //Chargement du contexte Spring
        AnnotationConfigWebApplicationContext rootContext = 
                  new AnnotationConfigWebApplicationContext();
        rootContext.register(getRootConfigClasses());
        servletContext.addListener(new ContextLoaderListener(rootContext));

        //Definition d'un dispatcher de servlet
        AnnotationConfigWebApplicationContext javamindContext = 
                 new AnnotationConfigWebApplicationContext();
        javamindContext.register(WebMvcConfiguration.class);
        registration = servletContext
            .addServlet("javamind", new DispatcherServlet(javamindContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");

        //Nous avons besoin de déclarer un Filter pour les requetes CORS
        FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("corsFilter", new SimpleCORSFilter());
        filterRegistration.addMappingForServletNames(
                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE),
                true,
                "javamind"
        );
    }
}

Que se passe t'il si vous combinez le fichier web.xml et la déclaration en Java ?
Eh bien c'est très simple, le fichier web.xml prend la main et la configuration n'est pas prise en compte. Notez que si vous optez pour la description en XML vous devrez utiliser les bons namespaces

<web-app version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

Pour les utilisateurs de Maven
Si vous utilisez Maven pour générer le war de votre application, vous risquez de tomber sur l'erreur

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.4:war (default-war) on project conference: Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode) -> [Help 1]

Par défaut Maven vérifie la présence du fichier web.xml pour construire le war. Pour lui indiquer que ce n'est pas un problème, vous devez surcharger le maven-war-plugin et utiliser la propriété failOnMissingWebXml
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <warName>devoxx2014</warName>
                </configuration>
            </plugin>

Voila vous n'avez plus qu'à essayer...

Aucun commentaire:

Enregistrer un commentaire

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.