RestTemplate Introduction
W dzisiejszym wpisie przyjrzymy się dobrze znanemu klientowi restowemu Springa – klasie RestTemplate
. Klasa RestTemplate
jest centralną klasą w ramach frameworka Spring służącą do wykonywania synchronicznych żądań HTTP po stronie klienta.
Podobnie jak Spring JdbcTemplate, RestTemplate
jest również wysokopoziomowym API, które z kolei bazuje na kliencie HTTP. Domyślnie w java.net.HttpURLConnection
wykorzystywana jest klasa RestTemplate
z Java SDK. Spring Framework umożliwia jednak łatwe przełączenie się na inne API klienta HTTP. Jak to zrobić, opisujemy w innym wpisie na blogu.
Większość z nas na pewno ma doświadczenie z HttpURLConnection
lub innym API klienta HTTP. Podczas korzystania z niego zauważyliśmy, że dla każdego żądania generowany jest ciągle ten sam kod:
- Tworzenie obiektu URL i otwieranie połączenia
- Konfigurowanie żądania HTTP
- Wykonanie żądania HTTP
- Interpretacja odpowiedzi HTTP
- Konwersja odpowiedzi HTTP na obiekt Java
- Obsługa wyjątków
Przy użyciu RestTemplate
wszystkie te rzeczy dzieją się w tle i programista nie musi sobie tym zawracać głowy.
Począwszy od Spring 5, nieblokujący i reaktywny WebClient oferuje nowoczesną alternatywę dla RestTemplate
WebClient
oferuje wsparcie zarówno dla synchronicznych, jak i asynchronicznych żądań HTTP oraz scenariuszy strumieniowych. Dlatego RestTemplate
będzie oznaczony jako deprecated w przyszłej wersji Spring Framework i nie będzie zawierał żadnych nowych funkcjonalności.
Ustawienie projektu
Zanim naprawdę zaczniemy, chciałbym przyjrzeć się bliżej następującym punktom ustawienia projektu:
- Użyte zależności
- KlasaPOJO
Employee
- REST web service do testowania
2.1 Używane zależności
Dla projektu demonstracyjnego RestTemplate
potrzebujemy następujących zależności w naszej aplikacji opartej na Spring Boot:
<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>
Zależność spring-boot-starter-web
jest starterem do budowania aplikacji internetowych. Zależność ta zawiera klasę RestTemplate
, możliwość publikowania usług webowych REST i wiele innych rzeczy związanych z webem.
Jako API klienta HTTP w poniższych przykładach wykorzystujemy Apache HttpComponents. Lombok generuje m.in. Getter i Setter i pomaga nam uniknąć powtarzania kodu.
2.2 Klasa POJO Pracownik
Nasza klasa POJO, która będzie nam towarzyszyć przez cały przykład, wygląda tak:
@Data@NoArgsConstructor@AllArgsConstructorpublic class Employee { private long id; private String firstName; private String lastName; private long yearlyIncome;}
Dzięki Lombokowi i @Data
Annotation dostajemy metody getter i setter za darmo. Ponadto, @Data
automatycznie generuje następujące metody:
equals()
hashCode()
toString()
- Konstruktor ze wszystkimi polami, które posiadają adnotacje m.in. z
@NonNull
@NoArgsConstructor
generuje konstruktor bez parametrów, a @AllArgsConstructor
generuje konstruktor ze wszystkimi parametrami.
2.3 REST Web Service For Testing
Aby lepiej zrozumieć poniższe przykłady, projekt demo zawiera bardzo przydatny REST web service. Odpowiadający jej RestController to guru.springframework.resttemplate.web.EmployeeRestController. Kod tego kontrolera jest bardzo prosty i funkcjonalny.
Usługa webowa REST zapewnia możliwość tworzenia, odczytu, aktualizacji i usuwania zasobów pracowników i obsługuje czasowniki HTTP GET
POST
PUT
i DELETE
. W momencie zatrzymania aplikacji wszystkie zmiany dokonane w zasobach są tracone. Usługa sieciowa jest dostępna w punkcie końcowym http://localhost:8080/rest/employees
.
Metody RestTemplate
Zanim przyjrzymy się pierwszemu kodowi źródłowemu razem, przyjrzymy się metodom klasy RestTemplate
. Klasa udostępnia ponad 50 metod, większość z nich jest przeciążana wielokrotnie. Poniższa tabela przedstawia ogólny zarys:
Metoda | Opis |
void delete |
Wykonuje DELETE zapytanie i nic nie zwraca. |
ResponseEntity<T> exchange |
Wykonuje określoną metodę HTTP, taką jak GET lub POST , i zwraca ResponseEntity , który zawiera zarówno kod statusu HTTP, jak i zasób jako obiekt. |
T execute |
Działa podobnie do exchange , ale oczekuje dodatkowego RequestCallback i ResultSetExtractor jako parametrów. Jest to przydatne, na przykład, jeśli często tworzysz złożone żądania lub chcesz przetwarzać złożone odpowiedzi. |
ResponseEntity<T> getForEntity |
Wykonuje polecenie. GET i zwraca ResponseEntity , który zawiera zarówno kod statusu, jak i zasób jako obiekt. |
T getForObject |
Działa podobnie do getForEntity , ale zwraca zasób bezpośrednio. |
HttpHeaders headForHeaders |
Wykonuje żądanie HEAD i zwraca wszystkie nagłówki HTTP dla podanego adresu URL. |
Set<HttpMethod> optionsForAllow |
Wykonuje żądanie OPTIONS żądanie i używa nagłówka Allow , aby zwrócić, które metody HTTP są dozwolone pod określonym adresem URL. |
T patchForObject |
Wykonuje żądanie PATCH i zwraca reprezentację zasobu z odpowiedzi. JDK HttpURLConnection nie obsługuje PATCH , ale Apache HttpComponents i inni to robią. |
ResponseEntity<T> postForEntity |
Wykonuje POST i zwraca ResponseEntity , który zawiera kod statusu oraz zasób jako obiekt. |
URI postForLocation |
Działa jak postForEntity , ale zwraca nagłówek Location z odpowiedzi, który wskazuje pod jakim URI można dotrzeć do nowo utworzonego zasobu. |
T postForObject |
Działa jak postForEntity , ale zwraca zasób bezpośrednio. |
void put |
Wykonuje żądanie PUT i nic nie zwraca. |
Większość metod jest przeciążona według następującego schematu:
- URL jako
String
oraz parametry URL jako VarArgs typuString
- URL jako
String
oraz parametry URL jakoMap<String, String>
- URL jako
java.net.URI
bez wsparcia dla parametrów URL
Każda metoda z typem zwrotu oczekuje typu klasy generycznej jako parametru w celu określenia typu odpowiedzi.
Demonstracje RestTemplate
Następujące przykłady pokazują, jak możemy konsumować usługę internetową REST przy użyciu klasy RestTemplate
. Wszystkie poniższe przykłady znajdują się w klasie EmployeeRestClient
. Jest to prosty klient, który owija RestTemplate
i udostępnia metody związane z Employee. Jak zawsze, kod można znaleźć w naszym Repozytorium GitHub.
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; }}
Jak na razie EmployeeRestClient
jest dość mało spektakularny. Z konstruktora otrzymujemy instancję RestTemplate
. Również poprzez parametry konstruktora otrzymujemy host i port, na którym działa usługa webowa REST.
Ważne: Wszystkie poniższe przykłady używają Apache HttpComponents jako bazowego API klienta HTTP. Jak można to skonfigurować dla RestTemplate
jest wyjaśnione w poście Using RestTemplate with Apaches HttpClient.
4.1 GET
4.1.1 getForEntity()
Zacznijmy od prostego przykładu zapytania o pojedynczy zasób:
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;}
W tym snippecie kodu używamy metody getForEntity()
, która zwraca jako wynik obiekt ResponseEntity
. Jako parametr, metoda oczekuje URI zasobu wraz z wszelkimi placeholderami oraz typ klasy do konwersji ciała.
ResponseEntity
enkapsuluje kod statusu odpowiedzi HTTP, nagłówki HTTP oraz ciało, które zostało już przekonwertowane na obiekt Java.
Zamiast zapytania o pojedynczy zasób, możliwe jest oczywiście również zapytanie o kolekcję zasobów, co pokazuje poniższy wycinek kodu:
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();}
Usługa internetowa REST oczekuje numeru strony i pageSize (liczba zasobów na stronę) jako parametrów zapytania o kolekcję zasobów. Dla tych parametrów, w tym wycinku kodu zamiast VarArgs użyty jest Map
. Parametr ResponseEntity
jest wpisany do tablicy Employee
ponieważ spodziewamy się niezdefiniowanej liczby pracowników w wyniku.
4.1.2 getForObject()
Jeśli interesuje nas tylko ciało, można użyć metody getForObject()
do zapytania o zasób bezpośrednio jako obiekt Javy:
public Optional<Employee> getForObject(long id) { Employee employee = restTemplate.getForObject(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); return Optional.ofNullable(employee);}
Jeśli jednak chcemy operować bezpośrednio na łańcuchu JSON, również jest to możliwe. Jeśli typ klasy to po prostu String.class
, otrzymamy surowy ciąg JSON:
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
możemy po prostu przekształcić ciąg JSON do postaci JsonNode
a następnie uzyskać dostęp do poszczególnych węzłów JsonNode
bardzo wygodnie poprzez jsonNode.path("fieldName")
:
@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()
Tworzenie nowego zasobu poprzez POST
jest możliwe za pomocą one-linera:
public Employee postForObject(Employee newEmployee) { return restTemplate.postForObject(REQUEST_URI, newEmployee, Employee.class);}
Oprócz URI żądania, metoda postForObject()
oczekuje dowolnego obiektu reprezentującego ciało żądania oraz typu klasy do konwersji odpowiedzi. Jako odpowiedź, usługa webowa REST zwraca utworzony zasób wraz z przypisanym ID.
4.2.2 postForLocation()
Bardzo podobnie do postForObject
działa metoda postForLocation()
. Tutaj jednak zamiast utworzonego zasobu otrzymujemy jedynie URI nowego zasobu:
public URI postForLocation(Employee newEmployee) { return restTemplate.postForLocation(REQUEST_URI, newEmployee);}
4.2.3 postForEntity()
I na koniec pozostaje postForEntity
, która zwraca ResponseEntity
. Dodatkowo przykład pokazuje nam, jak możemy wysyłać własne wartości w nagłówku HTTP do serwera:
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
Metoda put()
służy do wykonania HTTP PUT
. Zwrotem metody jest void. Możemy użyć tej metody do aktualizacji zasobu pracownika:
public void put(Employee updatedEmployee) { restTemplate.put(REQUEST_URI + "/{id}", updatedEmployee, Long.toString(updatedEmployee.getId()));}
Jednakże istnieją przypadki użycia, w których chcielibyśmy mieć ResponseEntity
jako odpowiedź, ponieważ daje nam to informacje o kodzie statusu HTTP i nagłówkach HTTP wysłanych przez serwer. W tym przypadku możemy skorzystać z metody exchange()
:
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
Metoda delete()
służy do wykonania żądania DELETE
:
public void delete(long id) { restTemplate.delete(REQUEST_URI + "/{id}", Long.toString(id));}
Tutaj ponownie to samo co w przypadku put()
. Jeśli interesuje nas kod statusu HTTP lub nagłówki HTTP należy użyć metody exchange()
:
public ResponseEntity<Void> deleteWithExchange(long id) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.DELETE, null, Void.class, Long.toString(id));}
Ponieważ serwer nic nam nie zwraca używamy Void.class
jako typ do konwersji ciała odpowiedzi.
4.5 HEAD
Jeśli interesują nas tylko nagłówki HTTP żądania HTTP, używamy metody headForHeaders()
:
public HttpHeaders headForHeaders() { return restTemplate.headForHeaders(REQUEST_URI);}
Test tej metody potwierdza, że otrzymujemy odpowiedź z typem zawartości application/json
, gdy zapytamy podany adres URL:
@Testvoid test_headForHeaders() { HttpHeaders httpHeaders = client.headForHeaders(); assertNotNull(httpHeaders.getContentType()); assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));}
4.6 OPCJE
Zapytaniem przez HTTP OPTIONS
możemy dowiedzieć się, jakie czasowniki HTTP są dozwolone dla podanego adresu URL. RestTemplate
udostępnia do tego metodę optionsForAllow()
:
public Set<HttpMethod> optionsForAllow(long id) { return restTemplate.optionsForAllow(REQUEST_URI + "/{id}", Long.toString(id));}
Test tej metody potwierdza, że możemy zadać zapytanie do adresu URL http://localhost:8080/rest/employees/1
za pomocą czasowników HTTP GET
PUT
i 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));}
Podsumowanie
W tym wpisie na blogu przyjrzeliśmy się, jak pracujemy z klasą RestTemplate
. Przyjrzeliśmy się następującym zagadnieniom:
- ogólny przegląd RestTemplate i jego metod
- liczna ilość przykładów kodu dla następujących czasowników HTTP:
GET
POST
PUT
DELETE
HEAD
OPTIONS
.
A także, jak sprawdzić repozytorium projektu na GitHub. Znajdziesz tam również klasę testową, której nie omawialiśmy tutaj szczegółowo.
Chciałbym również zwrócić Twoją uwagę na wpis na blogu Using RestTemplate with Apaches HttpClient. W tym wpisie przyjrzymy się jak skonfigurować RestTemplate
aby używać go z API klienta HTTP Apache.