Einstieg in Spring Boot, Teil 19 REST-konforme Webservices mit Spring Boot

Autor / Redakteur: Dr. Dirk Koller / Stephan Augsten

Eine REST-API gehört für viele Webprojekte inzwischen zum guten Ton. Mit ihr lassen sich beispielsweise Twitter-Tweets abfragen oder Kryptowährungen automatisiert handeln. Spring Boot bietet dafür eine Menge Unterstützung an.

Firmen zum Thema

Bei der Verwendung von REST stehen Ressourcen wie beispielsweise Datensätze im Mittelpunkt.
Bei der Verwendung von REST stehen Ressourcen wie beispielsweise Datensätze im Mittelpunkt.
(Bild: Spring.io / Montage)

Die Abkürzung REST steht für REpresentational State Transfer und bezeichnet einen Typ von Webservices, bei dem für den Datenaustausch die HTTP-eigenen Methoden wie GET, POST, PUT und DELETE verwendet werden. Das bevorzugte Datenaustauschformat von REST ist JSON, andere Formate sind aber möglich.

Im Vergleich zu den aufgeblähten SOAP-Webservices sind REST-Services schnell und platzsparend. Im Mittelpunkt stehen bei REST Ressourcen, die mit Hilfe eines URI (Ressource Uniform Identifier) angesprochen werden. Solche Ressourcen können beispielsweise Personen in der Datenbank sein, wie sie in diesem Beitrag verwendet werden.

„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Als Einstieg dient ein einfaches Spring Boot-Projekt mit den Dependencies Spring Web, Spring Data JPA, Lombok und H2 Database. Zunächst wird eine Klasse Person angelegt und als Entität gekennzeichnet:

@Entity
@Data
public class Person {   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private Long id;
   private String firstname;
   private String lastname;
}

Die Lombok-Annotation @Data generiert unter anderen die Get- und Set-Methoden. Der Primärschlüssel für einzufügende Personen in der H2-Datenbank wird hier automatisch vergeben. Der Zugriff auf Personen erfolgt über ein einfaches JPA-Repository:

public interface PersonRepository extends JpaRepository<Person, Long> {}
RestController = Controller + ResponseBody

Als Nächstes wird eine Controller-Klasse angelegt und mit @RestController annotiert. Die Convenience-Annotation bündelt @Controller und @ResponseBody. Letzteres sorgt dafür dass die Antworten an den Client in den HTTP-Body geschrieben werden:

@RestController
public class PersonRestController {
   @Autowired
   PersonRepository personRepository;
}

Analog den Controllern für Webprojekte werden die aufzurufenden Methoden mit den gewünschten Pfaden über ein @RequestMapping verknüpft. In der moderneren und eleganteren Variante wird der Name der HTTP-Methode im Namen der Annotation untergebracht, also etwa @GetMapping oder @PostMapping. Für das Anlegen von Personen mit der HTTP-Methode POST sieht das folgendermaßen aus:

@PostMapping("/persons")
public void createPerson(@RequestBody Person person) {
   personRepository.save(person);
}

Die Methode bekommt im Body Personendaten übergeben. Dank der Annotation @RequestBody werden diese automatisch in ein Person-Objekt konvertiert, das dann in der Datenbank gespeichert wird.

Der Aufruf des Webservice, hier in cURL, erfordert die Angabe des Content-Type (beispielsweise application/json) im Header. Spring Boot erhält dadurch die Information, in welchem Format die Daten vorliegen und kann den passenden Message-Konverter einsetzen:

curl -v --request POST 'localhost:8080/persons' \
--header 'Content-Type: application/json' \
--data-raw '{
   "firstname": "Spring",
   "lastname": "Boot"
}'

Status Quo

Als Ergebnis erhält der Aufrufer lediglich den HTTP-Statuscode 200 (OK) zurück. Um zu einer aussagekräftigeren Rückmeldung zu kommen, gibt es mehrere Möglichkeiten. Die Annotation @ResponseStatus(HttpStatus.CREATED) an der createPerson()-Methode liefert im Erfolgsfall den Code 201, das ist schon besser.

„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Im Fall eines erfolgreichen Inserts wird der Aufrufer neben dem Statuscode aber auch wissen wollen, unter welchem URI sich die neu angelegte Ressource wieder finden lässt. Das lässt sich mithilfe der Klasse _ResponseEntity realisieren. Das Wrapper-Objekt nimmt die Antwort auf, gestattet aber zusätzlich die Übergabe von HTTP-Code oder Header-Feldern. Die Location wird hier mit dem ServletUriComponentsBuilder zusammengebaut, entscheidend ist aber die letzte Zeile mit der Rückgabe:

@PostMapping("/persons")public ResponseEntity<Person> createPerson(@RequestBody Person person) {   Person createdPerson = personRepository.save(person);
   URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(createdPerson.getId()).toUri();
   return ResponseEntity.created(location).build();
}

Im Header der Response findet sich dann unter dem Key Location der Pfad zur neu angelegten Ressource:

Location: http://localhost:8080/persons/2

Die Abfrage aller Personen mit der HTTP-Methode GET ist unkomplizierter:

@GetMapping("/persons")public List<Person> getPersons() {   return personRepository.findAll();
}

JSON vs. XML

Wie man der cURL-Ausgabe entnehmen kann, werden die Personen im JSON-Format an den Consumer übertragen:

$ curl --request GET 'localhost:8080/persons'[{"id":1,"firstname":"Spring","lastname":"Boot"}]

Das ist der Standardfall. Immer wenn die Jackson2-Bibliothek im Classpath vorhanden ist, wird defaultmäßig eine Konvertierung der Pojos ins JSON-Format durchgeführt. Da Jackson in spring-boot-starter-web eingebunden wird, ist das hier gegeben. Ein anderes Datenaustauschformat lässt sich aber für Anfrage und Antwort durch die HTTP-Header Content-Type und Accept definieren. Mit Accept: application/xml erfolgt die Antwort in XML, dafür muss sich aber die folgende Dependency im POM befinden:

<dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
$ curl --request GET 'localhost:8080/persons' --header 'Accept: application/xml'<List><item><id>1</id><firstname>Spring</firstname><lastname>Boot</lastname></item></List>

Wir bleiben hier im Folgenden aber bei dem schlankeren JSON-Format. Für den Abruf einer einzelnen Person wird der Primärschlüssel „id“ als Pfadvariable angehängt:

@GetMapping("/persons/{id}")public Person getPersonById(@PathVariable Long id) {   return personRepository.findById(id).get();
}
curl --request GET 'localhost:8080/persons/1'

Das Löschen mit DELETE erfolgt analog:

@DeleteMapping("/persons/{id}")public void deletePersonById(@PathVariable Long id) {   personRepository.deleteById(id);
}
curl --request DELETE 'localhost:8080/persons/1'

Interessant ist das Update der kompletten Person mit PUT:

@PutMapping("/persons")public void updatePerson(@RequestBody Person person) {   personRepository.save(person);
}

Wie beim Create wird hier die Person in JSON-Form im Body übergeben, diesmal allerdings inklusive der id:

curl -v --request PUT 'localhost:8080/persons' \
--header 'Content-Type: application/json' \
--data-raw '{
   "id": 1,
   "firstname": "Spring",
   "lastname": "Boot Updated"
}'

Mit PATCH lassen sich auch einzelne Attribute einer Entität updaten, wir wollen es aber für diesen Einstieg beim Update der kompletten Entität belassen.

„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Die hier vorgestellten Zugriffe entsprechen dem Level 2 des dreistufigen Richardson Maturity Modells: Die Ressource wurde richtig angesprochen und das korrekte HTTP-Verb verwendet. In der Praxis ist das nicht immer zu erreichen, weil sich gerne Aktionen, sprich Remote Procedure Calls unter die Ressourcen mogeln (migratePerson, convertPerson usw.). Die sind in REST eigentlich nicht vorgesehen aber Teil fast jeder Projektrealität.

Über die Ansätze dieses Problem zu lösen, existieren zahlreiche Beiträge und Diskussionen im Netz. Im nächsten Beitrag der Reihe entwickeln wir einen Client für den Zugriff auf den Webservice, der anstelle der cURL-Aufrufe verwendet wird.

(ID:47554083)

Über den Autor