Ich nehme derzeit an einer Akademie zum Java Fullstack Software Engineer teil. In den kommenden Wochen möchte ich hier meine Mitschrift, so gut es geht, aufzeichnen und mitteilen. Hier ist das, was ich vom dritten Tag in Block 7 gelernt und behalten habe:
Java-Client Programme
Heute möchten wir in Java Clients bauen, mit denen wir auf Webserver zugreifen.
Marshalling:
Die Objekte werden in Truppen verwandelt und die Daten gehen einzeln über die Brücke (IO-Stream).
Unmarshalling:
Die Truppen lösen sich auf und werden wieder Objekte.

GET und POST Parameter:

Wir öffnen unser Beispielprojekt kap0101-jaxrs, starten die app (und damit den Server) und öffnen SimplestGetExample:

Zuerst schauen wir aber mit curl und dem Browser, ob wir auf die Webseite zugreifen können. Wir sehen, dass beim curl-Befehl eine json und mit dem Browser eine XML-Datei zurück kommt:

Wenn wir das Client-Programm laufen lassen, erhalten wir einen String als gemarshalltes XML. (Genauer: string.class macht das Unmarshalling):

Jetzt wollen wir sinnvoll unmarshallen (also die empfangenen Daten sinnvoll darstellen):

Von oben nach unten: Marshalling. Von unten nach oben: Unmarshalling:

Wenn wir marshalling in XML haben wollen, müssen wir in der pom.xml die jackson-dataformat-xml Dependency hinzufügen:
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
Hier kommentieren wie es mal aus:

Wenn wir das Jackson-Framework auskommentieren, erhalten wir die Daten in json. Vergleiche auch den Log-Eintrag "Accept=" zur Version oben (Logeintrag von SimplestGetPojoExample), wo xml mit integriert ist:


Jetzt möchten wir den Header beeinflussen:

Man kann auch den Header abfragen:

GetPojowithHeaderExample:

GetWithBasicAuthExmple:

PostForObjectExample, wir schicken ein Objekt an den Server:

Ergebnis:

PostForEntity_Example: Wir schicken eine Entity an den Server:

PutSimpleExample:

PutWithExchangeExample:

SimpleDeleteExample:

DeleteObjektExample:

Zusammenfassung Restful Client:
RestFulClientKommunikation:
- RestTemplate restTemplate = new RestTemplate();Simple (Keine Headerbeeinflussung) - restTemplate.getForObject(URL,Typen.class) - restTemplate.postForObject(URL,Typen.class) - restTemplate.put(URL) - restTemplate.delete(URL) - restTemplate.....(URL,Typen.class) Komplexer (mit URL, mit HttpMethode, mit requestEntity und RückgabeObjekt auch ggf. Void.class) - restTemplate.exchange(URL, HttpMethod.XXX, requestEntity, Void.class); requestEntity -> Header + Body responseEntity -> Header + Body - restTemplate.exchange(URL, HttpMethod.POST requestEntity, String.class);
Restful Spezifikation:

Wir bauen einen KehrwertService mit WebSchnittStelle:
Zuerst bauen wir uns eine DTO-Klasse. Daraus wird vom Controller ein Objekt erstellt werden, welches er an den Webservice zu übergibt und zurück bekommt:

Wir starten die App, welche den Controller aufruft. Mit curl können wir auf den simplen Webservice zugreifen und erhalten die gewünschte Antwort, den Kehrwert von 42:

Wenn wir im curl-Kommando zusätzlich angeben, dass wir auch xml accepten, erhalten wir einen "Internen Serverfehler" "< HTTP/1.1 500":

Die Ursache ist: Es fehlt der jackson Eintrag in der pom.xml

Response-Entity ist das komplette Objekt mit Header und Body, welches der Server zurück gibt.
Um noch mehr Fehler abzufangen bauen wir im Interface eine DivNullExcpetion :

Was wir NICHT machen, ist diese Exception an die Laufzeitumgebung weiterzureichen, auch wenn es sich anbietet. Also in der Controller-Klasse darf kein "throws" gefolgt von einer internen Exception stehen:

Das gibt im Fehlerfall einen "internen Server Fehler".
Stattdessen gebe ich nach oben die ResponsStatusException weiter:

public ResponseEntity<KehrwertDTO> kehrwert(@RequestBody KehrwertDTO kehrwertDTO) throws ResponseStatusException { //kehrwertDTO.setKehrwert(kehrwertService.kehrwert(kehrwertDTO.getZahl())); double zahl = kehrwertDTO.getZahl(); try { double erg = kehrwertService.kehrwert(zahl); kehrwertDTO.setKehrwert(erg); return ResponseEntity.status(201).body(kehrwertDTO); } catch (DivZeroException e) { throw new ResponseStatusException( HttpStatus.BAD_REQUEST, e.getMessage()); } }
In application.properties tragen wir noch ein:
server.error.include-message=always
So sieht der fertige KehrwertController aus:

And here are the results:

Übung ueb04-kreisservice-dto-errorhandling
ueb04-kreisservice-dto-errorhandling
- KreisDTO
radius
flaeche
umfang - Marshalling Unmarshalling
- XML - JSON
- Error-Handling
- Radius <= 0
Hier unser funktionierendes Ergebnis:

pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>de.firma</groupId> <artifactId>ueb03-kreisservice</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ueb03-kreisservice</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
Application.properties:
server.port=8099 server.error.include-message=always
Ueb04KreisRechnerServiceApplication.java:
package de.firma;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Ueb04KreisserviceApplication {public static void main(String[] args) { SpringApplication.run(Ueb04KreisserviceApplication.class, args); }
}
Das @SpringBootApplication sorgt dafür, das die Spring -Annotationen gesucht werden. Da sich der KreisRechnerController mit seinen Annotationen am Spring registriert, wird er gefunden und seine Aktionen ausgeführt.
KreisRechnerController.java:
package de.firma.controller;import de.firma.dto.KreisRechnerDTO;
import de.firma.services.KreisRechner;
import de.firma.services.NumberToSmallException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/kreisrechner")public class KreisRechnerController {
@Autowired
KreisRechner kreisRechner;/* @GetMapping("/flaeche") //http://localhost:8099/kreisrechner/flaeche?zahl=42 public double flaeche (@RequestParam double zahl){ return kreisRechner.flaeche(zahl); } @GetMapping("/umfang") //http://localhost:8099/kreisrechner/umfang?zahl=42 public double umfang (@RequestParam double zahl){ return kreisRechner.umfang(zahl); } */ //curl -X -v POST http://localhost:8099/kreisrechner/radius -d "{\"radius\":\"5\"}" -H "Content-Type:application/json" //curl -X -v POST http://localhost:8099/kreisrechner/radius -d "{\"radius\":\"5\"}" -H "Content-Type:application/xml" @RequestMapping( value = "/radius", method = RequestMethod.POST, consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE} ) public ResponseEntity<KreisRechnerDTO> kreisberechnung(@RequestBody KreisRechnerDTO kreisRechnerDTO) throws ResponseStatusException { double radius = kreisRechnerDTO.getRadius(); double flaeche = 0; try { flaeche = kreisRechner.flaeche(radius); } catch (NumberToSmallException e) { throw new ResponseStatusException( HttpStatus.BAD_REQUEST, e.getMessage() ); } double umfang = 0; try { umfang = kreisRechner.umfang(radius); } catch (NumberToSmallException e) { throw new ResponseStatusException( HttpStatus.BAD_REQUEST, e.getMessage() ); } kreisRechnerDTO.setFlaeche(flaeche); kreisRechnerDTO.setUmfang(umfang); return ResponseEntity.status(201).body(kreisRechnerDTO); }
}
Interface KreisRechner.java:
package de.firma.services;public interface KreisRechner{
public double flaeche (double radius) throws NumberToSmallException;public double umfang (double radius) throws NumberToSmallException;
}
KreisRechnerImpl.java
package de.firma.services;import org.springframework.stereotype.Service;
@Service
public class KreisRechnerImpl implements KreisRechner{@Override public double flaeche (double radius) throws NumberToSmallException{ if (radius <= 0){ throw new NumberToSmallException ("Bitte gib einen Wert größer Null ein!"); } return Math.PI*Math.pow(radius,2); } @Override public double umfang (double radius)throws NumberToSmallException{ if (radius <= 0){ throw new NumberToSmallException ("Bitte gib einen Wert größer Null ein!"); } return 2*Math.PI*radius; }
}
NumberToSmallException.java:
package de.firma.services;public class NumberToSmallException extends Exception {
public NumberToSmallException(String message) {
super(message);
}
}
KreisRechnerDTO.java:
package de.firma.dto;public class KreisRechnerDTO {
private double radius;
private double flaeche;
private double umfang;public KreisRechnerDTO(double radius, double flaeche, double umfang) { this.radius = radius; this.flaeche = flaeche; this.umfang = umfang; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public double getFlaeche() { return flaeche; } public void setFlaeche(double flaeche) { this.flaeche = flaeche; } public double getUmfang() { return umfang; } public void setUmfang(double umfang) { this.umfang = umfang; }
}
Disclaimer
Alles was ich mitschrieb und verstanden habe ist ohne Gewähr. Die Bilder stammen teilweise aus dem Internet und wir haben keine Urheberansprüche darauf.
Besten Dank an unseren sehr empfehlenswerten
Trainer: Hans-Joachim Blanke blanke@4point.de

In den nächsten Tagen und Wochen geht’s weiter, so: stay tuned!
Gruß, Achim Mertens