Die 10 häufigsten Spring Framework-Fehler

Veröffentlicht: 2022-03-11

Spring ist wohl eines der beliebtesten Java-Frameworks und auch ein mächtiges Tier, das es zu zähmen gilt. Während die grundlegenden Konzepte ziemlich einfach zu verstehen sind, erfordert es einige Zeit und Mühe, ein starker Spring-Entwickler zu werden.

In diesem Artikel behandeln wir einige der häufigsten Fehler in Spring, die speziell auf Webanwendungen und Spring Boot ausgerichtet sind. Wie auf der Website von Spring Boot angegeben, vertritt Spring Boot eine eigenwillige Ansicht darüber, wie produktionsreife Anwendungen erstellt werden sollten. Daher wird dieser Artikel versuchen, diese Ansicht nachzuahmen und einen Überblick über einige Tipps zu geben, die sich gut in die Standardentwicklung von Spring Boot-Webanwendungen integrieren lassen.

Für den Fall, dass Sie mit Spring Boot nicht sehr vertraut sind, aber dennoch einige der genannten Dinge ausprobieren möchten, habe ich ein GitHub-Repository erstellt, das diesen Artikel begleitet. Wenn Sie sich an irgendeinem Punkt während des Artikels verloren fühlen, würde ich empfehlen, das Repository zu klonen und mit dem Code auf Ihrem lokalen Computer herumzuspielen.

Häufiger Fehler Nr. 1: Zu niedriger Pegel

Wir verstehen uns mit diesem häufigen Fehler, weil das „nicht hier erfunden“-Syndrom in der Welt der Softwareentwicklung weit verbreitet ist. Zu den Symptomen gehört das regelmäßige Umschreiben von Teilen häufig verwendeten Codes, und viele Entwickler scheinen darunter zu leiden.

Während das Verständnis der Interna einer bestimmten Bibliothek und ihrer Implementierung zum größten Teil gut und notwendig ist (und auch ein großartiger Lernprozess sein kann), ist es Ihrer Entwicklung als Softwareentwickler abträglich, sich ständig mit der gleichen Low-Level-Implementierung auseinanderzusetzen Einzelheiten. Es gibt einen Grund, warum Abstraktionen und Frameworks wie Spring existieren, die Sie gerade von sich wiederholender manueller Arbeit trennen und es Ihnen ermöglichen, sich auf Details auf höherer Ebene zu konzentrieren – Ihre Domänenobjekte und Geschäftslogik.

Nehmen Sie also die Abstraktionen an - wenn Sie das nächste Mal mit einem bestimmten Problem konfrontiert werden, führen Sie zuerst eine schnelle Suche durch und stellen Sie fest, ob eine Bibliothek, die dieses Problem löst, bereits in Spring integriert ist; Heutzutage stehen die Chancen gut, dass Sie eine passende bestehende Lösung finden. Als Beispiel für eine nützliche Bibliothek verwende ich Anmerkungen zum Projekt Lombok in Beispielen für den Rest dieses Artikels. Lombok wird als Boilerplate-Code-Generator verwendet und der faule Entwickler in Ihnen sollte hoffentlich kein Problem haben, sich mit der Bibliothek vertraut zu machen. 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, kompiliert der obige Code zu:

 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 höchstwahrscheinlich ein Plugin installieren müssen, falls Sie beabsichtigen, Lombok mit Ihrer IDE zu verwenden. Die Version des Plug-ins von IntelliJ IDEA finden Sie hier.

Häufiger Fehler Nr. 2: „Undichte“ Interna

Die Offenlegung Ihrer internen Struktur ist niemals eine gute Idee, da dies zu Inflexibilität im Servicedesign führt und folglich schlechte Codierungspraktiken fördert. „Leaking“ Internals manifestieren sich, indem die Datenbankstruktur von bestimmten API-Endpunkten aus zugänglich gemacht wird. Nehmen wir als Beispiel an, 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 existiert ein Endpunkt, der auf die TopTalentEntity -Daten zugreifen muss. So verlockend es auch sein mag, TopTalentEntity Instanzen zurückzugeben, eine flexiblere Lösung wäre das Erstellen einer neuen Klasse, um die TopTalentEntity -Daten auf dem API-Endpunkt darzustellen:

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

Auf diese Weise erfordern Änderungen an Ihrem Datenbank-Backend keine zusätzlichen Änderungen in der Serviceschicht. Überlegen Sie, was passieren würde, wenn Sie TopTalentEntity ein „Passwort“-Feld hinzufügen würden, um die Passwort-Hashes Ihrer Benutzer in der Datenbank zu speichern – ohne einen Konnektor wie TopTalentData würde das Vergessen, das Service-Front-End zu ändern, versehentlich einige sehr unerwünschte geheime Informationen preisgeben !

Häufiger Fehler Nr. 3: Mangelnde Trennung von Bedenken

Wenn Ihre Anwendung wächst, wird die Code-Organisation zunehmend zu einer immer wichtigeren Angelegenheit. Ironischerweise beginnen die meisten guten Software-Engineering-Prinzipien mit der Zeit zusammenzubrechen – insbesondere in Fällen, in denen dem Design der Anwendungsarchitektur nicht viel Aufmerksamkeit geschenkt wurde. Einer der häufigsten Fehler, dem Entwickler dann erliegen, ist das Mischen von Code-Bedenken, und das ist extrem einfach!

Was normalerweise die Trennung von Anliegen durchbricht, ist einfach, neue Funktionalität in bestehende Klassen zu „dumping“. Dies ist natürlich eine großartige kurzfristige Lösung (für den Anfang erfordert es weniger Tipparbeit), aber später wird es unweigerlich zu einem Problem, 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()); } }

Auf den ersten Blick mag es nicht so aussehen, als wäre an diesem Codeabschnitt etwas Besonderes falsch; es stellt eine Liste von TopTalentData , die von TopTalentEntity Instanzen abgerufen werden. Bei genauerer Betrachtung können wir jedoch erkennen, dass TopTalentController hier tatsächlich einige Dinge ausführt; Das heißt, es ordnet Anforderungen einem bestimmten Endpunkt zu, ruft Daten aus einem Repository ab und konvertiert von TopTalentRepository empfangene Entitäten in ein anderes Format. Eine „sauberere“ Lösung wäre, diese Bedenken in ihre eigenen Klassen zu unterteilen. Es könnte etwa 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 sie es uns ermöglicht, festzustellen, wo sich die Funktionalität befindet, indem wir einfach den Klassennamen untersuchen. Darüber hinaus können wir beim Testen jede der Klassen bei Bedarf einfach durch eine Scheinimplementierung ersetzen.

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

Das Thema Konsistenz ist nicht unbedingt exklusiv für Spring (oder Java, was das betrifft), ist aber dennoch ein wichtiger Aspekt, den es bei der Arbeit an Spring-Projekten zu berücksichtigen gilt. Während der Codierungsstil zur Debatte stehen kann (und normalerweise eine Frage der Vereinbarung innerhalb eines Teams oder innerhalb eines gesamten Unternehmens ist), erweist sich ein gemeinsamer Standard als große Produktivitätshilfe. Dies gilt insbesondere für Mehrpersonenteams; Die Konsistenz ermöglicht eine Übergabe, ohne dass viele Ressourcen für das Händchenhalten aufgewendet werden oder langwierige Erklärungen bezüglich der Verantwortlichkeiten verschiedener Klassen bereitgestellt werden

Stellen Sie sich ein Spring-Projekt mit seinen verschiedenen Konfigurationsdateien, Diensten und Controllern vor. Die semantische Konsistenz bei der Benennung schafft eine leicht durchsuchbare Struktur, in der sich jeder neue Entwickler im Code zurechtfinden kann; Anhängen von Config-Suffixen an Ihre Konfigurationsklassen, Service-Suffixe an Ihre Dienste und Controller-Suffixe an Ihre Controller, zum Beispiel.

Eng verbunden mit dem Thema Konsistenz verdient die serverseitige Fehlerbehandlung einen besonderen Schwerpunkt. Wenn Sie jemals Ausnahmeantworten von einer schlecht geschriebenen API verarbeiten mussten, wissen Sie wahrscheinlich warum – es kann mühsam sein, Ausnahmen richtig zu analysieren, und noch schmerzhafter, den Grund dafür zu ermitteln, warum diese Ausnahmen überhaupt aufgetreten sind.

Als API-Entwickler möchten Sie idealerweise alle benutzerseitigen Endpunkte abdecken und sie in ein gemeinsames Fehlerformat übersetzen. Dies bedeutet normalerweise, dass Sie einen generischen Fehlercode und eine Beschreibung haben und nicht die Ausweichlösung, a) eine „500 Internal Server Error“-Meldung zurückzugeben oder b) einfach den Stack-Trace an den Benutzer zu senden (was eigentlich um jeden Preis vermieden werden sollte). da es Ihre Interna offenlegt und zusätzlich dazu, dass es schwierig ist, clientseitig zu handhaben).

Ein Beispiel für ein gängiges Fehlerantwortformat könnte sein:

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

Etwas Ähnliches findet sich häufig in den meisten gängigen APIs und funktioniert tendenziell gut, da es einfach und systematisch dokumentiert werden kann. Das Übersetzen von Ausnahmen in dieses Format kann durch Bereitstellen der Annotation @ExceptionHandler für eine Methode erfolgen (ein Beispiel für eine Annotation finden Sie in Common Mistake #6).

Häufiger Fehler Nr. 5: Unsachgemäßer Umgang mit Multithreading

Unabhängig davon, ob es in Desktop- oder Web-Apps, Spring oder nicht Spring auftritt, kann Multithreading eine harte Nuss sein, die es zu knacken gilt. Probleme, die durch die parallele Ausführung von Programmen verursacht werden, sind nervenaufreibend schwer zu fassen und oft extrem schwierig zu debuggen - aufgrund der Art des Problems werden Sie es wahrscheinlich tun, sobald Sie feststellen, dass Sie es mit einem Problem der parallelen Ausführung zu tun haben müssen Sie komplett auf den Debugger verzichten und Ihren Code „von Hand“ untersuchen, bis Sie die eigentliche Fehlerursache gefunden haben. Leider gibt es keine Standardlösung zur Lösung solcher Probleme; Abhängig von Ihrem speziellen Fall müssen Sie die Situation beurteilen und das Problem dann aus dem Blickwinkel angehen, den Sie für den besten halten.

Idealerweise möchten Sie natürlich Multithreading-Bugs ganz vermeiden. Auch hier gibt es keinen einheitlichen Ansatz dafür, aber hier sind einige praktische Überlegungen zum Debuggen und Verhindern von Multithreading-Fehlern:

Vermeiden Sie den globalen Staat

Denken Sie zunächst immer an das Problem des „globalen Zustands“. Wenn Sie eine Multithread-Anwendung erstellen, sollte absolut alles, was global änderbar ist, genau überwacht und nach Möglichkeit vollständig entfernt werden. Wenn es einen Grund gibt, warum die globale Variable änderbar bleiben muss, setzen Sie die Synchronisierung sorgfältig ein und verfolgen Sie die Leistung Ihrer Anwendung, um sicherzustellen, dass sie aufgrund der neu eingeführten Wartezeiten nicht träge ist.

Veränderlichkeit vermeiden

Dieser stammt direkt aus der funktionalen Programmierung und besagt, angepasst an OOP, dass Klassenveränderlichkeit und Zustandsänderung vermieden werden sollten. Kurz gesagt bedeutet dies, auf Setter-Methoden zu verzichten und private final-Felder für alle Ihre Modellklassen zu haben. Die einzige Zeit, in der ihre Werte mutiert werden, ist während des Baus. Auf diese Weise können Sie sicher sein, dass keine Konkurrenzprobleme auftreten und dass der Zugriff auf Objekteigenschaften jederzeit die richtigen Werte liefert.

Wichtige Daten protokollieren

Bewerten Sie, wo Ihre Anwendung Probleme verursachen könnte, und protokollieren Sie präventiv alle wichtigen Daten. Wenn ein Fehler auftritt, werden Sie dankbar sein, Informationen darüber zu haben, welche Anfragen eingegangen sind, und einen besseren Einblick in das Fehlverhalten Ihrer Anwendung zu haben. Auch hier muss beachtet werden, dass die Protokollierung zusätzliche Datei-I/Os einführt und daher nicht missbraucht werden sollte, da sie die Leistung Ihrer Anwendung stark beeinträchtigen kann.

Bestehende Implementierungen wiederverwenden

Wann immer Sie Ihre eigenen Threads erstellen müssen (z. B. um asynchrone Anfragen an verschiedene Dienste zu stellen), verwenden Sie vorhandene sichere Implementierungen wieder, anstatt Ihre eigenen Lösungen zu erstellen. Dies bedeutet zum größten Teil die Verwendung von ExecutorServices und CompletableFutures im funktionalen Stil von Java 8 für die Thread-Erstellung. Spring ermöglicht auch die asynchrone Verarbeitung von Anfragen über die Klasse DeferredResult.

Häufiger Fehler Nr. 6: Keine annotationsbasierte Validierung

Stellen wir uns vor, unser TopTalent-Service von früher benötigt einen Endpunkt zum Hinzufügen neuer Top-Talente. Nehmen wir außerdem an, dass jeder neue Name aus einem wirklich triftigen Grund genau 10 Zeichen lang sein muss. Eine Möglichkeit, dies zu tun, könnte die folgende 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); }

Allerdings ist das obige (zusätzlich dazu, dass es schlecht konstruiert ist) keine wirklich "saubere" Lösung. Wir prü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 eine Ausnahme aus, wenn die Daten ungültig sind .

Dies kann viel sauberer ausgeführt werden, indem der Hibernate-Validator mit Spring verwendet wird. Lassen Sie uns zuerst die Methode addTopTalent , 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 validieren möchten:

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

Jetzt fängt Spring die Anfrage ab und validiert sie, bevor die Methode aufgerufen wird – es müssen keine zusätzlichen manuellen Tests durchgeführt werden.

Eine andere Möglichkeit, dasselbe zu erreichen, wäre, unsere eigenen Anmerkungen zu erstellen. Obwohl Sie normalerweise nur benutzerdefinierte Anmerkungen verwenden, wenn Ihre Anforderungen den integrierten Einschränkungssatz von Hibernate überschreiten, nehmen wir für dieses Beispiel an, dass @Length nicht existiert. Sie würden einen Validator erstellen, der die Zeichenfolgenlänge überprüft, indem Sie zwei zusätzliche Klassen erstellen, eine zum Validieren und eine andere zum Annotieren 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 die Best Practices zur Trennung von Bedenken erfordern, dass Sie eine Eigenschaft als gültig markieren, wenn sie null ist ( s == null innerhalb der isValid Methode), und dann eine @NotNull Anmerkung verwenden, wenn dies eine zusätzliche Anforderung für die ist Eigentum:

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

Häufiger Fehler Nr. 7: Verwenden Sie (immer noch) eine XML-basierte Konfiguration

Während XML für frühere Versionen von Spring eine Notwendigkeit war, kann heutzutage der größte Teil der Konfiguration ausschließlich über Java-Code / Annotationen erfolgen; XML-Konfigurationen stellen nur zusätzlichen und unnötigen Boilerplate-Code dar.

Dieser Artikel (sowie das dazugehörige GitHub-Repository) verwendet Annotationen zum Konfigurieren von Spring und Spring weiß, welche Beans es verdrahten sollte, da das Root-Paket mit einer @SpringBootApplication Composite-Annotation wie folgt kommentiert wurde:

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

Die zusammengesetzte Annotation (Sie können mehr darüber in der Spring-Dokumentation erfahren, gibt Spring lediglich einen Hinweis darauf, welche Pakete gescannt werden sollten, um Beans abzurufen. In unserem konkreten Fall bedeutet dies, dass das folgende unter dem obersten (co.kukurin) Paket verwendet wird zur Verkabelung:

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

Wenn wir zusätzliche mit @Configuration annotierte Klassen hätten, würden sie auch auf Java-basierte Konfiguration überprüft.

Häufiger Fehler Nr. 8: Profile vergessen

Ein häufig auftretendes Problem bei der Serverentwicklung ist die Unterscheidung zwischen verschiedenen Konfigurationstypen, normalerweise zwischen Ihren Produktions- und Entwicklungskonfigurationen. Anstatt verschiedene Konfigurationseinträge jedes Mal manuell zu ersetzen, wenn Sie Ihre Anwendung vom Testen zum Bereitstellen Ihrer Anwendung wechseln, wäre ein effizienterer Weg, Profile zu verwenden.

Betrachten Sie den Fall, in dem Sie eine In-Memory-Datenbank für die lokale Entwicklung mit einer MySQL-Datenbank in der Produktion verwenden. Dies würde im Wesentlichen bedeuten, dass Sie eine andere URL und (hoffentlich) unterschiedliche Anmeldeinformationen für den Zugriff auf beide verwenden. Mal sehen, wie dies mit zwei verschiedenen Konfigurationsdateien geschehen könnte:

application.yaml-Datei

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

application-dev.yaml-Datei

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

Vermutlich möchten Sie nicht versehentlich irgendwelche Aktionen an Ihrer Produktionsdatenbank ausführen, während Sie am Code basteln, daher ist es sinnvoll, das Standardprofil auf dev. Auf dem Server können Sie dann das Konfigurationsprofil manuell überschreiben, indem Sie der JVM einen Parameter -Dspring.profiles.active=prod . Alternativ können Sie auch die Umgebungsvariable Ihres Betriebssystems auf das gewünschte Standardprofil setzen.

Häufiger Fehler Nr. 9: Die Abhängigkeitsinjektion nicht annehmen

Die richtige Verwendung von Dependency Injection mit Spring bedeutet, dass Sie alle Ihre Objekte miteinander verbinden können, indem Sie alle gewünschten Konfigurationsklassen scannen. Dies erweist sich als nützlich, um Beziehungen zu entkoppeln, und erleichtert auch das Testen erheblich. Anstelle von engen Kopplungsklassen, indem Sie so etwas tun:

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

Wir lassen Spring die Verkabelung für uns übernehmen:

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

Misko Heverys Google-Vortrag erklärt das „Warum“ der Abhängigkeitsinjektion ausführlich, also lassen Sie uns stattdessen sehen, wie sie in der Praxis verwendet wird. Im Abschnitt über die Trennung von Anliegen (Häufige Fehler Nr. 3) haben wir eine Service- und eine Controller-Klasse erstellt. Angenommen, wir möchten den Controller unter der Annahme testen, dass sich TopTalentService korrekt verhält. Wir können ein Scheinobjekt anstelle der eigentlichen Dienstimplementierung einfügen, indem wir 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; } }

Dann können wir das Scheinobjekt einfügen, indem wir Spring anweisen, SampleUnitTestConfig als Konfigurationslieferant zu verwenden:

 @ContextConfiguration(classes = { SampleUnitTestConfig.class })

Dadurch können wir dann die Kontextkonfiguration verwenden, um die benutzerdefinierte Bean in einen Komponententest einzufügen.

Häufiger Fehler Nr. 10: Fehlende Tests oder unsachgemäße Tests

Obwohl uns die Idee des Unit-Testing schon seit langer Zeit begleitet, scheinen viele Entwickler dies entweder zu „vergessen“ (insbesondere wenn es nicht erforderlich ist) oder es einfach nachträglich hinzuzufügen. Dies ist natürlich nicht wünschenswert, da Tests nicht nur die Korrektheit Ihres Codes überprüfen sollen, sondern auch als Dokumentation dienen sollen, wie sich die Anwendung in verschiedenen Situationen verhalten soll.

Beim Testen von Webdiensten führen Sie selten ausschließlich „reine“ Komponententests durch, da die Kommunikation über HTTP normalerweise erfordert, dass Sie Springs DispatcherServlet aufrufen und sehen, was passiert, wenn eine tatsächliche HttpServletRequest empfangen wird (was es zu einem Integrationstest macht, der sich mit Validierung und Serialisierung befasst , etc). REST Assured, eine Java-DSL zum einfachen Testen von REST-Diensten, zusätzlich zu MockMVC, hat sich als sehr elegante Lösung erwiesen. Betrachten Sie das folgende Code-Snippet 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 eine Scheinimplementierung von TopTalentService mit TopTalentController während alle anderen Klassen mithilfe der Standardkonfiguration verknüpft werden, die aus dem Scannen von Paketen abgeleitet wird, die im Paket der Anwendungsklasse verwurzelt sind. RestAssuredMockMvc wird einfach verwendet, um eine einfache Umgebung einzurichten und eine GET -Anforderung an den /toptal/get Endpunkt zu senden.

Frühlingsmeister werden

Spring ist ein leistungsstarkes Framework, mit dem Sie leicht beginnen können, das jedoch einige Hingabe und Zeit erfordert, um es vollständig zu beherrschen. Sich die Zeit zu nehmen, sich mit dem Framework vertraut zu machen, wird Ihre Produktivität auf lange Sicht definitiv verbessern und Ihnen letztendlich dabei helfen, saubereren Code zu schreiben und ein besserer Entwickler zu werden.

Wenn Sie nach weiteren Ressourcen suchen, ist Spring In Action ein gutes praktisches Buch, das viele Kernthemen des Frühlings abdeckt.