To jest ostatnia część naszego przeglądu REST. W poprzednich częściach omówiliśmy:
Kliknij przycisk „Dalej”. W następnym oknie określ „Projekt Maven” jako typ projektu, określ „Grupę” i „Artefakt”:
Kliknij przycisk „Dalej”. W kolejnym oknie musimy wybrać potrzebne do projektu komponenty Spring Framework. Wystarczy nam Spring Web:
Kliknij przycisk „Dalej”. Teraz pozostaje tylko wskazać nazwę projektu i jego lokalizację w systemie plików:
Kliknij przycisk „Zakończ”. Projekt jest tworzony i teraz możemy zobaczyć jego strukturę: IDEA wygenerowała dla nas
deskryptor wdrożenia Mavena (pom.xml) oraz główną klasę aplikacji ( ).
Kliknij przycisk „Nowe” w lewym górnym rogu. Następnie wybierz „Żądanie”:
Następnie nadaj mu nazwę i zapisz. Spróbujmy teraz wysłać żądanie POST do serwera i utworzyć pierwszego klienta:
Tworzymy w ten sposób kilku klientów. Następnie zmieniamy typ żądania na GET i wysyłamy żądanie do serwera:

Tworzenie projektu
W tej sekcji stworzymy małą aplikację RESTful przy użyciu Spring Boot. Nasza aplikacja zaimplementuje operacje CRUD (Create, Read, Update, Delete) na klientach z przykładu z poprzedniej części przeglądu. Na początek utworzymy nową aplikację Spring Boot poprzez menu: Plik -> Nowy -> Projekt... W oknie, które zostanie otwarte, wybierz Spring Initializr i określ SDK projektu:




RestExampleApplication
Oto jak wyglądają:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.codegym.lessons/groupId>
<artifactId>rest_example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest_example</name>
<description>REST example project</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
RestExampleAplikacja:
@SpringBootApplication
public class RestExampleApplication {
public static void main(String[] args) {
SpringApplication.run(RestExampleApplication.class, args);
}
}
Tworzenie funkcjonalności REST
Nasza aplikacja to system zarządzania klientami. Pierwszą rzeczą, którą musimy zrobić, to utworzyć podmiot klienta. Będzie to klasa POJO (zwykły stary obiekt Java). Utwórzmodel
pakiet wewnątrz com.codegym.lessons.rest_example
pakietu. Wewnątrz model
pakietu utwórz Customer
:
public class Customer {
private Integer id;
private String name;
private String email;
private String phone;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
Usługa zaimplementuje operacje CRUD na klientach. Kolejnym krokiem jest stworzenie usługi, która będzie realizowała te operacje. W com.codegym.lessons.rest_example
pakiecie utwórz service
pakiet. A wewnątrz tego utwórz CustomerService
interfejs. Oto kod interfejsu z komentarzami:
public interface CustomerService {
/**
* Creates a new customer
* @param customer - Customer to be created
*/
void create(Customer customer);
/**
* Returns a list of all existing customers
* @return List of customers
*/
List<Customer> readAll();
/**
* Returns a customer based on its ID
* @param id - Customer ID
* @return - Customer object with the given ID
*/
Customer read(int id);
/**
* Updates the customer with the given ID,
* according to the passed customer
* @param customer - Customer to use to update the data
* @param id - ID of the customer you want to update
* @return - true if the data has been updated, otherwise false
*/
boolean update(Customer customer, int id);
/**
* Deletes the customer with the given ID
* @param id - ID of the customer to be deleted
* @return - true if the customer was deleted, otherwise false
*/
boolean delete(int id);
}
Następnie musimy zaimplementować ten interfejs. Teraz Map<Integer, Customer>
będzie przechowywać naszych klientów. Kluczami mapy będą identyfikatory klientów, a wartościami będą sami klienci. Odbywa się to tak, aby nie przeciążać tego przykładu specyfiką pracy z prawdziwą bazą danych. Jednak w przyszłości będziemy mogli napisać inną implementację interfejsu, która umożliwi połączenie z prawdziwą bazą danych. W service
paczce utwórz implementację interfejsu CustomerService
:
@Service
public class CustomerServiceImpl implements CustomerService {
// Customer repository
private static final Map<Integer, Customer> CUSTOMER_REPOSITORY_MAP = new HashMap<>();
// Variable for generating a customer ID
private static final AtomicInteger CUSTOMER_ID_HOLDER = new AtomicInteger();
@Override
public void create(Customer customer) {
final int customerId = CUSTOMER_ID_HOLDER.incrementAndGet();
customer.setId(customerId);
CUSTOMER_REPOSITORY_MAP.put(customerId, customer);
}
@Override
public List<Customer> readAll() {
return new ArrayList<>(CUSTOMER_REPOSITORY_MAP.values());
}
@Override
public Customer read(int id) {
return CUSTOMER_REPOSITORY_MAP.get(id);
}
@Override
public boolean update(Customer customer, int id) {
if (CUSTOMER_REPOSITORY_MAP.containsKey(id)) {
customer.setId(id);
CUSTOMER_REPOSITORY_MAP.put(id, customer);
return true;
}
return false;
}
@Override
public boolean delete(int id) {
return CUSTOMER_REPOSITORY_MAP.remove(id) != null;
}
}
Adnotacja @Service
mówi Springowi, że ta klasa jest usługą. Jest to specjalny typ klasy, który implementuje pewną logikę aplikacji biznesowej. Następnie, dzięki tej adnotacji, Spring użyje wstrzykiwania zależności, aby dostarczyć nam instancję tej klasy we wszystkich miejscach, w których jest to potrzebne. Teraz czas na utworzenie kontrolera. Jest to specjalna klasa, w której zaimplementujemy logikę przetwarzania żądań klientów wysyłanych do punktów końcowych (URI). Aby to wszystko było bardziej przejrzyste, będziemy tworzyć tę klasę stopniowo. Najpierw utwórz samą klasę i dodaj zależność od CustomerService
:
@RestController
public class CustomerController {
private final CustomerService customerService;
@Autowired
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
}
Wyjaśnijmy adnotacje: @RestController mówi Springowi, że ta klasa jest kontrolerem REST. Innymi słowy, ta klasa implementuje logikę przetwarzania żądań klientów. @Autowired mówi Springowi, że należy tutaj dodać zależność. Przekazujemy CustomerService
interfejs do konstruktora. Wcześniej adnotacją oznaczyliśmy implementację tej usługi @Service
, a teraz Spring będzie mógł przekazać instancję tej implementacji do konstruktora kontrolera. Następnie zaimplementujemy każdą metodę kontrolera do obsługi operacji CRUD. Zacznijmy od operacji tworzenia. W tym celu piszemy create
metodę:
@PostMapping(value = "/customers")
public ResponseEntity<?> create(@RequestBody Customer customer) {
customerService.create(customer);
return new ResponseEntity<>(HttpStatus.CREATED);
}
Przeanalizujmy tę metodę: @PostMapping(value = "/customers")
oznacza to, że ta metoda przetwarza żądania POST wysyłane na adres „/customers”. Metoda zwraca ResponseEntity<?>
. A ResponseEntity
jest specjalną klasą do zwracania odpowiedzi. Później użyjemy go do zwrócenia klientowi kodu stanu HTTP. Metoda ma @RequestBody Customer customer
parametr. Wartość tego parametru pochodzi z treści żądania. Wskazuje na to adnotacja @RequestBody
. Wewnątrz ciała metody wywołujemy create()
metodę na utworzonej wcześniej usłudze i przekazujemy ją otrzymanemu w parametrach kontrolerowi klienta. Następnie zwracamy stan „201 Created” tworząc nowy obiekt i przekazując mu ResponseEntity
odpowiednie pole enum. HttpStatus
Następnie zaimplementujemyread
operacja: Najpierw zaimplementujemy operację, aby uzyskać listę wszystkich dostępnych klientów:
@GetMapping(value = "/customers")
public ResponseEntity<List<Customer>> read() {
final List<Customer> customers = customerService.readAll();
return customers != null && !customers.isEmpty()
? new ResponseEntity<>(customers, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Przejdźmy do rzeczy: @GetMapping(value = "/customers")
— wszystko tutaj jest podobne do @PostMapping
adnotacji, ale teraz przetwarzamy żądania GET. Tym razem zwrócimy ResponseEntity<List<Customer>>
, a oprócz statusu HTTP zwrócimy również treść odpowiedzi, którą będzie lista klientów. W kontrolerach REST Springa wszystko jest obiektami POJO i kolekcjami obiektów POJO, które są zwracane jako treść odpowiedzi i automatycznie serializowane do formatu JSON, chyba że określono inaczej. To nam pasuje idealnie. W ramach tej metody korzystamy z naszej usługi, aby uzyskać listę wszystkich klientów. Następnie, jeśli lista nie jest pusta i nie jest pusta, używamy metodyResponseEntity
class, aby zwrócić listę klientów i kod stanu HTTP „200 OK”. W przeciwnym razie po prostu zwracamy kod stanu HTTP „404 Not Found”. Teraz zaimplementujemy możliwość pozyskiwania klienta za pomocą jego ID:
@GetMapping(value = "/customers/{id}")
public ResponseEntity<Customer> read(@PathVariable(name = "id") int id) {
final Customer customer = customerService.read(id);
return customer != null
? new ResponseEntity<>(customer, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Nowością jest tutaj zmienna path. Zmienna jest zdefiniowana w URI: value = "/customers/{id}"
. Oznaczamy to w nawiasach klamrowych. I otrzymujemy go jako int
parametr metody za pomocą @PathVariable(name = "id")
adnotacji. Ta metoda akceptuje żądania wysyłane do identyfikatorów URI w postaci /customers/{id}
, gdzie {id}
reprezentuje dowolną wartość liczbową. Ta wartość jest następnie przekazywana przez int id
zmienną do parametru metody. W zabudowie otrzymujemy przedmiot Customer
korzystając z naszej usługi oraz otrzymany id
. I wtedy, analogicznie do listy, zwracamy albo stan „200 OK” i Customer
sam obiekt, albo po prostu stan „404 Not Found”, jeśli w systemie nie ma żadnego klienta z tym id
. Musimy jeszcze zaimplementować dwie operacje: update i delete. Oto kod tych metod:
@PutMapping(value = "/customers/{id}")
public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Customer customer) {
final boolean updated = customerService.update(customer, id);
return updated
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
@DeleteMapping(value = "/customers/{id}")
public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
final boolean deleted = customerService.delete(id);
return deleted
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
W tych metodach nie ma nic zasadniczo nowego, więc pominiemy szczegółowy opis. Jedyne, o czym warto wspomnieć, to że update()
metoda obsługuje żądania PUT ( @PutMapping
adnotacja), a delete()
metoda obsługuje żądania DELETE ( DeleteMapping
adnotacja). Oto pełny kod kontrolera:
@RestController
public class CustomerController {
private final CustomerService customerService;
@Autowired
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
@PostMapping(value = "/customers")
public ResponseEntity<?> create(@RequestBody Customer customer) {
customerService.create(customer);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@GetMapping(value = "/customers")
public ResponseEntity<List<Customer>> read() {
final List<Customer> customers = customerService.readAll();
return customers != null && !customers.isEmpty()
? new ResponseEntity<>(customers, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@GetMapping(value = "/customers/{id}")
public ResponseEntity<Customer> read(@PathVariable(name = "id") int id) {
final Customer customer = customerService.read(id);
return customer != null
? new ResponseEntity<>(customer, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@PutMapping(value = "/customers/{id}")
public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Customer customer) {
final boolean updated = customerService.update(customer, id);
return updated
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
@DeleteMapping(value = "/customers/{id}")
public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
final boolean deleted = customerService.delete(id);
return deleted
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
}
W rezultacie struktura naszego projektu wygląda następująco: 
Uruchomienie i testowanie
Aby uruchomić naszą aplikację wystarczy uruchomićmain()
metodę w RestExampleApplication
klasie. Ale aby przetestować usługi sieciowe RESTful, musimy pobrać dodatkowe oprogramowanie. Faktem jest, że żądania GET są dość proste do wysłania ze zwykłej przeglądarki, ale zwykła przeglądarka nie może wysyłać żądań POST, PUT i DELETE. Nie martw się: do wysyłania dowolnych żądań HTTP możesz użyć programu o nazwie Postman. Możesz go pobrać tutaj . Po pobraniu i zainstalowaniu Postmana przystępujemy do testowania naszej aplikacji. Aby to zrobić, otwórz program i utwórz nowe zgłoszenie: 



Streszczenie
Gratulacje! Wystarczająco omówiliśmy REST. Było dużo materiału, ale mam nadzieję, że był dla Ciebie przydatny:-
Dowiedzieliśmy się, czym jest REST.
-
Dowiedzieliśmy się, jak powstał REST.
-
Rozmawialiśmy o ograniczeniach i zasadach stojących za tym stylem architektonicznym:
- architektura klient-serwer
- bezpaństwowiec
- buforowanie
- jednolity interfejs
- warstwy
- kod na żądanie (opcjonalnie)
-
Zbadaliśmy korzyści zapewniane przez REST
-
Zbadaliśmy szczegółowo, w jaki sposób serwer i klient współdziałają ze sobą za pośrednictwem protokołu HTTP.
-
Przyjrzeliśmy się bliżej prośbom i odpowiedziom. Przeanalizowaliśmy ich części składowe.
-
W końcu zdobyliśmy praktyczne doświadczenie, pisząc naszą własną małą aplikację RESTful przy użyciu Spring Boot. Nauczyliśmy się nawet, jak to przetestować za pomocą Postmana.
Praca domowa
Spróbuj wykonać następujące czynności:- Postępując zgodnie z powyższym opisem, utwórz własny projekt Spring Boot i zaimplementuj tę samą logikę, co w lekcji. Powtórz wszystko dokładnie.
- Uruchom aplikację.
- Pobierz i skonfiguruj Postmana (lub dowolne inne narzędzie do wysyłania żądań, na przykład curl).
- Przetestuj żądania POST i GET w ten sam sposób, jak opisano w lekcji.
- Przetestuj samodzielnie żądania PUT i DELETE.
GO TO FULL VERSION