Skip to content

Spring

Optionnel

Spring ?

  • Vous n'avez jamais entendu parler de spring ? En quelques mots, voici ce qu'il faut retenir :
  • C'est un framework
  • C'est une alternative à JakartaEE (anciennement connu sous le nom de JEE)
  • Il offre DI (dependency injection) et IoC (Inversion of control)
  • Il exploite l'AOP (Aspect Oriented Programming) pour améliorer vos POJO (objets Java ordinaires).
  • Dans cette étape, vous allez ajouter le framework Spring-Boot à une base de code Java existante.

Spring vs Spring Boot

  • Il ne faut pas confondre Spring et Spring Boot.
  • Spring est le framework, celui qui offre DI, IoC, AOP etc... Il est puissant et plein de fonctionnalités et de modules, mais nécessite beaucoup de - configuration pour le faire fonctionner.
  • Spring Boot peut être considéré comme une extension de Spring. Il est construit au-dessus de Spring et fournit des fonctionnalités très pratiques : autoconfiguration, configuration par défaut, serveur embarqué, cohérence de la version des dépendances, starters, etc.

Générer un nouveau projet

Le pom

Allez sur start.spring.io, et download le projet. Le projet a été préconfiguré avec les settings suivants, n'hésitez pas à rajouter des dépendances pour tester si vous en avez besoin:

group:

  • io.takima.agencymanagement

artifact:

  • agencymanagement

dependencies:

  • postgreSQL
  • JDBC
  • Web

Code source

Copier votre code source dans le nouveau projet (Attention il faut que vous mixiez la classe main et vous allez peut-être avoir quelques problèmes d'imports)

Pom Parent

  • Comme vous avez pu le remarquer, <artifactId>postgresql</artifactId> n'a pas de numéro de version spécifié,
    c'est parce que spring-boot-starter-parent fournit et gère toutes les versions des dépendances.
  • Si, pour une raison quelconque, vous souhaitez modifier la version d'une dépendance, vous devez définir la balise dependencyManagement, puis à
    l'intérieur de celle-ci, vous devez spécifier la dépendance pour laquelle vous souhaitez une version spécifique et la version que vous souhaitez.
  • Mais réfléchissez-y à deux fois avant de le faire. Spring-Boot assure (la plupart du temps) la compatibilité entre les versions des dépendances. Il peut être préférable de mettre à jour la version de Spring Boot plutôt que celle d'une dépendance spécifique.
  • Cependant, cela peut permettre de corriger rapidement un bug dans une dépendance sans attendre la nouvelle version de Spring Boot.

HicariCP

  • Si vous regardez attentivement le fichier pom.xml, vous verrez que la dépendance hikariCP n'est pas spécifiée, c'est parce qu'elle est incluse par défaut par spring-boot-starter-jdbc, puisqu'elle est utilisée pour le connection pooling par défaut dans Spring Boot 2

Starter Spring Boot

  • Un spring boot starter est une dépendance regroupant un ou plusieurs modules Spring, et les dépendances externes nécessaires avec une version définie. - Comme son nom l'indique, il est utilisé pour commencer à utiliser rapidement une fonctionnalité.
  • Prenez un moment pour explorer le contenu de spring-boot-starter-parent et de certains spring-boot-starters comme spring-boot-starter-jdbc.
  • Lancez mvn dependency:tree pour afficher toutes les dépendances incluses dans votre installation Spring.

Tips Intellij

  • Notez qu'IntelliJ vous permet de démarrer un nouveau projet avec Spring Initializer directement (en évitant de le générer depuis le site web puis de le télécharger)

La db

Compilez et exécutez votre application :

Failure

=java.lang.IllegalStateException: Failed to load ApplicationContext= Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Si tu ouvres ton pom.xml, tu verras que ton projet vient avec une spring-boot-starter-jdbc dépendance. Celle-ci dit à Spring automatiquement de configurer une connection de db, mais Spring n'arrive pas à le faire car on lui a donné aucune info sur la db.

Une petite recherche sur internet te dirait que Spring (plus spécifiquement Spring Boot) a besoin d'une configuration (soit dans le fichier application.properties, soit dans le fichier application.yml) , pour configurer la connexion à la base de données:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/agencymanagement_db
    username: madmin
    password: madmin
spring.datasource.url=jdbc:postgresql://localhost:5432/agencymanagement_db
spring.datasource.username=madmin
spring.datasource.password=madmin

doc Spring

-Il y a plusieurs façons de définir les propriétés de Spring.
Les définir dans votre fichier application.properties/application.yml est la plus standard, mais il en existe d'autres !
Consultez la documentation pour en savoir plus.

Mettez à jour vos noms de propriétés, exécutez l'application à nouveau, et si votre docker agencymanagement_db est en cours d'exécution, vous devriez obtenir ce qui suit :

Tomcat started on port(s): 8080 (http) with context path ''
Started MaStoreApplication in 3.248 seconds (JVM running for 4.51)

Le logger

Spring boot est livré avec l'API de logging SLF4j (spring-boot-starter-web => spring-boot-starter => spring-boot-starter-logging ). L'implémentation par défaut est Logback (voir : spring-boot-starter-logging-X.X.X.RELEASE.pom:42). Logback est le successeur de log4j. Il a été conçu par le même développeur, avec la performance à l'esprit. De plus, il offre une intégration native avec slf4j et ne nécessite pas de connecteur.

Profitons de cette occasion pour passer de log4j à logback. Si vous le souhaitez, logback dispose d'un traducteur pour migrer votre ancien fichier log4j.properties vers le fichier logback.xml requis.

Cependant, si vous ne voulez pas écrire du XML, Spring Boot vous permet de configurer le logger à travers le fichier properties :

logging:
  level:
    root: info
  pattern:
    console: '%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n'
logging.level.root=info
logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n

Info

  • Spring peut configurer logback pour utiliser les couleurs avec cette option:spring.output.ansi.enabled=always
  • N'oubliez pas de supprimer l'ancien fichier log4j.properties, car il n'est plus utile
  • Ecrivez le application.properties comme ça application.yml, le yaml est plus lisible

L'injection de dépendances

Que le fun commence !

L'ensemble du framework Spring est construit autour du concept d'injection de dépendances (Dependency Injection).
Dans cette étape, vous découvrirez l'injection de dépendance en configurant la manière d'injecter le DataSource.

DataSource est un composant JPA qui contient les connexions à la base de données.

Lorsque vous avez introduit le pool de connexion DB dans l'étape Java précédente, vous avez probablement créé une sorte de singleton ConnectionManager pour configurer HikariCP et fournir un objet de connexion. En fait, HikariCP vous donne accès à la connexion par l'intermédiaire d'un HikariDataSource :

DataSourceManager() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl(url);
    // ...
    this.datasource = new HikariDataSource(config);
}
Connection getConnection() {
    return this.datasource.getConnection();
}
Tous vos DAO ont besoin de cette source de données et, plutôt que de coder en dur le lien entre chaque DAO et la source de données, injectons-le !

Info

L'instanciation d'objets directement dans la classe qui les requiert n'est pas flexible, car elle engage la classe dans un ensemble particulier d'objets et rend impossible la modification ultérieure de leur instanciation, indépendamment de la classe.
Cela empêche la classe d'être réutilisable si d'autres objets sont nécessaires, et rend difficile le test de la classe, car les objets réels ne peuvent pas être remplacés par des objets fictifs. C'est là que l'injection de dépendances devient pratique.

Créez un nouveau fichier ApplicationContext.java, qui utilise les annotations suivantes :

@Configuration
@Bean
@Primary

Le fichier ApplicationContext.java est utilisé par Spring pour définir le contexte.
Considérez le contexte comme une classe annotée avec @Configuration, que Spring utilise au démarrage de l'application, pour instancier vos services,
vos singletons, tout configurer et tout relier.

Info

Il peut y avoir autant de classes @Configuration que vous le souhaitez. Initialement, Spring s'appuyait sur des fichiers XML pour configurer le contexte (i.e. : context.xml). Depuis Spring 4.X, nous préférons utiliser la configuration basée sur JavaConfig, qui s'appuie largement sur des annotations (comme @Bean ou @Configuration) plutôt que sur l'approche XML. Consultez cette excellent article on Baeldung si vous voulez en savoir plus!

Lorsque le fichier ci-dessus est lu par Spring, le DataSource est alors considéré comme un bean et peut être @Autowired à vos DAOs, leur donnant accès à une Connection. Si vous avez besoin de changer votre source de données, vous n'avez qu'à changer votre bean, aucune modification de DAO n'est nécessaire. N'est-ce pas génial ?

Allez y créer votre class ApplicationContext avec un bean datasource !

Tip
    @Configuration
    public class ApplicationContext {

    @Bean
    @Primary
    DataSource datasource() {
        return DataSourceManager.INSTANCE.getDatasource();
    }
}

Spring Autowire

Consultez cet article de Baeldung sur spring autowire si vous voulez en savoir plus.

Comme nous l'avons vu précédemment, le contexte consiste essentiellement à fournir des beans de certains types à l'ensemble de l'application.

Au cours de cette étape, vous allez transformer tous vos singletons en composants gérés par Spring et Autowired les uns avec les autres.

Info

Une classe annotée avec @Component se transformera en Bean une fois instanciée et pourra alors être injectée.

  • Dans votre DAO, injectez et utilisez la source de données que nous avons configurée plus tôt:
class SomeDao {  
    @Autowired. 
    DataSource ds;  // Spring will inject a DataSource instance HERE. 
}  

L'annotation @Autowired indique à Spring d'injecter le bean DatasSource que nous avons configuré plus tôt.

Info

  • Il est recommandé d'utiliser @Autowired sur les constructeurs plutôt que sur les champs, car ils vous permettent d'implémenter des composants d'application comme des objets immuables et de vous assurer que les dépendances requises ne sont pas nulles.
  • Les injections de setter ou de champ ne doivent être utilisées que pour les dépendances optionnelles.
  • Vous pouvez en savoir plus à ce sujet ici
  • Le saviez-vous ? Depuis Spring 4.3, les classes annotées @Component qui n'ont qu'un seul constructeur ne doivent pas nécessairement définir l'annotation @Autowired pour bénéficier de la DI.
  • Ajoutez l'annotation @Repository à vos classes DAO. Cela les transforme en Composants gérés par Spring.

Info

  • Les deux @Component et @Repository marquent un bean comme un composant géré par Spring, mais @Repository attrape aussi les exceptions spécifiques à la persistance et les relance comme l'une des exceptions unifiées non contrôlées de Spring.

  • Si une classe n'est pas un composant, elle n'est probablement pas gérée par Spring. N'utilisez jamais @Autowired à l'intérieur d'une telle classe, car rien ne sera injecté.

  • Annotez vos classes de service avec l'annotation @Service.

Info

Tous vos composants doivent avoir un constructeur public et trivial.
Par trivial, nous entendons un constructeur sans argument ou avec des arguments qui sont autowired.

  • Modifiez vos services pour autowire les composants requis.

  • Vous n'aurez pas besoin de autowire quoi que ce soit dans votre main dans le futur, parce que votre application gérera les requêtes web.
    Cependant, pour le moment, vous avez toujours besoin d'exécuter votre application CLI, et pour ce faire, vous pouvez écrire un constructeur dans votre main et l'injecter avec vos dépendances.

@SpringBootApplication  
public class MaStoreApplication { 
    private final TravelService travelService; 
    @Autowired
    public MaStoreApplication(TravelService travelService) {  
        this.travelService = travelService;  
        // Put your old main code here
}
    public static void main(String[] args) { 
        SpringApplication.run(MaStoreApplication.class, args);  
    }
} 

Info

Spring instanciera la classe @SpringBootApplication en appelant le constructeur. Vous pouvez également externaliser votre code dans une autre classe et l'annoter avec @Service pour réduire la quantité de code dans le constructeur.

  • Compilez et testez votre code ; tout devrait bien se passer !

Comme vous le voyez, il n'y a rien comme new TravelDao() dans votre code. C'est à Spring d'appeler les constructeurs du composant et d'instancier l'objet.
C'est ce que signifie l'inversion de contrôle (IoC).
Vous laissez le Framework contrôler l'instanciation des beans au lieu de le faire vous-même.

Info

Avant d'aller plus loin, vous pouvez remarquer que dans votre TravelService, vous avez réussi à injecter votre TravelDao même si celui-ci n'est pas explicitement fournis par la classe ApplicationContext.java.
Vous vous souvenez de l'annotation @ComponentScan dans ApplicationContext ?
Cette annotation, comme son nom l'indique, active l'analyse des composants 😄.
Cela signifie que Spring est capable de découvrir automatiquement toutes vos classes annotées @Component, à condition que @ComponentScan ait une configuration correcte.
Cela signifie que votre contexte Spring peut être alimenté de deux manières principales : en utilisant une classe de configuration Java (comme ApplicationContext.java) ou en recherchant des composants dans le classpath via @ComponentScan.
Le @SpringBootApplication au dessus de votre classe Main ajoute automatiquement un @ComponentScan.

Spring Web

Comme vous le savez déjà, lorsque Spring Boot démarre, il crée son propre Tomcat pour gérer les requêtes web (une des nombreuses fonctionnalités de Spring Boot).

Comparons Tomcat à un serveur HTTP :

Apache2 est un serveur HTTP.

Habituellement, nous donnons à Apache2 quelques fichiers HTML et CSS statiques.
Il fonctionne en arrière-plan, écoutant le réseau sur les ports 80 et 443 pour les requêtes HTTP entrantes.
Lorsque le client (le navigateur) demande une certaine URL, il envoie le fichier correspondant (le cas échéant) en réponse.
Tomcat est un conteneur de servlets.

Habituellement, nous donnons à Tomcat des servlets. Une servlet est un code Java qui s'exécute lorsqu'une certaine URL est atteinte.
Une servlet produit des données qui seront envoyées au client.

Elle s'exécute en arrière-plan, en écoutant le réseau sur le port 8080 par défaut pour les requêtes HTTP entrantes.
Lorsque le client (le navigateur) demande une certaine URL, il exécute la servlet correspondante (s'il y en a une) et renvoie la réponse de la servlet au client.

L'utilisation d'un servlet pour produire du HTML est tout à fait dépassée. En pratique :

Si vous devez produire du HTML :
- utilisez des langages de template côté serveur (JSF,
JSP,
ou thymeleaf)

  • Le HTML ne peut être consommé que par un navigateur. Si vous avez besoin d'un service web, produisez plutôt du JSON (ou du XML).

Dans cette étape, nous nous appuyons sur Spring Web MVC pour développer une API qui consomme du JSON.

Pour résumer, la DispatcherServlet est une Servlet qui écoute toutes les urls. Donc oui, Spring n'utilise qu'une seule servlet par défaut pour gérer les requêtes.
- Une requête arrive. Par exemple : GET /api-url?param=value
- Le DispatcherServlet regarde toutes les classes avec l'annotation @Controller.
- Si une classe @Controller a une méthode liée à GET /api-url, il appelle cette méthode.
- Si la méthode définit un mapping pour le paramètre param (grâce à @RequestParam), elle appelle la méthode avec le paramètre rempli avec la valeur value.
- Si la méthode produit quelque chose, elle sérialise la sortie (c'est-à-dire : en JSON ou XML) et l'envoie au client.

Créer un travelController dans le package controller et faites la méthode List findAll();

Info

La sérialisation en JSON est effectuée par la bibliothèque Jackson, qui est incluse dans la dépendance spring-boot-starter-web.
C'est la partie résolveur de vue de Spring qui s'en charge. Oui, ici, la "vue" est un Json. Cela aurait pu être un HTML si JSP, Thymeleaf etc. avait été
utilisé à la place de Jackson.

Ajoute les annotations suivantes sur ton controller: @Controller
@RequestMapping("/api")

et sur ta méthode findAll:
@ResponseBody
@GetMapping(value = "/travels", produces = "application/json")

Relance ton application et va sur localhost:8080/api/travels et voyez que votre contrôleur sert du contenu JSON.