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 RestTemplateWebClient 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 GETPOSTPUT 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 DELETEzapytanie 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
Stringoraz parametry URL jako VarArgs typuString - URL jako
Stringoraz parametry URL jakoMap<String, String> - URL jako
java.net.URIbez 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 GETPUT 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:
GETPOSTPUTDELETEHEADOPTIONS
.
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.