Java EE / Spring

Master e-services 2016

View the Project on GitHub gdufrene/java_ee_spring-14

Spring.

« The Spring Framework provides a comprehensive programming and configuration model
for modern Java-based enterprise applications »

Comprenez que Spring est un "couteau suisse" pour faciliter la vie du développeur JAVA, spécialement si il développe pour JAVA EE (mais pas que).

Le coeur de spring est un moteur d'injection de dépendance permettant l'inversion de contrôle du cycle de vie des objets. L'IoC permet, entre autre, de "repousser" le choix d'implémentation (ou d'instance) d'un objet inclus dans un autre.

Prenons l'exemple d'un DAO JPA ou JDBC. Imaginons que le développeur de notre interface ne soit pas le même que celui qui réalise la couche de persistance (donc du DAO). Le développeur de l'interface ne connait pas la classe qui sera utilisée, et d'ailleurs il n'a pas envie de faire un new xxxJPADao(), ce qui "casserait" en parti l'abstraction que nous avons essayé de mettre en place. De l'autre coté, le developpeur de notre DAO ne sait pas où et comment son implémentation JPA sera utilisée. Il ne peut pas faire view.setDao( ... )

Heureusement il y a Spring. Dans l'interface on demandera à l'injecteur de dépendance de trouver et d'affecter le DAO, de l'autre on déclarera l'implémentation JPA comme composant DAO à être utilisé. Le tout sera initialisé et affecté au lancement de l'application.

Merci Martin, que ferions-nous sans toi.
pour votre culture générale : Inversion of Control Containers and the Dependency Injection pattern

L'injection de dépendances

Le principe est de construire des objets, que l'on nommera "composant" et de les mettre à disposition des autres composants.
Un composant est une instance partagée du contexte spring, selon le pattern "singleton" par défaut.
Une annotation @Component permet de définir une classe java comme un composant spring. Dans ces objets il sera possible d'utiliser l'annotation @Autowired pour injecter automatiquement une instance présente dans le contexte et possédant un type compatible.
Si une injection ne peut pas être satisfaite, alors une erreur sera levée au démarrage du contexte spring.
Certaines injections pouvant être facultatives, vous pouvez mettre le paramètre "required=false" dans l'annotation Autowired.

Il existe différentes implémentation de contexte spring. Nous utiliserons généralement AnnotationConfigApplicationContext comme instance d'ApplicationContext. Cette implémentation permet de configurer un contexte spring à l'aide des annotations que nous avons placés.
Spring peut rechercher après tous les composants (ceux annotés) dans une partie du classpath, cela se configure à l'aide de l'annotation @ComponentScan. Spring inspectera la liste des packages passés en paramètre pour constituer son contexte, à l'aide des composants qui y sont définis.

La lecture de la documentation de référence concernant les beans et surtout ce paragraphe vous seront sans doute d'une aide précieuse.

L'intérêt de spring est surtout de réaliser des scénarii d'injection lorsque les objets proviennent de librairies différentes, ou lorsque plusieurs implémentations d'une même classe peuvent être utilisées dans l'application.
L'inspection des composants du classpath peut se faire dans vos packages ou ceux définis dans une autre librairie (jar).

Cela peut vite devenir complexe de comprendre ce qui se trouve ou non dans le contexte spring. Il faudra veiller à bien structurer ses packages et la définition de ses composants pour s'y retrouver. Il peut être intéressant d'utiliser un contexte décrit en XML, ou de regrouper la création des objets du contexte dans un objet portant l'annotation @Configuration.

Si généralement nous préférons mettre des annotations pour configurer un contexte spring, il est effectivement possible d'utiliser des fichiers de configuration en xml. Historiquement, c'est la manière dont on configurait un contexte spring. Les deux approches (annotations / xml) ont leurs avantages et inconvénients.

Selon l'utilisation de votre librairie ou de votre application, il peut être intéressant de prévoir une configuration par annotation ou par fichier XML. Les deux approches peuvent aussi se mixer.

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)

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 vigilant sur les urls exposées par les controleurs et celles interceptées par spring. En effet, si vous définissez le "RequestDispatcher" sur "*.html" et qu'un controleur 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". Celà 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és @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 permettra 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 chaine 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 contôleurs en JSON.

DAO

La partie "Model" de spring web-mvc n'est pas spécifique et vous pouvez l'implémenter de la manière que vous le souhaitez. Une bonne pratique reste évidemment de dissocier les DAO et de les injecter dans vos contrôleurs à l'aide de "@Component" et "@Autowire".

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 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