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 String oraz parametry URL jako VarArgs typu String
  • URL jako String oraz parametry URL jako Map<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 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:
    • 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.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *