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
- Exception handling
. de HTTP response in een Java object
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 RestTemplate
WebClient
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 GET
POST
PUT
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 typeString
- URL als
String
en URL-parameters alsMap<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 GET
PUT
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.