RestTemplate Introductie

In de blogpost van vandaag nemen we een kijkje naar Springs bekende rest client – de RestTemplate. De RestTemplate is de centrale class binnen het Spring framework voor het uitvoeren van synchrone HTTP requests aan de client kant.

Net als Spring JdbcTemplate is ook RestTemplate een high-level API, die op zijn beurt weer gebaseerd is op een HTTP client. Standaard wordt de class java.net.HttpURLConnection uit de Java SDK gebruikt in RestTemplate. Het Spring Framework maakt het echter mogelijk om eenvoudig over te schakelen naar een andere HTTP client API. Hoe je dit doet staat beschreven in een andere blog post.

De meesten van ons hebben vast wel ervaring met HttpURLConnection of een andere HTTP client API. Bij het gebruik ervan hebben we gemerkt dat voor elk verzoek steeds weer dezelfde boilerplate-code wordt gegenereerd:

  • Een URL object maken en de verbinding openen
  • Het HTTP verzoek configureren
  • Het HTTP verzoek uitvoeren
  • Interpretatie van het HTTP antwoord
  • Het HTTP antwoord omzetten in een Java object
  • Het HTTP antwoord omzetten in een Java object
  • Het HTTP antwoord omzetten in een Java object
  • . de HTTP response in een Java object

  • Exception handling

Bij gebruik van RestTemplate gebeuren al deze dingen op de achtergrond en hoeft de ontwikkelaar zich er niet mee bezig te houden.

Beginnend met Spring 5 biedt de non-blocking en reactive WebClient een modern alternatief voor RestTemplateWebClient biedt ondersteuning voor zowel synchrone als asynchrone HTTP requests en streaming scenario’s. Daarom zal RestTemplate in een toekomstige versie van het Spring Framework als deprecated worden gemarkeerd en geen nieuwe functionaliteiten bevatten.

Project Setup

Voordat we echt van start gaan, wil ik de volgende punten van de project setup nog even onder de loep nemen:

  • Gebruikte afhankelijkheden
  • POJO class Employee
  • REST web service voor testen

2.1 Gebruikte Dependencies

Voor het RestTemplate demo project hebben we de volgende dependencies nodig in onze Spring Boot gebaseerde applicatie:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency></dependencies>

De Dependency spring-boot-starter-web is een starter voor het bouwen van web applicaties. Deze dependency bevat de class RestTemplate, de mogelijkheid om REST web services te publiceren en vele andere web-gerelateerde zaken.

Als HTTP client API gebruiken we Apache HttpComponents voor de volgende voorbeelden. Lombok genereert o.a. Getter en Setter en helpt ons om herhalende code te vermijden.

2.2 POJO-klasse Werknemer

Onze POJO-klasse, die ons door het voorbeeld zal begeleiden, ziet er als volgt uit:

@Data@NoArgsConstructor@AllArgsConstructorpublic class Employee { private long id; private String firstName; private String lastName; private long yearlyIncome;}

Dankzij Lombok en @Data Annotatie krijgen we de getter en setter methodes er gratis bij. Verder genereert @Data de volgende methodes automatisch:

  • equals()
  • hashCode()
  • toString()
  • Constructor met alle velden die zijn geannoteerd met @NonNull

@NoArgsConstructor genereert een constructor zonder parameters en @AllArgsConstructor genereert een constructor met alle parameters.

2.3 REST Web Service For Testing

Om de volgende voorbeelden beter te begrijpen, bevat het demo project een zeer handige REST web service. De bijbehorende RestController is guru.springframework.resttemplate.web.EmployeeRestController. De code voor de controller is zeer eenvoudig en functioneel gehouden.

De REST webservice biedt de mogelijkheid om employee resources te creëren, lezen, updaten en verwijderen en ondersteunt de HTTP werkwoorden GETPOSTPUT en DELETE. Zodra de toepassing wordt gestopt, gaan alle wijzigingen in de resources verloren. De webservice is beschikbaar op het eindpunt http://localhost:8080/rest/employees.

RestTemplate Methods

Voordat we samen naar de eerste broncode kijken, bekijken we eerst de methoden van de klasse RestTemplate. De klasse voorziet in meer dan 50 methodes, waarvan de meeste meermaals overloaded worden. De volgende tabel geeft een ruw overzicht:

Method Description
void delete Uitvoert een DELETE verzoek uit en retourneert niets.
ResponseEntity<T> exchange Uitvoeren van een gespecificeerde HTTP-methode, zoals GET of POST, en retourneert een ResponseEntity die zowel de HTTP-statuscode als de bron als object bevat.
T execute Werkt ongeveer hetzelfde als exchange, maar verwacht een extra RequestCallback en een ResultSetExtractor als parameters. Dit is bijvoorbeeld handig als je vaak complexe verzoeken maakt of complexe antwoorden wilt verwerken.
ResponseEntity<T> getForEntity Uitvoert een GET verzoek uit en stuurt een ResponseEntity terug dat zowel de statuscode als de bron als een object bevat.
T getForObject Werkt ongeveer hetzelfde als getForEntity, maar geeft de resource direct terug.
HttpHeaders headForHeaders Voert een HEAD verzoek uit en geeft alle HTTP-headers voor de opgegeven URL terug.
Set<HttpMethod> optionsForAllow Uitvoert een OPTIONS verzoek uit en gebruikt de Allow header om terug te geven welke HTTP-methodes zijn toegestaan onder de opgegeven URL.
T patchForObject Voert een PATCH verzoek uit en retourneert de representatie van de bron uit het antwoord. De JDK HttpURLConnection ondersteunt geen PATCH, maar Apache HttpComponents en anderen wel.
ResponseEntity<T> postForEntity Uitvoert een POST verzoek uit en stuurt een ResponseEntity terug die de statuscode bevat, evenals de bron als object.
URI postForLocation Werkt als postForEntity, maar retourneert de Location header uit het antwoord, die aangeeft onder welke URI de nieuw aangemaakte bron kan worden bereikt.
T postForObject Werkt als postForEntity, maar geeft de resource direct terug.
void put Uitvoert een PUT verzoek uit en geeft niets terug.

De meeste methoden worden overloaded volgens het volgende schema:

  • URL als String en URL-parameters als VarArgs van het type String
  • URL als String en URL-parameters als Map<String, String>
  • URL als java.net.URI zonder ondersteuning voor URL-parameters

Elke methode met een terugkeertype verwacht een generiek klassetype als parameter om het type antwoord te bepalen.

RestTemplate Demonstraties

De volgende voorbeelden laten zien hoe we een REST web service kunnen consumeren met behulp van de RestTemplate class. Alle volgende voorbeelden zijn in de EmployeeRestClient klasse. Het is een eenvoudige client die RestTemplate omhult en Werknemer-gerelateerde methodes aanbiedt. Zoals altijd kunt u de code vinden in onze GitHub Repository.

public class EmployeeRestClient { private static final String RESOURCE_PATH = "/rest/employees"; private Logger LOG = LoggerFactory.getLogger(EmployeeRestClient.class); private String REQUEST_URI; private RestTemplate restTemplate; public EmployeeRestClient(RestTemplate restTemplate, String host, int port) { this.restTemplate = restTemplate; this.REQUEST_URI = host + ":" + port + RESOURCE_PATH; }}

Tot nu toe is de EmployeeRestClient vrij onspectaculair. We krijgen een instantie van de RestTemplate uit de constructor. Ook via de constructor parameters krijgen we de host en de poort waarop de REST web service draait.

Belangrijk: Alle volgende voorbeelden gebruiken Apache HttpComponents als onderliggende HTTP client API. Hoe dit geconfigureerd kan worden voor de RestTemplate wordt uitgelegd in de post RestTemplate gebruiken met Apaches HttpClient.

4.1 GET

4.1.1 getForEntity()

Laten we beginnen met een eenvoudig voorbeeld om een enkele resource te bevragen:

public ResponseEntity<Employee> getForEntity(long id) { ResponseEntity<Employee> entity = restTemplate.getForEntity(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); LOG.info("Status code value: " + entity.getStatusCodeValue()); LOG.info("HTTP Header 'ContentType': " + entity.getHeaders().getContentType()); return entity;}

In dit codefragment gebruiken we de getForEntity() methode, die als resultaat een ResponseEntity object retourneert. Als parameter verwacht de methode de URI van de bron, inclusief eventuele placeholders en het class type voor het converteren van de body.

ResponseEntity kapselt de status code van het HTTP antwoord in, de HTTP headers en de body die al is geconverteerd in een Java object.

In plaats van een enkele bron te bevragen, is het natuurlijk ook mogelijk een verzameling bronnen te bevragen, zoals de volgende code snippet laat zien:

public List<Employee> getAll(int page, int pageSize) { String requestUri = REQUEST_URI + "?page={page}&pageSize={pageSize}"; Map<String, String> urlParameters = new HashMap<>(); urlParameters.put("page", Integer.toString(page)); urlParameters.put("pageSize", Long.toString(pageSize)); ResponseEntity<Employee> entity = restTemplate.getForEntity(requestUri, Employee.class, urlParameters); return entity.getBody() != null? Arrays.asList(entity.getBody()) : Collections.emptyList();}

De REST webservice verwacht een paginanummer en een pageSize (aantal bronnen per pagina) als query parameters voor het bevragen van een verzameling bronnen. Voor deze parameters wordt in deze code snippet een Map gebruikt in plaats van VarArgs. De ResponseEntity wordt getypt naar een array van Employee omdat we een ongedefinieerd aantal medewerkers in het resultaat verwachten.

4.1.2 getForObject()

Als alleen de body van belang is, kan de getForObject() methode worden gebruikt om de resource direct als Java object op te vragen:

public Optional<Employee> getForObject(long id) { Employee employee = restTemplate.getForObject(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); return Optional.ofNullable(employee);}

Als u echter direct op de JSON string wilt werken, is dit ook mogelijk. Als het class type gewoon String.class is, krijgen we de ruwe JSON string:

public JsonNode getAsJsonNode(long id) throws IOException { String jsonString = restTemplate.getForObject(REQUEST_URI + "/{id}", String.class, id); ObjectMapper mapper = new ObjectMapper(); return mapper.readTree(jsonString);}

Via ObjectMapper kunnen we de JSON string gewoon in een JsonNode en dan de individuele nodes van de JsonNode heel gemakkelijk via jsonNode.path("fieldName") benaderen:

@Testvoid test_getAsJsonNode() throws Exception { JsonNode jsonNode = client.getAsJsonNode(3); assertNotNull(jsonNode); assertEquals(peterGrey.getId(), jsonNode.path("id").asLong()); assertEquals(peterGrey.getFirstName(), jsonNode.path("firstName").asText()); assertEquals(peterGrey.getLastName(), jsonNode.path("lastName").asText()); assertEquals(peterGrey.getYearlyIncome(), jsonNode.path("yearlyIncome").asLong());}

4.2 POST

4.2.1 postForObject()

Het aanmaken van een nieuwe resource via POST is mogelijk met een one-liner:

public Employee postForObject(Employee newEmployee) { return restTemplate.postForObject(REQUEST_URI, newEmployee, Employee.class);}

Naast de URI van het verzoek verwacht de methode postForObject() een willekeurig object dat de body van het verzoek weergeeft en een class type voor de conversie van het antwoord. Als antwoord retourneert de REST web service de aangemaakte resource inclusief de toegewezen ID.

4.2.2 postForLocation()

Zeer vergelijkbaar met postForObject werkt de methode postForLocation(). Hier krijgen we alleen de URI van de nieuwe resource in plaats van de aangemaakte resource:

public URI postForLocation(Employee newEmployee) { return restTemplate.postForLocation(REQUEST_URI, newEmployee);}

4.2.3 postForEntity()

En tenslotte is er postForEntity, die een ResponseEntity retourneert. Daarnaast laat het voorbeeld ons zien hoe we onze eigen waarden in de HTTP-header naar de server kunnen sturen:

public ResponseEntity<Employee> postForEntity(Employee newEmployee) { MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("User-Agent", "EmployeeRestClient demo class"); headers.add("Accept-Language", "en-US"); HttpEntity<Employee> entity = new HttpEntity<>(newEmployee, headers); return restTemplate.postForEntity(REQUEST_URI, entity, Employee.class);}

4.3 PUT

De methode put() wordt gebruikt voor een HTTP PUT. De return van de methode is void. We kunnen deze methode gebruiken om een employee resource bij te werken:

public void put(Employee updatedEmployee) { restTemplate.put(REQUEST_URI + "/{id}", updatedEmployee, Long.toString(updatedEmployee.getId()));}

Echter, er zijn enkele use cases waar we graag een ResponseEntity als response willen hebben omdat dit ons informatie geeft over de HTTP status code en de HTTP headers die door de server zijn meegestuurd. In dit geval kunnen we de methode exchange() gebruiken:

public ResponseEntity<Employee> putWithExchange(Employee updatedEmployee) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.PUT, new HttpEntity<>(updatedEmployee), Employee.class, Long.toString(updatedEmployee.getId()));}

4.4 DELETE

De methode delete() wordt gebruikt om een DELETE verzoek uit te voeren:

public void delete(long id) { restTemplate.delete(REQUEST_URI + "/{id}", Long.toString(id));}

Hier geldt weer hetzelfde als bij put(). Als de HTTP-statuscode of de HTTP-headers van belang zijn, moet de methode exchange() worden gebruikt:

public ResponseEntity<Void> deleteWithExchange(long id) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.DELETE, null, Void.class, Long.toString(id));}

Omdat de server ons niets teruggeeft, gebruiken we Void.class als type voor de conversie van de responsbody.

4.5 HEAD

Als alleen de HTTP-headers van een HTTP-verzoek van belang zijn, gebruiken we methode headForHeaders():

public HttpHeaders headForHeaders() { return restTemplate.headForHeaders(REQUEST_URI);}

Een test van deze methode bevestigt dat we een antwoord krijgen met het inhoudstype application/json wanneer we de opgegeven URL bevragen:

@Testvoid test_headForHeaders() { HttpHeaders httpHeaders = client.headForHeaders(); assertNotNull(httpHeaders.getContentType()); assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));}

4.6 OPTIONS

Met een query via HTTP OPTIONS, kunnen we te weten komen welke HTTP werkwoorden zijn toegestaan voor de opgegeven URL. RestTemplate biedt hiervoor de optionsForAllow() methode:

public Set<HttpMethod> optionsForAllow(long id) { return restTemplate.optionsForAllow(REQUEST_URI + "/{id}", Long.toString(id));}

Een test van deze methode bevestigt dat we de URL http://localhost:8080/rest/employees/1 kunnen bevragen met de HTTP-werkwoorden GETPUT en DELETE:

@Testvoid test_optionsForAllow() { Set<HttpMethod> httpMethods = client.optionsForAllow(1); List<HttpMethod> expectedHttpMethods = List.of(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE); assertTrue(httpMethods.containsAll(expectedHttpMethods));}

Samenvatting

In deze blogpost hebben we gekeken hoe we werken met de klasse RestTemplate. We hebben het volgende bekeken:

  • een algemeen overzicht van RestTemplate en zijn methodes
  • telbare code voorbeelden voor de volgende HTTP werkwoorden:
    • GET
    • POST
    • PUT
    • DELETE
    • HEAD
    • OPTIONS

Ook, is het leuk om de project repository op GitHub te bekijken. Daar vind je ook een test class, die we hier niet in detail hebben besproken.

Ik wil ook graag je aandacht vestigen op de blog post Using RestTemplate with Apaches HttpClient. In deze post bekijken we hoe we RestTemplate kunnen configureren om het te gebruiken met de HTTP client API van Apache.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *