Die 10 häufigsten Spring Framework-Fehler

Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels „Top 10 der häufigsten Fehler im Frühjahrsrahmen“ von Toni Kukurin.

Spring ist wahrscheinlich eines der beliebtesten Java-Frameworks und ein mächtiges Tier zum Zähmen. Obwohl die Grundkonzepte ziemlich einfach zu verstehen sind, braucht es Zeit und Mühe, um ein starker Spring-Entwickler zu werden.

In diesem Artikel werden einige der häufigsten Fehler in Spring behandelt, insbesondere diejenigen, die sich auf Webanwendungen und Spring Boot beziehen. Wie auf der Spring Boot-Website angegeben , enthält es eine Vorstellung davon, wie industrielle Anwendungen erstellt werden sollten. In diesem Artikel werden wir versuchen, diese Idee zu demonstrieren und einen Überblick über einige Tipps zu geben, die gut in den Standardentwicklungsprozess für Spring Boot-Webanwendungen passen.
Wenn Sie mit Spring Boot nicht sehr vertraut sind, aber dennoch einige der genannten Dinge ausprobieren möchten, habe ich das GitHub-Repository erstellt, das diesem Artikel beiliegt . Wenn Sie das Gefühl haben, irgendwo im Artikel verloren zu sein, empfehle ich, das Repository auf Ihren lokalen Computer zu klonen und mit dem Code zu spielen.

Häufiger Fehler Nr. 1: Runter zu niedrig


Wir stoßen auf diesen häufigen Fehler, weil das Syndrom „hier nicht erfunden“ in der Welt der Softwareentwicklung weit verbreitet ist. Zu den Symptomen gehört das regelmäßige Umschreiben von Fragmenten häufig verwendeten Codes, und viele Entwickler scheinen darunter zu leiden.

Obwohl das Verständnis der Innenseiten einer bestimmten Bibliothek und ihrer Implementierung zum größten Teil gut und notwendig ist (und ein hervorragender Lernprozess sein kann), ist es für Ihre Entwicklung als Softwareentwickler schädlich, ständig dieselben Implementierungsdetails auf niedriger Ebene zu lösen. Es gibt einen Grund dafür, dass Abstraktionen und Frameworks wie Spring existieren, die Sie strikt von sich wiederholenden manuellen Arbeiten trennen und es Ihnen ermöglichen, sich auf übergeordnete Details zu konzentrieren - Ihre Domänenobjekte und Ihre Geschäftslogik.

Verwenden Sie daher Abstraktionen. Wenn Sie das nächste Mal auf ein bestimmtes Problem stoßen, führen Sie zunächst eine schnelle Suche durch und stellen Sie fest, ob die Bibliothek, die dieses Problem löst, in Spring integriert ist. Derzeit finden Sie höchstwahrscheinlich eine geeignete Lösung. Als Beispiel für eine nützliche Bibliothek werde ich in den Beispielen des restlichen Artikels die Anmerkungen des Lombok-Projekts verwenden . Lombok wird als Vorlagencode-Generator verwendet und der faule Entwickler in Ihnen sollte hoffentlich kein Problem mit der Idee dieser Bibliothek haben. Schauen Sie sich als Beispiel an, wie eine „Standard-Java-Bean“ mit Lombok aussieht:

@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; } 

Wie Sie sich vorstellen können, wird der obige Code wie folgt kompiliert:

 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 jedoch, dass Sie das Plugin höchstwahrscheinlich installieren müssen, wenn Sie Lombok mit Ihrer IDE verwenden möchten. Die Plugin-Version für IntelliJ IDEA finden Sie hier .

Häufiger Fehler Nr. 2: Undichter interner Inhalt


Das Aufdecken Ihrer internen Struktur ist immer eine schlechte Idee, da dies zu einer Starrheit bei der Gestaltung des Dienstes führt und daher zu einer schlechten Codierungspraxis beiträgt. Ein „Leck“ an internem Inhalt manifestiert sich in der Tatsache, dass auf die Datenbankstruktur von bestimmten API-Endpunkten aus zugegriffen werden kann. Angenommen, das folgende POJO ("Plain Old Java Object") 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 TopTalentEntity-Daten zugreifen muss. Unabhängig davon, wie verlockend es ist, TopTalentEntity-Instanzen zurückzugeben, besteht eine flexiblere Lösung darin, eine neue Klasse zum Anzeigen von TopTalentEntity-Daten auf dem API-Endpunkt zu erstellen:

 @AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; } 

Für Änderungen am Datenbank-Backend sind daher keine zusätzlichen Änderungen an der Service-Schicht erforderlich. Überlegen Sie, was passiert, wenn Sie das Kennwortfeld zu TopTalentEntity hinzufügen, um Benutzerkennwort-Hashes in der Datenbank zu speichern. Wenn Sie ohne einen Connector wie TopTalentData vergessen, den Dienst zu ändern, zeigt das Frontend versehentlich einige sehr unerwünschte geheime Informationen an!

Häufiger Fehler Nr. 3: Fehlende Aufgabentrennung


Wenn Ihre Anwendung wächst, wird die Organisation Ihres Codes zu einem immer wichtigeren Thema. Ironischerweise werden die meisten guten Prinzipien der Softwareentwicklung überall verletzt - insbesondere in den Fällen, in denen dem Entwurf der Anwendungsarchitektur wenig Aufmerksamkeit geschenkt wird. Einer der häufigsten Fehler, mit denen Entwickler konfrontiert sind, ist das Mischen von Code-Verantwortlichkeiten, und das ist sehr einfach!

Was normalerweise gegen das Prinzip der Aufgabentrennung verstößt, ist das „Hinzufügen“ neuer Funktionen zu vorhandenen Klassen. Dies ist natürlich eine ausgezeichnete kurzfristige Lösung (für den Anfang erfordert sie weniger Eingabe), aber sie wird in Zukunft unweigerlich zu einem Problem werden, sei es während des Testens, der Wartung oder irgendwo dazwischen. Betrachten Sie den folgenden Controller, der TopTalentData aus seinem 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()); } } 

Zunächst fällt nicht auf, dass mit diesem Code etwas nicht stimmt. Es enthält eine TopTalentData-Liste, die aus TopTalentEntity-Instanzen abgerufen wird. Wenn Sie genau hinschauen, werden wir jedoch feststellen, dass TopTalentController hier tatsächlich einige Dinge tut. Das heißt: Es ordnet Anforderungen für einen bestimmten Endpunkt zu, ruft Daten aus dem Repository ab und konvertiert von TopTalentRepository erhaltene Entitäten in ein anderes Format. Eine „sauberere“ Lösung wäre, diese Verantwortlichkeiten in ihre eigenen Klassen zu unterteilen. Es könnte ungefähr 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 durch einfaches Überprüfen des Klassennamens feststellen können, wo sich die Funktionalität befindet. Darüber hinaus können wir während des Testens bei Bedarf problemlos jede der Klassen durch eine Scheinimplementierung ersetzen.

Häufiger Fehler Nr. 4: Inkonsistenz und schlechte Fehlerbehandlung


Das Thema Konsistenz ist nicht unbedingt nur Spring (oder Java) vorbehalten, aber es ist immer noch ein wichtiger Aspekt, der bei der Arbeit an Spring-Projekten berücksichtigt werden muss. Während der Stil des Codeschreibens Gegenstand von Diskussionen sein kann (und normalerweise eine Vereinbarung im Team oder im gesamten Unternehmen ist), ist das Vorhandensein eines gemeinsamen Standards eine große Hilfe für die Leistung. Dies gilt insbesondere für Teams mit mehreren Personen. Die Konsistenz ermöglicht die Übertragung von Code ohne den Aufwand von Ressourcen für die Wartung oder die Bereitstellung detaillierter Erklärungen zu den Verantwortlichkeiten verschiedener Klassen.

Stellen Sie sich ein Spring-Projekt mit verschiedenen Konfigurationsdateien, Diensten und Controllern vor. Da die Benennung semantisch konsistent ist, wird eine leicht durchsuchbare Struktur erstellt, in der jeder neue Entwickler steuern kann, wie mit dem Code gearbeitet wird: Beispielsweise wird das Suffix Config zu Konfigurationsklassen hinzugefügt, das Service-Suffix zu Diensten und das Controller-Suffix zu Controllern.

In enger Beziehung zum Thema Konsistenz verdient die serverseitige Fehlerbehandlung besondere Aufmerksamkeit. Wenn Sie jemals Ausnahmeantworten von einer schlecht geschriebenen API verarbeiten mussten, wissen Sie wahrscheinlich, warum das Analysieren von Ausnahmen schmerzhaft sein kann, und es ist noch schwieriger, den Grund zu bestimmen, warum diese Ausnahmen ursprünglich aufgetreten sind.

Als API-Entwickler möchten Sie idealerweise alle Benutzerendpunkte abdecken und in ein gemeinsames Fehlerformat übersetzen. Dies bedeutet normalerweise, dass Sie einen allgemeinen Fehlercode und eine allgemeine Beschreibung haben und nicht nur eine Entschuldigung in Form von: a) Zurücksenden der Meldung „500 Internal Server Error“ oder b) Zurücksetzen des Stack-Trace an den Benutzer (was um jeden Preis vermieden werden sollte, da es Ihre Innenseiten zeigt zusätzlich zur Komplexität der Verarbeitung auf der Client-Seite).
Ein Beispiel für ein gängiges Fehlerantwortformat könnte sein:

 @Value public class ErrorResponse { private Integer errorCode; private String errorMessage; } 

Ähnliches findet sich normalerweise in den meisten gängigen APIs und funktioniert normalerweise gut, da es einfach und systematisch dokumentiert werden kann. Sie können Ausnahmen in dieses Format übersetzen, indem Sie der Methode die Annotation @ExceptionHandler bereitstellen (ein Beispiel für die Annotation finden Sie in Common Mistake # 6).

Häufiger Fehler Nr. 5: Falsches Multithreading


Unabhängig davon, ob es in Desktop- oder Webanwendungen gefunden wird, im Frühjahr oder nicht im Frühjahr, kann Multithreading eine entmutigende Aufgabe sein. Die Probleme, die durch das Ausführen paralleler Programme verursacht werden, sind schwer zu beheben und oft sehr schwer zu debuggen. Aufgrund der Art des Problems sollten Sie den Debugger wahrscheinlich vollständig verlassen und starten, sobald Sie verstanden haben, dass Sie sich mit dem Problem der parallelen Ausführung befassen Überprüfen Sie Ihren Code manuell, bis Sie die Fehlerursache gefunden haben. Leider gibt es zur Lösung solcher Probleme keine Vorlagenlösung. Je nach Einzelfall müssen Sie die Situation einschätzen und das Problem dann aus einem Blickwinkel angreifen, den Sie für den besten halten.

Idealerweise möchten Sie natürlich die Multithreading-Fehler vollständig vermeiden. Auch hier gibt es keinen einheitlichen Ansatz, aber hier sind einige praktische Überlegungen zum Debuggen und Verhindern von Multithreading-Fehlern:

Vermeiden Sie den globalen Status


Denken Sie immer immer an das Problem des „globalen Staates“. Wenn Sie eine Multithread-Anwendung erstellen, sollte absolut alles, was global geändert werden kann, sorgfältig überwacht und wenn möglich vollständig entfernt werden. Wenn es einen Grund gibt, warum die globale Variable veränderbar bleiben sollte, verwenden Sie die Synchronisierung sorgfältig und überwachen Sie die Leistung Ihrer Anwendung, um sicherzustellen, dass sie aufgrund neuer Wartezeiten nicht langsamer wird.

Vermeiden Sie Veränderlichkeit


Dies ergibt sich direkt aus der funktionalen Programmierung und besagt laut OOP, dass Klassenvolatilität und Zustandsänderung vermieden werden sollten. Kurz gesagt bedeutet das Vorstehende das Vorhandensein von Setzern und privaten Endfeldern in allen Klassen des Modells. Ihre Werte ändern sich nur während des Baus. So können Sie sicher sein, dass es im Wettlauf um Ressourcen keine Probleme gibt und dass der Zugriff auf die Eigenschaften des Objekts immer die richtigen Werte liefert.

Kritische Daten protokollieren


Bewerten Sie, wo Ihre Anwendung Probleme verursachen kann, und protokollieren Sie alle wichtigen Daten vorab. Wenn ein Fehler auftritt, sind Sie dankbar für Informationen darüber, welche Anfragen eingegangen sind, und Sie können besser verstehen, warum sich Ihre Anwendung schlecht verhält. Auch hier sollte beachtet werden, dass die Protokollierung die Datei-E / A erhöht, sodass Sie sie nicht missbrauchen sollten, da dies die Leistung Ihrer Anwendung ernsthaft beeinträchtigen kann.

Bestehende Implementierungen wiederverwenden


Wenn Sie eigene Threads erstellen müssen (z. B. um asynchrone Anforderungen an verschiedene Dienste zu stellen), verwenden Sie vorhandene sichere Implementierungen erneut, anstatt eigene Lösungen zu erstellen. In den meisten Fällen würde dies bedeuten, ExecutorServices und CompletableFutures im übersichtlichen Funktionsstil von Java 8 zum Erstellen von Threads zu verwenden. Spring ermöglicht auch die asynchrone Anforderungsverarbeitung über die DeferredResult- Klasse.

Häufiger Fehler Nr. 6: Keine annotationsbasierte Validierung verwenden


Stellen wir uns vor, unser oben genannter TopTalent-Service benötigt einen Endpunkt, um neue Super-Talente hinzuzufügen. Nehmen wir außerdem an, dass jeder neue Name aus einem wirklich guten Grund genau 10 Zeichen lang sein sollte. Ein Weg, dies zu tun, könnte wie folgt sein:

 @RequestMapping("/put") public void addTopTalent(@RequestBody TopTalentData topTalentData) { boolean nameNonExistentOrHasInvalidLength = Optional.ofNullable(topTalentData) .map(TopTalentData::getName) .map(name -> name.length() == 10) .orElse(true); if (nameNonExistentOrInvalidLength) { // throw some exception } topTalentService.addTopTalent(topTalentData); } 

Das oben Genannte (jedoch ist nicht nur schlecht designt) ist auch keine wirklich „saubere“ Lösung. Wir überprüfen mehr als einen Gültigkeitstyp (nämlich, dass TopTalentData nicht null ist und dass TopTalentData.name nicht null ist und dass TopTalentData.name 10 Zeichen lang ist) und lösen auch eine Ausnahme aus, wenn die Daten ungültig sind.

Dies kann mit dem Hibernate-Validator mit Spring viel sauberer durchgeführt werden. Zuerst schreiben wir die addTopTalent-Methode neu, um die Validierung zu unterstützen:

 @RequestMapping("/put") public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) { // handle validation exception } 

Außerdem müssen wir angeben, welche Eigenschaft wir in der TopTalentData-Klasse überprüfen möchten:

 public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; } 

Spring fängt jetzt die Anforderung ab und überprüft sie, bevor die Methode aufgerufen wird. Es sind keine zusätzlichen manuellen Tests erforderlich.

Eine andere Möglichkeit, dasselbe zu erreichen, besteht darin, eigene Anmerkungen zu erstellen. Obwohl benutzerdefinierte Anmerkungen normalerweise nur verwendet werden, wenn Ihre Anforderungen den integrierten Satz von Konstanten im Ruhezustand überschreiten, stellen wir uns in diesem Beispiel vor, dass Längenanmerkungen nicht vorhanden sind. Sie müssen einen Validator erstellen, der die Länge einer Zeichenfolge überprüft, indem Sie zwei zusätzliche Klassen erstellen, eine zum Überprüfen und eine zum Kommentieren von Eigenschaften:

 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = { MyAnnotationValidator.class }) public @interface MyAnnotation { String message() default "String length does not match expected"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int value(); } @Component public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> { private int expectedLength; @Override public void initialize(MyAnnotation myAnnotation) { this.expectedLength = myAnnotation.value(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return s == null || s.length() == this.expectedLength; } } 

Beachten Sie, dass in diesen Fällen Best Practices für die Aufgabentrennung erfordern, dass Sie eine Eigenschaft als gültig markieren, wenn sie null ist (s == null in der isValid-Methode), und dann die NotNull- Annotation verwenden, wenn dies eine zusätzliche Anforderung für die Eigenschaft ist:

 public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; } 

Häufiger Fehler Nr. 7: Verwenden der (noch) XML-Konfiguration


Obwohl XML für frühere Versionen von Spring erforderlich war, kann der größte Teil der Konfiguration derzeit ausschließlich mit Java-Code / Anmerkungen durchgeführt werden. XML-Konfigurationen stellen lediglich eine zusätzliche und unnötige Boilerplate dar.
In diesem Artikel (und dem dazugehörigen GitHub-Repository) werden Anmerkungen zum Konfigurieren von Spring verwendet. Spring weiß, welche Beans verbunden werden sollen, da das Stammpaket mit der zusammengesetzten Annotation @SpringBootApplication mit Anmerkungen versehen wurde. Beispiel:

 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 

Diese zusammengesetzte Anmerkung (mehr dazu in der Spring- Dokumentation) gibt Spring nur einen Hinweis darauf, welche Pakete gescannt werden sollten, um die Beans zu extrahieren. In unserem speziellen Fall bedeutet dies, dass die folgenden Klassen verwendet werden, um die Beans zu verbinden, beginnend mit dem Paket der obersten Ebene (co.kukurin):

  • @Component (TopTalentConverter, MyAnnotationValidator) @RestController (TopTalentController) @Repository (TopTalentRepository) @Service (TopTalentService)
  • @Component (TopTalentConverter, MyAnnotationValidator) @RestController (TopTalentController) @Repository (TopTalentRepository) @Service (TopTalentService)
  • @Component (TopTalentConverter, MyAnnotationValidator) @RestController (TopTalentController) @Repository (TopTalentRepository) @Service (TopTalentService)
  • @Component (TopTalentConverter, MyAnnotationValidator) @RestController (TopTalentController) @Repository (TopTalentRepository) @Service (TopTalentService)

Wenn zusätzliche Klassen mit @Configuration versehen wären, würden sie auch auf Java-Konfiguration überprüft.

Häufiger Fehler Nummer 8: Profile vergessen


Das Problem, das häufig bei der Entwicklung von Servern auftritt, ist der Unterschied zwischen verschiedenen Arten von Konfigurationen, normalerweise Industrie- und Entwicklungskonfigurationen. Anstatt die verschiedenen Konfigurationsparameter bei jedem Wechsel vom Test zur Anwendungsbereitstellung manuell zu ändern, besteht eine effizientere Möglichkeit darin, Profile zu verwenden.

Betrachten Sie den Fall, wenn Sie die In-Memory-Datenbank für die lokale Entwicklung und die MySQL-Datenbank in PROM verwenden. Im Wesentlichen bedeutet dies, dass Sie unterschiedliche URLs und (hoffentlich) unterschiedliche Anmeldeinformationen verwenden, um auf jede dieser URLs zuzugreifen. Mal sehen, wie das mit zwei verschiedenen Konfigurationsdateien gemacht werden kann:

DATEI ANWENDUNG.YAML


 # set default profile to 'dev' spring.profiles.active: dev # production database details spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal' spring.datasource.username: root spring.datasource.password: 

DATEI ANWENDUNG-DEV.YAML


 spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2 

Anscheinend möchten Sie nicht versehentlich Aktionen in Ihrer Industriedatenbank ausführen, während Sie mit dem Code herumspielen. Daher ist es sinnvoll, das Standardprofil in dev festzulegen. Anschließend können Sie auf dem Server das Konfigurationsprofil manuell überschreiben, indem Sie den Parameter -Dspring.profiles.active = prod für die JVM angeben. Darüber hinaus können Sie die Umgebungsvariable des Betriebssystems auf das gewünschte Standardprofil einstellen.

Häufiger Fehler Nr. 9: Unfähigkeit, Abhängigkeitsinjektion zu akzeptieren


Die ordnungsgemäße Verwendung der Abhängigkeitsinjektion in Spring bedeutet, dass Sie alle Ihre Objekte miteinander verbinden können, indem Sie alle erforderlichen Konfigurationsklassen scannen. Dies ist nützlich zum Entkoppeln von Beziehungen und erleichtert das Testen erheblich. Anstatt Klassen fest zu verknüpfen, gehen Sie folgendermaßen vor:

 public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } } 


Wir lassen Spring die Bindung für uns machen:

 public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } } 

Misko Hevery von Google Talk erläutert ausführlich die „Gründe“ für die Abhängigkeitsinjektion. Lassen Sie uns stattdessen sehen, wie dies in der Praxis verwendet wird. In der Aufgabenteilung (Common Mistakes # 3) haben wir Service- und Controller-Klassen erstellt. Angenommen, wir möchten einen Controller unter der Annahme testen, dass sich TopTalentService korrekt verhält. Wir können anstelle der eigentlichen Service-Implementierung ein Scheinobjekt einfügen und eine separate Konfigurationsklasse bereitstellen:

 @Configuration public class SampleUnitTestConfig { @Bean public TopTalentService topTalentService() { TopTalentService topTalentService = Mockito.mock(TopTalentService.class); Mockito.when(topTalentService.getTopTalent()).thenReturn( Stream.of("Mary", "Joel") .map(TopTalentData::new).collect(Collectors.toList())); return topTalentService; } } 

Anschließend können wir das Scheinobjekt einbetten, indem wir Spring anweisen, SampleUnitTestConfig als Konfigurationsanbieter zu verwenden:

 @ContextConfiguration(classes = { SampleUnitTestConfig.class }) 

Auf diese Weise können wir die Kontextkonfiguration zum Einbetten der benutzerdefinierten Bean in den Komponententest verwenden.

Häufiger Fehler Nr. 10: Fehlende oder falsche Tests


Trotz der Tatsache, dass die Idee des Unit-Tests schon lange bei uns ist, scheinen viele Entwickler dies zu „vergessen“ (insbesondere wenn dies nicht erforderlich ist) oder es einfach für später zu belassen. Dies ist natürlich unerwünscht, da Tests nicht nur die Richtigkeit Ihres Codes überprüfen, sondern auch als Dokumentation darüber dienen sollen, wie sich die Anwendung in verschiedenen Situationen verhalten soll.

Beim Testen von Webdiensten führen Sie selten außergewöhnlich „saubere“ Komponententests durch, da für die Interaktion über HTTP normalerweise DispatcherServlet Spring aufgerufen und überprüft werden muss, was passiert, wenn die eigentliche HttpServletRequest empfangen wird (was es zu einem Integrationstest macht) unter Verwendung von Validierung, Serialisierung usw.). REST Assured - Java DSL zum einfachen Testen von REST-Services auf MockMVC erwies sich als sehr elegante Lösung. Betrachten Sie das folgende Codefragment mit Abhängigkeitsinjektion:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { Application.class, SampleUnitTestConfig.class }) public class RestAssuredTestDemonstration { @Autowired private TopTalentController topTalentController; @Test public void shouldGetMaryAndJoel() throws Exception { // given MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given() .standaloneSetup(topTalentController); // when MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get"); // then response.then().statusCode(200); response.then().body("name", hasItems("Mary", "Joel")); } } 

SampleUnitTestConfig aktiviert die Mock-Implementierung von TopTalentService im TopTalentController, während alle anderen Klassen mit der Standardkonfiguration verbunden sind, die durch das Scannen von Paketen erhalten wird, die Wurzeln im Paket der Application-Klasse haben. RestAssuredMockMvc wird einfach verwendet, um eine kompakte Umgebung zu erstellen und eine GET-Anforderung an den Endpunkt / toptal / get zu senden.

Werde ein Frühlingsmeister


Der Frühling ist ein leistungsstarkes Framework, mit dem man leicht anfangen kann, das jedoch etwas Engagement und Zeit benötigt, um die volle Meisterschaft zu erlangen. Wenn Sie Zeit damit verbringen, das Framework kennenzulernen, wird es auf lange Sicht sicherlich Ihre Produktivität steigern und Ihnen letztendlich helfen, saubereren Code zu schreiben und ein besserer Entwickler zu werden.

Wenn Sie nach zusätzlichen Ressourcen suchen, ist Spring In Action ein bewährtes Übungsbuch, das viele Kernthemen des Frühlings abdeckt.

TAGS
Java SpringFramework

Kommentare


Timothy Schimandle
Bei # 2 denke ich, dass die Rückgabe eines Domänenobjekts in den meisten Fällen bevorzugt wird. Ihr benutzerdefiniertes Beispielobjekt ist eine von mehreren Klassen mit Feldern, die ausgeblendet werden sollen. Die überwiegende Mehrheit der Objekte, mit denen ich gearbeitet habe, unterliegt jedoch keiner solchen Einschränkung, und das Hinzufügen der Klasse dto ist nur unnötiger Code.
Alles in allem ein guter Artikel. Gut gemacht.

GEISTIG zu Timothy Schimandle
Ich stimme vollkommen zu. Es scheint, als ob eine unnötige zusätzliche Codeebene hinzugefügt wurde. Ich denke, dass @JsonIgnore dabei helfen wird, Felder zu ignorieren (wenn auch mit Fehlern in den Standardstrategien für die Repository-Erkennung), aber insgesamt ist dies ein großartiger Blog-Beitrag. Stolz zu stolpern ...

Arokiadoss Asirvatham
Alter, ein weiterer häufiger Anfängerfehler ist: 1) Zyklische Abhängigkeit und 2) Nichteinhaltung grundlegender Lehren für die Deklaration von Singleton-Klassen, z. B. die Verwendung einer Instanzvariablen in Beans mit Singleton-Gültigkeitsbereich.

Hlodowig
In Bezug auf Nummer 8 glaube ich, dass die Herangehensweisen an die Profile sehr unbefriedigend sind. Mal sehen:

  • Sicherheit: Einige Leute sagen: Wenn Ihr Repository öffentlich wäre, gäbe es geheime Schlüssel / Passwörter? Dies wird höchstwahrscheinlich nach diesem Ansatz der Fall sein. Es sei denn, Sie fügen .gitignore Konfigurationsdateien hinzu, dies ist jedoch keine ernsthafte Option.
  • Duplizierung: Jedes Mal, wenn ich andere Einstellungen habe, muss ich eine neue Eigenschaftendatei erstellen, was ziemlich ärgerlich ist.
  • Portabilität: Ich weiß, dass dies nur ein JVM-Argument ist, aber Null ist besser als Eins. Unendlich weniger fehleranfällig.

Ich habe versucht, eine Möglichkeit zu finden, Umgebungsvariablen in meinen Konfigurationsdateien zu verwenden, anstatt die Werte „hart zu codieren“, aber bisher ist mir dies nicht gelungen. Ich denke, ich muss mehr Nachforschungen anstellen.

Toller Artikel Tony, mach weiter so!

Übersetzung abgeschlossen: tele.gg/middle_java

Source: https://habr.com/ru/post/de465897/


All Articles