Erstellen einer MVC-Anwendung mit Spring Framework: Ein Tutorial für Anfänger

Veröffentlicht: 2022-03-11

Java wird oft nachgesagt, dass es zu kompliziert ist und zu lange dauert, um einfache Anwendungen zu erstellen. Nichtsdestotrotz bietet Java eine stabile Plattform mit einem sehr ausgereiften Ökosystem, das es zu einer wunderbaren Option für die Entwicklung robuster Software macht.

Das Spring Framework, eines der vielen leistungsstarken Frameworks im Java-Ökosystem, enthält eine Sammlung von Programmier- und Konfigurationsmodellen mit dem Ziel, die Entwicklung performanter und testbarer Anwendungen in Java zu vereinfachen.

Frühlingsrahmen

In diesem Tutorial stellen wir uns der Herausforderung, eine einfache Anwendung zu erstellen, die als Datenbank für Softwareentwickler fungiert, die Spring Framework und die Java Persistence API (JPA) verwenden.

Die Anwendung folgt einer Standard-MVC-Architektur. Es verfügt über einen Controller (ContractsController-Klasse), Ansichten (basierend auf Thymeleaf-Vorlagen) und ein Modell (ein Java-Map-Objekt). Der Einfachheit halber verwenden wir eine In-Memory-Datenbank hinter JPA, um Daten beizubehalten, während die Anwendung ausgeführt wird.

Erste Schritte mit dem Spring Framework-Tutorial

Um eine Spring-basierte Anwendung zu erstellen, müssen wir eines der folgenden Build-Tools verwenden:

  • Maven
  • Gradl

In diesem Tutorial verwenden wir Maven. Wenn Sie mit keinem dieser Tools vertraut sind, können Sie die Spring Tool Suite herunterladen, um loszulegen. Die Suite ist für Spring Framework bestimmt und wird mit einer eigenen Eclipse-basierten IDE geliefert.

In der Spring Tool Suite erstellen wir ein neues Projekt, indem wir „Spring Starter Project“ im Menü „File > New“ auswählen.

Spring Framework-Tutorial

Sobald ein neues Projekt erstellt wurde, müssen wir die Maven-Konfigurationsdatei „ pom.xml “ bearbeiten und die folgenden Abhängigkeiten hinzufügen:

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

Diese aufgelisteten Abhängigkeiten laden Spring Boot Web, Thymeleaf, JPA und H2 (das als unsere In-Memory-Datenbank dient). Alle notwendigen Bibliotheken werden automatisch gezogen.

Entitätsklassen

Um Informationen über Entwickler und ihre Skills speichern zu können, müssen wir zwei Entity-Klassen definieren: „ Developer “ und „ Skill “.

Beide sind als einfache Java-Klassen mit einigen Anmerkungen definiert. Indem wir „@Entity“ vor den Klassen hinzufügen, machen wir ihre Instanzen für JPA verfügbar. Dies erleichtert das Speichern und Abrufen von Instanzen aus dem persistenten Datenspeicher bei Bedarf. Darüber hinaus ermöglichen uns die Annotationen „@Id“ und „@GeneratedValue“, das eindeutige ID-Feld für die Entität anzugeben und ihren Wert automatisch generieren zu lassen, wenn sie in der Datenbank gespeichert werden.

Da ein Entwickler über viele Fähigkeiten verfügen kann, können wir mit der Annotation „@ManyToMany“ eine einfache Viele-zu-Viele-Beziehung definieren.

Entwickler

 @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; } }

Fertigkeit

 @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; } }

Aufbewahrungsorte

Mit JPA können wir eine sehr nützliche DeveloperRepository-Schnittstelle und eine SkillRepository-Schnittstelle definieren, die einfache CRUD-Operationen ermöglichen. Diese Schnittstellen ermöglichen uns den Zugriff auf gespeicherte Entwickler und Fähigkeiten durch einfache Methodenaufrufe, wie zum Beispiel:

  • „respository.findAll()“: gibt alle Entwickler zurück
  • „repository.findOne(id)“: Gibt den Entwickler mit der angegebenen ID zurück

Um diese Schnittstellen zu erstellen, müssen wir lediglich die CrudRepository-Schnittstelle erweitern.

Entwickler-Repository

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

Skill-Repository

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

Die Funktionalität für die hier deklarierte Zusatzmethode „ findByLabel “ wird von JPA automatisch bereitgestellt.

Regler

Als nächstes können wir an der Steuerung für diese Anwendung arbeiten. Der Controller ordnet Anforderungs-URIs Ansichtsvorlagen zu und führt alle erforderlichen Verarbeitungen dazwischen durch.

 @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"; } }

Die Zuordnung von URIs zu Methoden erfolgt über einfache „@RequestMapping“-Annotationen. In diesem Fall wird jede Methode des Controllers auf eine URI abgebildet.

Der Modellparameter dieser Methoden ermöglicht die Übergabe von Daten an die Ansicht. Im Wesentlichen sind dies einfache Zuordnungen von Schlüsseln zu Werten.

Jede Controller-Methode gibt entweder den Namen des Thymeleaf-Templates zurück, das als View verwendet werden soll, oder eine URL in einem bestimmten Muster („redirect: “) umzuleiten. Beispielsweise geben die Methoden „developer“ und „_developersList_“ den Namen einer Vorlage zurück, während „developersAdd“ und „developersAddSkill“ URLs zurückgeben, auf die umgeleitet werden soll.

Innerhalb des Controllers weisen die Annotationen „@Autowired“ automatisch eine gültige Instanz unseres definierten Repositorys im entsprechenden Feld zu. Dies ermöglicht den Zugriff auf relevante Daten innerhalb der Steuerung, ohne sich mit viel Boilerplate-Code befassen zu müssen.

Ansichten

Schließlich müssen wir einige Vorlagen für die zu generierenden Ansichten definieren. Dafür verwenden wir Thymeleaf, eine einfache Templating-Engine. Das Modell, das wir in Controller-Methoden verwendet haben, ist direkt in den Vorlagen verfügbar, dh wenn wir in einem Modell einen Vertrag in den Schlüssel „ Vertrag “ eingeben, können wir aus der Vorlage heraus auf das Namensfeld als „Vertragsname“ zugreifen.

Thymeleaf enthält einige spezielle Elemente und Attribute, die die Generierung von HTML steuern. Sie sind sehr intuitiv und unkompliziert. Um beispielsweise den Inhalt eines span-Elements mit dem Namen einer Fertigkeit zu füllen, müssen Sie lediglich das folgende Attribut definieren (vorausgesetzt, der Schlüssel „ Fertigkeit “ ist im Modell definiert):

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

Um das Attribut „ href “ eines Ankerelements zu setzen, kann ähnlich das spezielle Attribut „ th:href “ verwendet werden.

In unserer Anwendung benötigen wir zwei einfache Vorlagen. Aus Gründen der Übersichtlichkeit überspringen wir hier im eingebetteten Vorlagencode alle Stil- und Klassenattribute (insbesondere Bootstrap-Attribute).

Entwicklerliste

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

Entwicklerdetails

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

Ausführen des Servers

Spring enthält ein Boot-Modul. Dies ermöglicht es uns, den Server einfach von der Befehlszeile aus als Befehlszeilen-Java-Anwendung zu starten:

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

Da wir eine In-Memory-Datenbank verwenden, ist es sinnvoll, die Datenbank beim Start mit einigen vordefinierten Daten zu booten. Auf diese Weise haben wir zumindest einige Daten in der Datenbank, wenn der Server läuft.

 @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); }

Fazit

Spring ist ein vielseitiges Framework, mit dem MVC-Anwendungen erstellt werden können. Das Erstellen einer einfachen Anwendung mit Spring ist schnell und transparent. Die Anwendung kann auch einfach mit JPA in eine Datenbank integriert werden.

Der Quellcode dieses gesamten Projekts ist auf GitHub verfügbar.