Master e-services 2016
« 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
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).
@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.
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.
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.
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 !
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.
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.
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".
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