Einfaches REST API mit Spring Boot

Erstellen einer einfachen REST API mit Spring Boot

Avatar von Martin Runge

Dieser Artikel richtet sich vorwiegend an Leser, die das Spring Framework noch nicht kennen, und soll ein kleines Intro in dieses geben.

Im Folgenden wollen wir beispielhaft einen einfachen Spring Boot Service erstellen, der ein REST API für Wetterdaten zur Verfügung stellt.

Spring vs. Spring Boot

Das Spring-Framework ist ein Open Source Java-Framework, das im Kern einen Dependency-Injection-Mechanismus (DI), aber auch viele weitere Funktionalitäten bereitstellt.

Spring Boot ist ein Aufsatz auf Spring, der das Erstellen von Applikationen erheblich erleichtert. So wird der Konfigurationsaufwand sowie das Verwalten von Abhängigkeiten vereinfacht. Hierfür gibt es verschiedene sogenannte Starter Module. Man kann sie einfach in sein Projekt einbinden und bekommt dann Zugriff auf bestimmte Funktionalitäten.

Das ganze Framework wirkt am Anfang etwas magisch, es ist wie ein kleines Zauberbuch und jede der Annotationen einer der Zaubersprüche. Das liegt daran, dass es viele Funktionalitäten bereits mitbringt, die man nur per Annotation aktivieren oder konfigurieren muss. Das Ganze ist aber weit weniger mystisch, als es jetzt vielleicht klingt. Mit der Zeit wird man mit den wichtigsten Annotationen vertraut, ein paar davon lernen wir in diesem Artikel kennen, und schreibt eventuell sogar eigene.

Vorraussetzungen

Um den Codebeispielen zu folgen, sollte folgendes installiert sein:

  • Java 8
  • Maven
  • (empfohlen) eine Java IDE, wie z. B. IntelliJ

Kurz im Terminal überprüfen, ob alles korrekt installiert ist:

java -version
mvn -v

Dann kann es auch schon losgehen.

Bootstrappen der Applikation mit Spring Initializr

Spring Initializr ist ein einfaches Web UI, mit dem wir unser Spring-Projekt generieren können. Die Oberfläche ist relativ selbsterklärend. Die einzige Einstellung die wir hier vornehmen ist, dass wir unter Dependencies „Web“ hinzufügen.

Anschließend können wir das Projekt generieren und erhalten eine ZIP-Datei; darin enthalten ist das gescaffoldete Projekt. Interessant sind hier erst ein mal die pom.xml und die DemoApplication.java.

Die pom.xml enthält die Konfiguration für Maven, ein Build-Management-Tool. Hier wird etwa festgelegt, dass wir die Konfiguration aus spring-boot-starter-parent erben. Außerdem werden zusätzliche Abhängigkeiten definiert, wie das von uns ausgewählte spring-boot-starter-web.

Wenn wir uns die DemoApplikation-Klassen anschauen, fällt die Annotation @SpringBootApplication auf. Wie eingangs bereits beschrieben, wird das Verhalten des Frameworks hauptsächlich über solche Annotationen gesteuert. @SpringBootApplication ist eine Abkürzung für die folgenden drei Sachen:

  • @EnableAutoConfiguration: Aktiviert das Auto-Configuration-Feature
  • @ComponentScan: Aktiviert den Component Scan
  • @Configuration: Erlaubt es, Beans zu registrieren sowie andere Konfigurationen zu importieren

Aha – und was heißt das nun?

Dazu sollten wir zunächst klären, was denn eine Bean eigentlich ist. Als Bean bezeichnet man in Spring all die Objekte, die vom Spring Inversion of Control (IoC) Container erstellt und verwaltet werden. Sie bilden gewissermaßen das Rückgrat der gesamten Applikation. Welche Arten von Beans der Container zur Verfügung stellen kann, wird über Metadaten konfiguriert. Das kann ganz old-school via XML-Datei geschehen oder, wie heute eher üblich, durch bestimmte Annotationen. Und genau dazu dienen auch die oben genannten Annotationen.

@EnableAutoConfiguration bewirkt, dass Spring für uns automatisch Beans konfiguriert, die wir vermutlich brauchen könnten – beispielsweise eine TomcatServletWebServerFactory, weil wir spring-boot-starter-web als Abhängigkeit hinzugefügt haben.

@ComponentScan bewirkt, dass beim Start der Classpath nach Klassen gescannt wird, die mit @Component oder einer Spezialisierung davon (@Service, @Repository, @Controller) annotiert sind. Für die gefundenen Klassen werden dann automatisch Beans konfiguriert.

@Configuration kann für beliebige Klassen verwendet werden und erlaubt uns, Beans explizit zu konfigurieren. Hierzu werden einfach Methoden in der Klasse mit @Bean annotiert, deren Rückgabewert ist dann als Bean über die DI verfügbar.

Erstellen der HTTP-Endpunkte

Zunächst erstellen wir hierfür einen Controller. Dieser verarbeitet eingehende HTTP Requests und liefert eine passende Response zurück.

Ein einfacher Controller sieht z. B. so aus:

@RestController
public class WeatherDataController {

    Logger logger = LoggerFactory.getLogger(WeatherDataController.class);

    @RequestMapping(method = RequestMethod.GET, path = "/weather")
    public void retrieveWeatherData() {
        logger.info("received GET request");
    }

    @RequestMapping(method = RequestMethod.POST, path = "/weather")
    public void createWeatherData() {
        logger.info("received POST request");
    }
}

@RestController ist eine Kombination aus @Controller und @ResponseBody.

Durch die @Controller-Annotation weiß Spring, dass es sich bei dieser Klasse um einen Controller handelt, also dass HTTP Requests hierher geroutet werden können. @ResponseBody bewirkt, dass keine View, sondern stattdessen der Rückgabewert der Methode – als JSON serialisiert – zurückgeliefert wird.

Welcher Request durch welche Methode bearbeitet wird, wird mit @RequestMapping konfiguriert.

Wir können die Applikation nun starten und zum Test folgende HTTP Requests senden:

curl -X GET "localhost:8080/weather"                
curl -X POST "localhost:8080/weather"

Unsere Log-Meldungen sollten in der IDE sichtbar sein – so weit, so gut.

API Model mit Lombok

Um die Wetterdaten abbilden zu können, benötigen wir eine Klasse für das Model. Lombok ist eine Bibliothek, mit deren Hilfe sich zu erstellender Boilerplate-Code für solche Klassen erheblich reduzieren lässt.

Um Lombok zu verwenden, fügen wir zunächst eine neue Dependency in unserer pom.xml hinzu.

<dependencies>
    <!-- other dependencies... -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

Anschließend können wir eine einfache Model-Klasse wie folgt deklarieren:

import lombok.Data;

@Data
public class WeatherData {
    private int id;
    private int stationId;
    private int temperature;
    private int humidity;
    private Double latitude;
    private Double longitude;
}

Es reicht aus, die entsprechenden Felder zu deklarieren und die Klasse mit @Data zu annotieren. Durch die Annotation wird der übrige Code, der üblicherweise benötigt wird, automatisch generiert. Dazu gehören Getter und Setter, Implementierungen für equals und hashCode, sowie ein Konstruktor, in dem alle final deklarierten Felder initialisiert werden.

Von dem generierten Code sehen wir hierbei nichts, das passiert alles automatisch während des Buildprozesses; wir können allerdings bereits damit arbeiten.

Lombok lässt sich durch entsprechende Plugins in IDEs integrieren. Damit bekommt man dann beispielsweise Autovervollständigung für die generierten Methoden.

Wie viel Boilerplate man sich dadurch tatsächlich spart, verdeutlicht das Beispiel in der Dokumentation.

Repository

Nun wollen wir natürlich mit diesen Daten auch arbeiten und erstellen uns hierfür eine entsprechende Repository-Klasse. Der Einfachheit halber simulieren wir die Datenbank an dieser Stelle nur und beschränken uns auf den Create- und Read-Teil.

@Repository
public class WeatherDataRepository {

    private Map<Integer, WeatherData> data = new HashMap<>();
    private int nextId = 0;

    public void create(WeatherData record) {
        if(record.getId() == null) {
            record.setId(nextId);
            nextId++;
        }

        data.put(record.getId(), record);
    }

    public WeatherData findById(int id) {
        return data.get(id);
    }

    public Collection<WeatherData> findAll() {
        return data.values();
    }
}

Spring DI in Aktion

Durch die @Repository-Annotation erkennt Spring die Klasse als Komponente, die mit der DI verwendet werden kann. Wir können es nun also in den Controller injecten. Dazu annotieren wir den Konstruktorparameter einfach mit @Autowired, den Rest erledigt das Framework. In den Spring-Versionen ab Version 4.3 kann man die Annotation bei Konstruktorparametern auch weglassen, wenn nur ein einziger Konstruktor existiert.

@RestController
public class WeatherDataController {

    private WeatherDataRepository dataRepository;

    WeatherDataController(@Autowired WeatherDataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    @RequestMapping(method = RequestMethod.POST, path = "/weather")
    public void createWeatherData(@RequestBody WeatherData weatherData) {
        this.dataRepository.create(weatherData);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/weather")
    public Collection<WeatherData> retrieveWeatherData() {
        return this.dataRepository.findAll();
    }
}

Damit sollten unsere Endpunkte für GET und POST schon funktionieren. Wir können das Ganze mit folgenden Requests testen:

curl -X POST http://localhost:8080/weather \
  -H 'Content-Type: application/json' \
  -d '{"temperature":20,"humidity":20,"latitude":49.796538,"longitude":9.9546057}'

curl -X GET http://localhost:8080/weather

Um nun den Eintrag für eine bestimmt ID abzufragen, können wir den Controller noch um folgenden Endpunkt ergänzen.

    @RequestMapping(method = RequestMethod.GET, path = "/weather/{id}")
    public WeatherData retrieveWeatherData(@PathVariable int id) {
        return dataRepository.findById(id);
    }

Durch die @PathVariable-Annotation werden automatisch die Parameter aus der URL auf die entsprechenden Methodenparameter gemappt.

Damit sind unsere GET- und POST-Endpunkte soweit „fertig“. Um den Artikel kurz zu halten, wurde das Thema Tests an dieser Stelle bewusst ausgeklammert, ebenso das Arbeiten mit einer echten Datenbank. Bei Fragen stehe ich euch aber gerne zur Verfügung.

Avatar von Martin Runge

Kommentare

Eine Antwort zu „Erstellen einer einfachen REST API mit Spring Boot“

  1. super gutes Starttutorial.
    Ein kleiner Fehler hat sich eingeschlichen.
    Da in der Klasse WeatherDataRepository in der Methode create die Selektion
    if(result.getId() == null) ist,
    muss der Datentyp in WeatherData ein Integer sein. Es ist jedoch ein int.
    Vielen Dank

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.