Spring web MVC : Doit-on déclarer un ContextLoaderListener?

Spring_Framework

L'objectif de ce billet est tout simplement de répondre à la question indiquée dans son titre:

Doit-on déclarer, dans une application web, un ContextLoaderListener?

Ou encore, n'est-il pas suffisant de configurer uniquement la "ServletDispatcher" dans web.xml?

Pour répondre rapidement à ces deux questions je dirais:

Pour la première question: Non, mais...!
Et pour la seconde question: Oui, mais...!

Insistons d'abord sur ce point: Les applications java standard (non web) utilisent forcément le contexte  "ApplicationContext" pour initialiser les conteneur Spring. Ceci se fait, par exemple, ainsi:


ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");

Or, ce qui nous importe ici c'est plutôt:

Dans une application web Spring mvc, a-t-on besoin de déclarer deux contextes:

Un "ApplicationContext" via le listener et un second "WebApplicationContext" associé à la servlet "DispacherServlet" de spring mvc?

Commençons par rappeler que, dans le cas d'application web, la création d'une instance du contexte "ApplicationContext" se fait selon deux modes (déclaratif ou programmatique):

  • En enregistrant un listener "ContextLoaderListener" dans le web.xml,
  • Par code java.

Voici en mode déclaratif, un exemple verbeux d'enregistrement d'un listener dans web.xml.


<listener>
<listener-class>o.s.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>

Quand on enregistre un listener dans le web.xml, Spring inspecte le paramètre "contextConfigLocation" pour identifier le(s) fichier(s) de configuration des beans à charger au démarrage de l'application web J2EE. Si ce paramètre est absent, par convention, Spring inspecte le fichier "/WEB-INF/applicationContext.xml" et charge les beans déclarés dedans.

Là, on n'a pas encore parlé du contexte WebApplicationContext. Si!

Le contexte initié par  le listener est un contexte "WebApplicationContext" qui représente le parent ou le context root de tous les autres contextes. Lesquels? Nous y reviendrons.

Rappelons aussi qu'il existe un lien d'héritage entre les interfaces "ApplicationContext" et "WebApplicationContext" comme illustré ci-dessous:

heritage_contexte_spring

Important Une application Spring Web MVC ne peut exister sans un contexte "WebApplicationContext".

Dans une application spring web mvc, le contexte "WebAplicationContext" est associé à la "DispatcherServlet"déclarée dans web.xml.

Cette servlet peut définir explicitement le(s) fichier(s) Spring de configuration via init-param ou encore implicitement selon le principe "convention over configuration".
Dans ce dernier cas, un fichier de configuration doit-être fourni dans le répertoire /WEB-INF/ et nommé par convention ainsi [name of dispatcher Servlet]-servlet.xml .

Ci-dessous un exemple complet de déclarations de deux Servlets.

<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>o.s.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!-- un webAppCtx est associé à la servlet "demo2"-->
<!-- Les fichiers de config sont définis explicitement via init-param -->
<servlet>
<servlet-name>demo2</servlet-name>
<servlet-class>o.s.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/another-ctx.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>demo2</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

Notez bien que le paramètre contextConfigLocation est défini dans le tag <init-param> à l'intérieur du tag <servlet>. C'est légèrement différent de celui du listener vu précédemment.

Il est peut-être temps de tirer une première conclusion (intermédiaire).
Si notre application web spring mvc embarque tous les beans nécessaires à son exécution alors auquel cas nul besoin de configurer un ContextLoaderListener.

Mais si d'autres beans doivent être importés depuis une autre location en dehors de la "DispatcherServlet" alors là il faudrait déclarer un ContextLoaderListener.

Une bonne pratique consiste à déclarer les beans de middle-tiers (services, datasources,...) dans le "ApplicationContext" via le ContextLoaderListener et de déclarer les controllers et les beans spécifiques au web dans (le webContext associé au) "DispatcherServlet".

On peut relever dans les logs de Spring MVC au moment du démarrage de l'application ces traces qui confirment la création d'un contexte root suivi de celui de la dispatcherServlet:


INFO: Initializing Spring root WebApplicationContext
DEBUG o.s.b.f.xml.XmlBeanDefinitionReader - Loaded 10 bean definitions from location pattern [classpath*:spring-context.xml]

Puis ceci:


INFO: Initializing Spring FrameworkServlet 'demo1'

DEBUG o.s.b.f.xml.XmlBeanDefinitionReader - Loaded 6 bean definitions from location pattern [/WEB-INF/demo1-servlet.xml]

....

Vous constatez que le context root est initialisé en premier puis le webApplicationContext (qui hérite du premier) associé à la DispatchServlet "demo1". De même pour la servlet "demo2".

Pour chacune des servlets, les logs affichent les initialisations des "WebApplicationContext" associés à ces différentes servlets.

Avec tout cela, on peut déduire la réponse à la question posée.

Résumons:

Dans spring MVC, il peut y avoir un, deux voire plusieurs contextes:

Un contexte global (root) partagé qui est démarré et configuré par ContextLoaderListener dans le web.xml. Et un contexte WebApplicationContext associé à chaucune des DispatcherServlet.

Le "WebApplicationContext" associé au Dispatcher contient les déclarations des beans Spring liées au web.

Cependant, le "ApplicationContext", généré via le ContextLoaderListener, doit contenir les beans Spring des autres couches : services et d'autres middle-tiers (Ex. les objets des couches dao, service, datasources,..).

Enfin, terminons avec cette autre question:

Peut-on déclarer plusieurs Servlet (DispatcherServlet) dans web.xml?

Réponse:

Vous vous en doutez bien au vu de l'exemple précédent.
Et lorsque plusieurs servlet sont déclarées le contexte root "ApplicationContext" est partagé par les différents autres "WebApplicationContext" associés à chacune des servlet déclarées.

En espérant que ce billet sera utile dans la jungle des différentes configurations de Spring.

A vous de le dire?

3 commentaires

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.