Construirea unei aplicații MVC cu Spring Framework: Un tutorial pentru începători
Publicat: 2022-03-11Deseori se spune că Java este prea complicat și că durează prea mult pentru a construi aplicații simple. Cu toate acestea, Java oferă o platformă stabilă cu un ecosistem foarte matur în jurul său, ceea ce o face o opțiune minunată pentru dezvoltarea de software robust.
Spring Framework, unul dintre numeroasele cadre puternice din ecosistemul Java, vine cu o colecție de modele de programare și configurare cu scopul de a simplifica dezvoltarea de aplicații performante și testabile în Java.
În acest tutorial, vom accepta provocarea de a construi o aplicație simplă care va acționa ca o bază de date a dezvoltatorilor de software folosind Spring Framework și Java Persistence API (JPA).
Aplicația urmează o arhitectură MVC standard. Va avea un controler (clasa ContractsController), vederi (bazate pe șabloane Thymeleaf) și un model (un obiect de hartă Java). Din motive de simplitate, vom folosi o bază de date în memorie în spatele JPA pentru a persista datele în timp ce aplicația rulează.
Noțiuni introductive cu tutorialul Spring Framework
Pentru a construi o aplicație bazată pe Spring, va trebui să folosim unul dintre următoarele instrumente de compilare:
- Maven
- Gradle
În acest tutorial, vom folosi Maven. Dacă nu sunteți familiarizat cu niciunul dintre aceste instrumente, o modalitate ușoară de a începe este să descărcați Spring Tool Suite. Suita este dedicată pentru Spring Framework și vine cu propriul său IDE bazat pe Eclipse.
În Spring Tool Suite, creăm un nou proiect selectând „Spring Starter Project” din meniul „Fișier > Nou”.
Odată ce a fost creat un nou proiect, va trebui să edităm fișierul de configurare Maven, „ pom.xml ”, și să adăugăm următoarele dependențe:
<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>
Aceste dependențe enumerate vor încărca Spring Boot Web, Thymeleaf, JPA și H2 (care vor servi ca bază de date în memorie). Toate bibliotecile necesare vor fi extrase automat.
Clasele de entitati
Pentru a putea stoca informații despre dezvoltatori și abilitățile acestora, va trebui să definim două clase de entități: „ Developer ” și „ Skill ”.
Ambele sunt definite ca clase Java simple cu unele adnotări. Adăugând „@Entity” înainte de cursuri, punem la dispoziție instanțe ale acestora pentru JPA. Acest lucru va facilita stocarea și preluarea instanțelor din depozitul de date persistente atunci când este necesar. În plus, adnotările „@Id” și „@GeneratedValue” ne permit să indicăm câmpul ID unic pentru entitate și să avem valoarea acestuia generată automat atunci când este stocată în baza de date.
Deoarece un dezvoltator poate avea multe abilități, putem defini o relație simplă multi-la-mulți folosind adnotarea „@ManyToMany”.
Dezvoltator
@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; } }
Îndemânare
@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; } }
Depozitele
Cu JPA putem defini o interfață DeveloperRepository și o interfață SkillRepository foarte utile, care permit operațiuni CRUD ușoare. Aceste interfețe ne vor permite să accesăm dezvoltatorii și abilitățile stocate prin apeluri de metode simple, cum ar fi:
- „repository.findAll()”: returnează toți dezvoltatorii
- „repository.findOne(id)”: returnează dezvoltatorul cu ID-ul dat
Pentru a crea aceste interfețe, tot ce trebuie să facem este să extindem interfața CrudRepository.
Depozitul dezvoltatorului
public interface DeveloperRepository extends CrudRepository<Developer, Long> { }
Depozitul de aptitudini
public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }
Funcționalitatea pentru metoda suplimentară „ findByLabel ” declarată aici va fi furnizată automat de JPA.
Controlor
În continuare, putem lucra la controlerul pentru această aplicație. Controlorul va mapa URI-urile de solicitare pentru a vizualiza șabloanele și pentru a efectua toate procesările necesare între ele.
@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"; } }
Maparea URI-urilor la metode se face prin adnotări simple „@RequestMapping”. În acest caz, fiecare metodă a controlerului este mapată la un URI.

Parametrul de model al acestor metode permite transmiterea datelor către vizualizare. În esență, acestea sunt hărți simple ale cheilor către valori.
Fiecare metodă de controler returnează fie numele șablonului Thymeleaf pentru a fi utilizat ca vizualizare, fie o adresă URL într-un anumit model („redirecționare:
În cadrul controlerului, adnotările „@Autowired” atribuie automat o instanță validă a depozitului nostru definit în câmpul corespunzător. Acest lucru permite accesul la datele relevante din interiorul controlerului fără a fi nevoie să se ocupe de o mulțime de coduri standard.
Vizualizări
În cele din urmă, trebuie să definim câteva șabloane pentru vizualizările care urmează să fie generate. Pentru aceasta folosim Thymeleaf, un simplu motor de șabloane. Modelul pe care l-am folosit în metodele de control este disponibil direct în cadrul șabloanelor, adică atunci când introducem un contract în cheia „ contract ” într-un model, vom putea accesa câmpul de nume ca „contract.name” din interiorul șablonului.
Thymeleaf conține câteva elemente și atribute speciale care controlează generarea de HTML. Sunt foarte intuitive și directe. De exemplu, pentru a popula conținutul unui element span cu numele unei aptitudini, tot ce trebuie să faceți este să definiți următorul atribut (presupunând că cheia „ skill ” este definită în model):
<span th:text="${skill.label}"></span>
În mod similar, pentru a seta atributul „ href ” al unui element ancoră, poate fi folosit atributul special „ th:href ”.
În aplicația noastră, vom avea nevoie de două șabloane simple. Pentru claritate, vom sări peste toate atributele de stil și clasă (și anume cele Bootstrap) aici în codul șablonului încorporat.
Lista dezvoltatorilor
<!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>
Detalii despre dezvoltator
<!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/> <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>
Rularea serverului
Spring conține un modul de încărcare. Acest lucru ne permite să pornim serverul cu ușurință din linia de comandă ca o aplicație Java în linia de comandă:
@SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Deoarece folosim o bază de date în memorie, este logic să pornim baza de date cu unele date predefinite la lansare. În acest fel vom avea cel puțin câteva date în baza de date atunci când serverul este în funcțiune.
@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); }
Concluzie
Spring este un cadru versatil care permite construirea de aplicații MVC. Crearea unei aplicații simple cu Spring este rapidă și transparentă. Aplicația poate fi, de asemenea, integrată cu o bază de date cu ușurință folosind JPA.
Codul sursă al întregului proiect este disponibil pe GitHub.