Spring MVC

La librairie MVC de Spring permet d'utiliser les mécanismes d'injection de dépendance pour faciliter le branchement des différentes parties (model, vue, controller) de notre application web tout en assurant une certaine indépendance entre nos objets.


(source : documentation Spring MVC)

La vidéo suivante présente le module Spring MVC.

Transparents présentés dans la vidéo

Initialisation

Spring MVC fournit un objet "RequestDispatcher" qui est une servlet qui va aiguiller toutes les requêtes vers l'un des contrôleurs annotés et présents dans le contexte Spring.
Pour brancher cette servlet dans Tomcat (ou autre), il faudra définir le pattern des URLs qui seront aiguillées vers la "RequestDispatcher" de Spring. On pourra par exemple intercepter toutes les requêtes, dans notre contexte, qui commencent par "/app/", ou qui se terminent pas ".do".
Vous devez être vigilants sur les URL exposées par les contrôleurs et celles interceptées par Spring. En effet, si vous définissez le "RequestDispatcher" sur "*.html" et qu'un contrôleur est exposé sur "/hello" (sans extension), le traitement n'arrivera jamais jusqu'à lui.

Pour configurer le mapping de la "RequestDispatcher", cela peut se faire avec le fichier "web.xml" contenu normalement dans "WEB-INF" et définissant tous les mapping de servlet (avant les annotations). Récemment, l'API servlet 3.0 a permis de configurer le contexte web par l'extension d'une interface. Spring utilise ce mécanisme, et cherche dans le classpath une implémentation de WebApplicationInitializer pour vous permettre de préparer le contexte Spring et brancher le "RequestDispatcher". Cela ressemblera à :


public class WebAppInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext container) throws ServletException {
    AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
    dispatcherContext.register(AppConfig.class);
    container.addListener( new ContextLoaderListener(dispatcherContext) );
    // Register and map the dispatcher servlet
    ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", 
      new DispatcherServlet(dispatcherContext));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/app/*");
  }
}

"AppConfig" sera une classe sur laquelle on mettra les annotations:
@EnableWebMvc pour activer les mécanisme de web-mvc ;
@Configuration si cette classe fournit des Beans à ajouter au contexte ;
@ComponentScan pour spécifier les packages dans lesquels chercher les composants.

Les contrôleurs

Spring cherchera à enregistrer dans le RequestDispatcher tous les composants annotés @Controller ou @RestController.
Toutes les méthodes de ces contrôleurs annotées @RequestMapping seront exposées, en fonction des paramètres de cette annotation (ex: uniquement sur GET ou POST).
Spring essaiera d'injecter automatiquement tous les types en paramètre de votre méthode.
Habituellement on injectera une instance de "Model" sur lequel on placera des attributs à passer dans la vue.
Si votre méthode retourne une chaîne de caractères, Spring essaiera d'afficher la vue correspondante en y injectant les attributs du modèle.

Des annotations complémentaires vont également nous permettre de manipuler facilement les données provenant de la requête. Par exemple l'annotation @RequestParam permet de copier automatiquement le contenu d'un paramètre dans une variable.
Soit l'équivalent de String param = request.getParameter("paramName").
Exemple d'une méthode de contrôleur utilisant les annotation spring web-mvc :


  	@RequestMapping(value="/authors.html", method=RequestMethod.GET)
  	public String listByName( Model model, @RequestParam("nameLike") String name ) {
  		List<Author> authors = dao.findAuthorsLikeName( name );
  		model.addAttribute("authors", authors);
  		return "authors";
  	}

Si vous avez besoin d'accéder à l'objet HttpServletRequest (ou Response) il vous suffit de l'ajouter à la liste des paramètres de votre opération et Spring vous le passera lors de l'invocation de la méthode.
Il n'est pas recommandable de créer ce type de dépendance vers l'API servlet car votre contrôleur pourrait être utilisé dans un tout autre contexte que celui d'une application web. Toutefois, il est parfois nécessaire de le faire et Spring vous en offre la possibilité.
Consulter cette page pour voir la liste des types de paramètres que Spring peut vous fournir !

Les vues

La manière la plus simple d'associer une vue à un contrôleur est de lui faire retourner une chaîne de caractères indiquant le fichier que la DispatcherServlet devra afficher.

L'association entre le fichier de vue et la chaîne de caractères retournée par vos contrôleurs se fait à l'aide d'un composant "ViewResolver". Pour en changer le comportement, créez une méthode annotée "Bean" dans l'objet de configuration (celui annoté @Configuration). Cette méthode retournera un "InternalResourceViewResolver". Cela permet d'aller charger les vues à un endroit quelconque du classpath ou du disque ...
Voir la documentation.

Pour des vues écrites en JSP, tous les attributs du modèle sont accessibles par request.getAttribute("attrName") et par ${attrName}. Cette dernière notation utilise les "Expression Langage". N'hésitez pas à consulter ce tutorial pour découvrir les mécanismes de base des EL.

Ecrire des webservices REST/JSON

Vous serez sans doute régulièrement amené à développer des services REST renvoyant du JSON.
Avec l'annotation @ResponseBody les méthodes de @Controller ne chercheront plus à exécuter une vue et retourneront directement le résultat de votre opération, en la convertissant éventuellement.
C'est le comportement par défaut des @RestController.
Vous pourriez donc renvoyer une chaîne de caractères composée de JSON écrite "à la main".
Mais en ajoutant simplement la librairie "jackson-databind" à vos dépendances Maven, vous pouvez faire transformer automatiquement les objets retournés par vos contrôleurs en JSON.

Le screencast suivant montre la configuration d'une application web pour spring webmvc et l'utilisation d'un contrôleur avec une vue JSP.

Code présenté dans le screencast

DAO

Pour alimenter les objets du "Model" de Spring MVC vous pouvez utiliser ce que vous voulez. Une bonne pratique reste évidemment de dissocier des DAO et de les injecter dans vos contrôleur à l'aide de @Component et @Autowire.

Le screencast suivant présente l'usage de DAO implémentés en JPA et configurés à l'aide du module Spring ORM.

Code présenté dans le screencast

Lancer Tomcat avec les dépendances Maven

Il existe de nombreux plugins dans les IDE pour gérer le lancement et le rafraichissement des contextes de Tomcat. Toutefois, leur comportement est parfois hasardeux et vous pourriez avoir besoin de déployer "à la main" votre projet pour vous assurer qu'il fonctionnera dans un serveur d'application web Java standard.
Vous pouvez utiliser mvn package pour préparer un fichier WAR à déployer. Il sera disponible dans /target.

Notez que vous pouvez aussi exporter l'ensemble des librairies dont dépend votre projet dans un répertoire. Cela peut être pratique si vous souhaitez packager vous même l'application par exemple.

mvn dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=WEB-INF/lib

Exercices

Nous vous proposons différents exercices qui permettront de vous familiariser par la pratique avec Spring MVC.