Construire une application MVC avec Spring Framework : Tutoriel pour débutant

Publié: 2022-03-11

On dit souvent de Java qu'il est trop compliqué et qu'il prend trop de temps pour créer des applications simples. Néanmoins, Java fournit une plate-forme stable avec un écosystème très mature autour d'elle, ce qui en fait une excellente option pour développer des logiciels robustes.

Spring Framework, l'un des nombreux frameworks puissants de l'écosystème Java, est livré avec une collection de modèles de programmation et de configuration dans le but de simplifier le développement d'applications performantes et testables en Java.

Cadre de printemps

Dans ce didacticiel, nous relèverons le défi de créer une application simple qui agira comme une base de données de développeurs de logiciels utilisant Spring Framework et l'API Java Persistence (JPA).

L'application suit une architecture MVC standard. Il aura un contrôleur (classe ContractsController), des vues (basées sur des modèles Thymeleaf) et un modèle (un objet de carte Java). Par souci de simplicité, nous utiliserons une base de données en mémoire derrière JPA pour conserver les données pendant que l'application est en cours d'exécution.

Premiers pas avec le didacticiel Spring Framework

Pour créer une application basée sur Spring, nous devrons utiliser l'un des outils de génération suivants :

  • Maven
  • Gradle

Dans ce tutoriel, nous utiliserons Maven. Si vous n'êtes pas familier avec l'un de ces outils, un moyen simple de commencer est de télécharger Spring Tool Suite. La suite est dédiée à Spring Framework et est livrée avec son propre IDE basé sur Eclipse.

Dans Spring Tool Suite, nous créons un nouveau projet en sélectionnant "Spring Starter Project" dans le menu "Fichier> Nouveau".

Tutoriel Spring Framework

Une fois qu'un nouveau projet a été créé, nous devrons éditer le fichier de configuration Maven, " pom.xml ", et ajouter les dépendances suivantes :

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-commons</artifactId> </dependency>

Ces dépendances répertoriées chargeront Spring Boot Web, Thymeleaf, JPA et H2 (qui serviront de base de données en mémoire). Toutes les bibliothèques nécessaires seront extraites automatiquement.

Classes d'entité

Pour pouvoir stocker des informations sur les développeurs et leurs compétences, nous allons devoir définir deux classes d'entités : « Développeur » et « Compétence ».

Ces deux éléments sont définis comme des classes Java simples avec quelques annotations. En ajoutant "@Entity" avant les classes, nous mettons leurs instances à la disposition de JPA. Cela facilitera le stockage et la récupération des instances du magasin de données persistant en cas de besoin. De plus, les annotations « @Id » et « @GeneratedValue » nous permettent d'indiquer le champ d'identification unique de l'entité et de générer automatiquement sa valeur lorsqu'elle est stockée dans la base de données.

Comme un développeur peut avoir de nombreuses compétences, nous pouvons définir une simple relation plusieurs-à-plusieurs en utilisant l'annotation "@ManyToMany".

Développeur

 @Entity public class Developer { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String firstName; private String lastName; private String email; @ManyToMany private List<Skill> skills; public Developer() { super(); } public Developer(String firstName, String lastName, String email, List<Skill> skills) { super(); this.firstName = firstName; this.lastName = lastName; this.email = email; this.skills = skills; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public List<Skill> getSkills() { return skills; } public void setSkills(List<Skill> skills) { this.skills = skills; } public boolean hasSkill(Skill skill) { for (Skill containedSkill: getSkills()) { if (containedSkill.getId() == skill.getId()) { return true; } } return false; } }

Compétence

 @Entity public class Skill { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String label; private String description; public Skill() { super(); } public Skill(String label, String description) { super(); this.label = label; this.description = description; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }

Référentiels

Avec JPA, nous pouvons définir une interface DeveloperRepository et une interface SkillRepository très utiles, qui permettent des opérations CRUD faciles. Ces interfaces nous permettront d'accéder aux développeurs et aux compétences stockées via de simples appels de méthode, tels que :

  • "respository.findAll()": renvoie tous les développeurs
  • « repository.findOne(id) » : renvoie le développeur avec l'ID donné

Pour créer ces interfaces, il suffit d'étendre l'interface CrudRepository.

Référentiel des développeurs

 public interface DeveloperRepository extends CrudRepository<Developer, Long> { }

Référentiel de compétences

 public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }

La fonctionnalité de la méthode supplémentaire « findByLabel » déclarée ici sera fournie automatiquement par JPA.

Manette

Ensuite, nous pouvons travailler sur le contrôleur pour cette application. Le contrôleur mappera les URI de demande pour afficher les modèles et effectuera tout le traitement nécessaire entre les deux.

 @Controller public class DevelopersController { @Autowired DeveloperRepository repository; @Autowired SkillRepository skillRepository; @RequestMapping("/developer/{id}") public String developer(@PathVariable Long id, Model model) { model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "developer"; } @RequestMapping(value="/developers",method=RequestMethod.GET) public String developersList(Model model) { model.addAttribute("developers", repository.findAll()); return "developers"; } @RequestMapping(value="/developers",method=RequestMethod.POST) public String developersAdd(@RequestParam String email, @RequestParam String firstName, @RequestParam String lastName, Model model) { Developer newDeveloper = new Developer(); newDeveloper.setEmail(email); newDeveloper.setFirstName(firstName); newDeveloper.setLastName(lastName); repository.save(newDeveloper); model.addAttribute("developer", newDeveloper); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + newDeveloper.getId(); } @RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST) public String developersAddSkill(@PathVariable Long id, @RequestParam Long skillId, Model model) { Skill skill = skillRepository.findOne(skillId); Developer developer = repository.findOne(id); if (developer != null) { if (!developer.hasSkill(skill)) { developer.getSkills().add(skill); } repository.save(developer); model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + developer.getId(); } model.addAttribute("developers", repository.findAll()); return "redirect:/developers"; } }

Le mappage des URI aux méthodes se fait via de simples annotations "@RequestMapping". Dans ce cas, chaque méthode du contrôleur est mappée à un URI.

Le paramètre de modèle de ces méthodes permet de transmettre des données à la vue. Essentiellement, ce sont de simples cartes de clés de valeurs.

Chaque méthode de contrôleur renvoie soit le nom du modèle Thymeleaf à utiliser comme vue, soit une URL dans un modèle spécifique ("redirect : ”) vers lequel rediriger. Par exemple, les méthodes "developer" et "_developersList_" renvoient le nom d'un modèle, tandis que "developersAdd" et "developersAddSkill" renvoient les URL vers lesquelles rediriger.

Dans le contrôleur, les annotations "@Autowired" attribuent automatiquement une instance valide de notre référentiel défini dans le champ correspondant. Cela permet d'accéder aux données pertinentes depuis le contrôleur sans avoir à gérer un grand nombre de codes passe-partout.

Vues

Enfin, nous devons définir des modèles pour les vues à générer. Pour cela, nous utilisons Thymeleaf, un moteur de template simple. Le modèle que nous avons utilisé dans les méthodes de contrôleur est disponible directement dans les modèles, c'est-à-dire que lorsque nous entrons un contrat dans la clé « contract » dans un modèle, nous pourrons accéder au champ de nom en tant que « contract.name » depuis le modèle.

Thymeleaf contient des éléments et des attributs spéciaux qui contrôlent la génération de HTML. Ils sont très intuitifs et simples. Par exemple, pour remplir le contenu d'un élément span avec le nom d'une compétence, il suffit de définir l'attribut suivant (en supposant que la clé « compétence » est définie dans le modèle) :

 <span th:text="${skill.label}"></span>

De même pour définir l'attribut « href » d'un élément d'ancrage, l'attribut spécial « th:href » peut être utilisé.

Dans notre application, nous aurons besoin de deux modèles simples. Pour plus de clarté, nous allons ignorer tous les attributs de style et de classe (à savoir ceux de Bootstrap) ici dans le code de modèle intégré.

Liste des développeurs

 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developers database</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developers</h1> <table> <tr> <th>Name</th> <th>Skills</th> <th></th> </tr> <tr th:each="developer : ${developers}"> <td th:text="${developer.firstName + ' ' + developer.lastName}"></td> <td> <span th:each="skill,iterStat : ${developer.skills}"> <span th:text="${skill.label}"/><th:block th:if="${!iterStat.last}">,</th:block> </span> </td> <td> <a th:href="@{/developer/{id}(id=${developer.id})}">view</a> </td> </tr> </table> <hr/> <form th:action="@{/developers}" method="post" enctype="multipart/form-data"> <div> First name: <input name="firstName" /> </div> <div> Last name: <input name="lastName" /> </div> <div> Email: <input name="email" /> </div> <div> <input type="submit" value="Create developer" name="button"/> </div> </form> </body> </html>

Détails du développeur

 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developer</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developer</h1> Name: <b th:text="${developer.firstName}" /> <b th:text="${developer.lastName}" /><br/> Email: <span th:text="${developer.email}" /><br/> Skills: <span th:each="skill : ${developer.skills}"> <br/>&nbsp;&nbsp;<span th:text="${skill.label}" /> - <span th:text="${skill.description}" /> </span> <form th:action="@{/developer/{id}/skills(id=${developer.id})}" method="post" enctype="multipart/form-data" > <select name="skillId"> <option th:each="skill : ${skills}" th:value="${skill.id}" th:text="${skill.description}">Skill</option> </select> <input type="submit" value="Add skill"/> </form> </body> </html>

Exécution du serveur

Spring contient un module de démarrage. Cela nous permet de démarrer le serveur facilement à partir de la ligne de commande en tant qu'application Java en ligne de commande :

 @SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

Puisque nous utilisons une base de données en mémoire, il est logique de démarrer la base de données avec des données prédéfinies au lancement. De cette façon, nous aurons au moins quelques données dans la base de données lorsque le serveur sera opérationnel.

 @Override public void run(String... args) throws Exception { Skill javascript = new Skill("javascript", "Javascript language skill"); Skill ruby = new Skill("ruby", "Ruby language skill"); Skill emberjs = new Skill("emberjs", "Emberjs framework"); Skill angularjs = new Skill("angularjs", "Angularjs framework"); skillRepository.save(javascript); skillRepository.save(ruby); skillRepository.save(emberjs); skillRepository.save(angularjs); List<Developer> developers = new LinkedList<Developer>(); developers.add(new Developer("John", "Smith", "[email protected]", Arrays.asList(new Skill[] { javascript, ruby }))); developers.add(new Developer("Mark", "Johnson", "[email protected]", Arrays.asList(new Skill[] { emberjs, ruby }))); developers.add(new Developer("Michael", "Williams", "[email protected]", Arrays.asList(new Skill[] { angularjs, ruby }))); developers.add(new Developer("Fred", "Miller", "[email protected]", Arrays.asList(new Skill[] { emberjs, angularjs, javascript }))); developers.add(new Developer("Bob", "Brown", "[email protected]", Arrays.asList(new Skill[] { emberjs }))); developerRepository.save(developers); }

Conclusion

Spring est un framework polyvalent qui permet de créer des applications MVC. Construire une application simple avec Spring est rapide et transparent. L'application peut également être facilement intégrée à une base de données à l'aide de JPA.

Le code source de l'ensemble de ce projet est disponible sur GitHub.