Hallo an alle. Heute teilen wir den ersten Teil des Artikels, dessen Ăbersetzung speziell fĂŒr Studenten des Kurses "Developer on the Spring Framework" erstellt wurde . Fangen wir an!
Spring ist vielleicht eine der beliebtesten Java-Entwicklungsplattformen. Dies ist ein mĂ€chtiges, aber ziemlich schwer zu erlernendes Werkzeug. Die grundlegenden Konzepte sind relativ einfach zu verstehen und zu erlernen, aber es erfordert Zeit und MĂŒhe, ein erfahrener Spring-Entwickler zu werden.
In diesem Artikel werden einige der hÀufigsten Fehler bei der Arbeit in Spring behandelt, die sich insbesondere auf die Entwicklung von Webanwendungen und die Verwendung der Spring Boot-Plattform beziehen. Wie auf
der Spring Boot-Website erwÀhnt , verfolgt Spring Boot einen
standardisierten Ansatz zum Erstellen gebrauchsfertiger Anwendungen. Dieser Artikel folgt diesem Ansatz. Es enthÀlt eine Reihe von Empfehlungen, die bei der Entwicklung von Standard-Webanwendungen auf Basis von Spring Boot effektiv verwendet werden können.
Falls Sie mit der Spring Boot-Plattform nicht sehr vertraut sind, aber mit den Beispielen in diesem Artikel experimentieren möchten, habe ich ein
GitHub-Repository mit zusĂ€tzlichen Materialien fĂŒr diesen Artikel erstellt . Wenn Sie diesen Artikel irgendwann etwas verwirrt lesen, wĂŒrde ich Ihnen raten, einen Klon dieses Repositorys zu erstellen und mit dem Code auf Ihrem Computer zu experimentieren.
HĂ€ufiger Fehler Nr. 1: Zu einfache Programmierung
Wir können diesem weit verbreiteten Fehler leicht erliegen, da das
"Syndrom der Ablehnung der Entwicklung eines anderen" fĂŒr die Programmierumgebung recht typisch ist. Eines seiner Symptome ist das stĂ€ndige Umschreiben von hĂ€ufig verwendeten Codeteilen, und dies ist ein Symptom, das von vielen Programmierern gesehen wird.
Das Studium der internen Struktur und der Implementierungsfunktionen einer bestimmten Bibliothek ist hĂ€ufig ein nĂŒtzlicher, notwendiger und interessanter Prozess. Wenn Sie jedoch wĂ€hrend der Implementierung von Funktionen auf niedriger Ebene stĂ€ndig denselben Codetyp schreiben, kann sich dies als schĂ€dlich fĂŒr Sie als Softwareentwickler herausstellen. Aus diesem Grund existieren und werden Abstraktionen und Plattformen wie Spring verwendet. Sie ersparen Ihnen wiederholte manuelle Arbeit und ermöglichen es Ihnen, sich auf Objekte Ihres Themenbereichs und der Programmcodelogik auf einer höheren Ebene zu konzentrieren.
Abstraktionen sollten daher nicht vermieden werden. Wenn Sie das nĂ€chste Mal mit der Notwendigkeit konfrontiert sind, ein bestimmtes Problem zu lösen, fĂŒhren Sie zunĂ€chst eine schnelle Suche durch und finden Sie heraus, ob die Bibliothek, die dieses Problem löst, bereits in Spring integriert ist. Sie werden wahrscheinlich eine geeignete schlĂŒsselfertige Lösung finden. Eine solche nĂŒtzliche Bibliothek ist
Project Lombok , die Anmerkungen, aus denen ich in den Beispielen in diesem Artikel verwenden werde. Lombok wird als Vorlagencode-Generator verwendet, daher wird der faule Entwickler, der in jedem von uns lebt, sehr glĂŒcklich sein, diese Bibliothek kennenzulernen. Sehen Sie sich beispielsweise an, wie die
Standard-JavaBean-Komponente in Lombok aussieht:
@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; }
Wie Sie vielleicht bereits verstanden haben, wird der obige Code in die folgende Form konvertiert:
public class Bean implements Serializable { private int firstBeanProperty; private String secondBeanProperty; public int getFirstBeanProperty() { return this.firstBeanProperty; } public String getSecondBeanProperty() { return this.secondBeanProperty; } public void setFirstBeanProperty(int firstBeanProperty) { this.firstBeanProperty = firstBeanProperty; } public void setSecondBeanProperty(String secondBeanProperty) { this.secondBeanProperty = secondBeanProperty; } public Bean() { } }
Beachten Sie, dass Sie höchstwahrscheinlich das entsprechende Plug-In installieren mĂŒssen, wenn Sie Lombok in Ihrer integrierten Entwicklungsumgebung verwenden möchten. Die Version dieses Plugins fĂŒr die IntelliJ IDEA finden Sie
hier .
HĂ€ufiger Fehler Nummer 2. âLeckâ interner Strukturen
Der Zugang zu internen Strukturen war nie eine gute Idee, da dies die FlexibilitĂ€t des Servicemodells beeintrĂ€chtigt und infolgedessen zur Bildung eines schlechten Programmierstils beitrĂ€gt. Das âLeckâ interner Strukturen manifestiert sich in der Tatsache, dass auf die Datenbankstruktur von bestimmten API-Endpunkten aus zugegriffen werden kann. Angenommen, das folgende âgute alte Java-Objektâ (POJO) reprĂ€sentiert eine Tabelle in Ihrer Datenbank:
@Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } }
Angenommen, es gibt einen Endpunkt, der auf die Daten eines
TopTalentEntity
Objekts
TopTalentEntity
. Das ZurĂŒckgeben von Instanzen von
TopTalentEntity
scheint eine verlockende Idee zu sein. Eine flexiblere Lösung wĂ€re jedoch, eine neue Klasse zu erstellen, die TopTalentEntity-Daten fĂŒr den API-Endpunkt darstellt:
@AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; }
Das Vornehmen von Ănderungen an den internen Komponenten der Datenbank erfordert daher keine zusĂ€tzlichen Ănderungen auf Serviceebene. Mal sehen, was passiert, wenn das Kennwortfeld zur
TopTalentEntity
Klasse hinzugefĂŒgt wird, um Benutzerkennwort-Hashes in der Datenbank zu speichern: Wenn kein Connector wie
TopTalentData
ist und der Entwickler vergisst, den Schnittstellenteil des Dienstes zu Ă€ndern, kann dies zu einer sehr unerwĂŒnschten Offenlegung geheimer Informationen fĂŒhren!
HÀufiger Fehler Nummer 3. Kombinieren von Funktionen, die besser im Code verteilt werden können
Das Organisieren von Anwendungscode wÀhrend des Wachstums wird zu einer immer wichtigeren Aufgabe. Seltsamerweise funktionieren die meisten Prinzipien einer effektiven Programmierung nicht mehr, wenn ein bestimmter Entwicklungsgrad erreicht ist, insbesondere wenn die Anwendungsarchitektur nicht gut durchdacht ist. Und einer der am hÀufigsten gemachten Fehler ist die Kombination von Funktionen im Code, deren separate Implementierung sinnvoller ist.
Der Grund fĂŒr die Verletzung des Grundsatzes der
Aufgabentrennung ist in der Regel die HinzufĂŒgung neuer Funktionen zu bestehenden Klassen. Vielleicht ist dies eine gute momentane Lösung (insbesondere erfordert es das Schreiben einer geringeren Menge an Code), aber in Zukunft wird es unweigerlich zu einem Problem, auch in den Phasen des Testens und Verwaltens des Codes und zwischen diesen. Betrachten Sie den folgenden Controller, der
TopTalentData
aus dem Repository zurĂŒckgibt:
@RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping("/toptal/get") public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }
Auf den ersten Blick scheint es keine offensichtlichen Fehler in diesem Codefragment zu geben. Es enthÀlt eine Liste der
TopTalentData
Objekte, die aus Instanzen der
TopTalentEntity
Klasse stammt. Wenn Sie sich den Code genauer ansehen, werden Sie
TopTalentController
dass
TopTalentController
in Wirklichkeit mehrere Aktionen ausfĂŒhrt, nĂ€mlich Anforderungen mit einem bestimmten Endpunkt korreliert, Daten aus dem Repository
TopTalentRepository
und von
TopTalentRepository
empfangene
TopTalentRepository
in ein anderes Format konvertiert. Eine sauberere Lösung sollte diese Funktionen in separate Klassen unterteilen. Zum Beispiel könnte es so aussehen:
@RestController @RequestMapping("/toptal") @AllArgsConstructor public class TopTalentController { private final TopTalentService topTalentService; @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); } } @AllArgsConstructor @Service public class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } } @Component public class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }
Ein zusĂ€tzlicher Vorteil dieser Hierarchie besteht darin, dass wir anhand des Klassennamens verstehen können, wo sich eine Funktion befindet. AnschlieĂend können wir wĂ€hrend des Testens den Code einer dieser Klassen bei Bedarf problemlos durch Ersatzcode ersetzen.
HĂ€ufige Fehlernummer 4. Einheitlicher Code und schlechte Fehlerbehandlung
Das Thema der Code-Einheitlichkeit gilt nicht nur fĂŒr Spring (oder allgemein fĂŒr Java), ist jedoch ein wichtiger Aspekt, der bei der Arbeit mit Projekten in Spring berĂŒcksichtigt werden muss. Die Auswahl eines bestimmten Programmierstils kann ein Diskussionsthema sein (und ist normalerweise innerhalb des Entwicklungsteams oder im gesamten Unternehmen konsistent), aber in jedem Fall trĂ€gt das Vorhandensein eines gemeinsamen Standards zum Schreiben von Code zu einer Steigerung der Arbeitseffizienz bei. Dies gilt insbesondere dann, wenn mehrere Personen am Code arbeiten. Einheitlicher Code kann von Entwickler zu Entwickler weitergegeben werden, ohne dass viel Aufwand fĂŒr die Wartung oder ausfĂŒhrliche ErklĂ€rungen erforderlich ist, warum diese oder jene Klassen benötigt werden.
Stellen Sie sich vor, es gibt ein Spring-Projekt, in dem verschiedene Konfigurationsdateien, Dienste und Controller vorhanden sind. Nach der semantischen Einheitlichkeit bei der Benennung erstellen wir eine Struktur, anhand derer Sie leicht suchen können und in der jeder Entwickler den Code leicht verstehen kann. Sie können beispielsweise das Konfigurationssuffix zu den Namen der Konfigurationsklassen, das Dienstsuffix zu den Dienstnamen und das Controller-Suffix zu den Controllernamen hinzufĂŒgen.
Die serverseitige Fehlerbehandlung hÀngt eng mit der Code-Einheitlichkeit zusammen und verdient besondere Aufmerksamkeit. Wenn Sie jemals Ausnahmen behandelt haben, die von einer schlecht geschriebenen API stammen, wissen Sie wahrscheinlich, wie schwierig es ist, die Bedeutung dieser Ausnahmen richtig zu verstehen und noch schwieriger festzustellen, warum sie tatsÀchlich aufgetreten sind.
Als API-Entwickler möchten Sie im Idealfall alle Endpunkte abdecken, mit denen der Benutzer arbeitet, und sie dazu bringen, ein einziges Fehlermeldungsformat zu verwenden. In der Regel bedeutet dies, dass Sie Standardfehlercodes und deren Beschreibung verwenden und zweifelhafte Entscheidungen wie das Versenden einer Meldung â500 Internal Server Errorâ oder Stack-Trace-Ergebnisse an den Benutzer aufgeben mĂŒssen (letztere Option sollte ĂŒbrigens auf jeden Fall vermieden werden, da Sie interne Daten preisgeben AuĂerdem sind solche Ergebnisse auf Kundenseite schwer zu verarbeiten.
Hier zum Beispiel, wie das allgemeine Format der Fehlermeldung aussehen könnte:
@Value public class ErrorResponse { private Integer errorCode; private String errorMessage; }
Ein Àhnliches Format findet sich hÀufig in den gÀngigsten APIs und funktioniert in der Regel hervorragend, da es einfach und systematisch dokumentiert werden kann. Sie können eine Ausnahme in dieses Format konvertieren, indem Sie der
@ExceptionHandler
Annotation
@ExceptionHandler
hinzufĂŒgen (ein Beispiel fĂŒr die Annotation finden Sie im Abschnitt "HĂ€ufiger Fehler Nr. 6").
HĂ€ufiger Fehler Nummer 5. Falsche Arbeit mit Multithreading
Das Implementieren von Multithreading kann eine schwierige Aufgabe sein, unabhĂ€ngig davon, ob es in Desktopanwendungen oder Webanwendungen, in Spring-Projekten oder in Projekten fĂŒr andere Plattformen verwendet wird. Probleme, die durch die parallele AusfĂŒhrung von Programmen verursacht werden, sind schwer zu verfolgen, und die Behandlung mit einem Debugger ist oft sehr schwierig. Wenn Sie verstehen, dass es sich um einen parallelen AusfĂŒhrungsfehler handelt, mĂŒssen Sie höchstwahrscheinlich den Debugger verlassen und den Code manuell untersuchen, bis die Hauptursache des Fehlers gefunden ist. Leider gibt es keinen Standardweg, um solche Probleme zu lösen. In jedem Fall ist es notwendig, die Situation zu bewerten und das Problem mit der Methode âanzugreifenâ, die unter den gegebenen Bedingungen am besten erscheint.
Im Idealfall möchten Sie natĂŒrlich die mit Multithreading verbundenen Fehler vollstĂ€ndig vermeiden. Und obwohl es hier kein universelles Rezept gibt, kann ich dennoch einige praktische Empfehlungen geben.
Vermeiden Sie die Verwendung globaler ZustÀnde
Denken Sie zunĂ€chst immer an das Problem der âglobalen Staatenâ. Wenn Sie eine Multithread-Anwendung entwickeln, mĂŒssen Sie absolut alle global verĂ€nderbaren Variablen sorgfĂ€ltig ĂŒberwachen und sie nach Möglichkeit vollstĂ€ndig entfernen. Wenn Sie immer noch einen Grund haben, warum die globale Variable geĂ€ndert werden sollte, implementieren Sie die
Synchronisierung ordnungsgemÀà und ĂŒberwachen Sie die Leistung Ihrer Anwendung. Sie sollten sicherstellen, dass sie nicht durch zusĂ€tzliche Wartezeiten verlangsamt wird.
Vermeiden Sie verÀnderbare Objekte
Diese Idee stammt direkt aus den Prinzipien der
funktionalen Programmierung und besagt, dass verĂ€nderbare Klassen und verĂ€nderliche ZustĂ€nde vermieden werden sollten, da sie an die Prinzipien der OOP angepasst sind. Kurz gesagt bedeutet dies, dass Sie keine Einstellungsmethoden (Setter) festlegen und private Felder mit dem endgĂŒltigen Modifikator in allen Modellklassen haben sollten. Ihre Werte Ă€ndern sich nur, wenn das Objekt erstellt wird. In diesem Fall können Sie sicher sein, dass mit der Konkurrenz um Ressourcen keine Probleme verbunden sind. Wenn Sie auf die Eigenschaften des Objekts zugreifen, werden immer die richtigen Werte erhalten.
Wichtige Daten protokollieren
Bewerten Sie, wo Probleme in der Anwendung auftreten können, und richten Sie im Voraus eine Protokollierung aller wichtigen Daten ein. Wenn ein Fehler auftritt, freuen Sie sich ĂŒber Informationen darĂŒber, welche Anfragen eingegangen sind, und können die GrĂŒnde fĂŒr die Fehlfunktion Ihrer Anwendung besser verstehen. Beachten Sie jedoch, dass die Protokollierung zusĂ€tzliche Datei-E / A umfasst und nicht missbraucht werden sollte, da dies die Anwendungsleistung beeintrĂ€chtigen kann.
Verwenden Sie vorgefertigte Thread-Implementierungen
Wenn Sie Ihre Threads erzeugen mĂŒssen (z. B. um asynchrone Anforderungen an verschiedene Dienste zu erstellen), verwenden Sie vorgefertigte sichere Thread-Implementierungen, anstatt Ihre eigenen zu erstellen. In den meisten FĂ€llen können Sie die
ExecutorService- Abstraktionen und die spektakulÀren funktionalen Abstraktionen
CompletableFuture fĂŒr Java 8 verwenden, um Threads zu erstellen. Mit der Spring-Plattform können Sie auch asynchrone Anforderungen mithilfe der
DeferredResult- Klasse verarbeiten.
Das Ende des ersten Teils.
Lesen Sie den zweiten Teil.