Това е последната част от нашия преглед на REST. В предишните части разгледахме: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 1

Създаване на проект

В този раздел ще създадем малко RESTful приложение с помощта на Spring Boot. Нашето приложение ще реализира CRUD (Създаване, Четене, Актуализиране, Изтриване) операции на клиентите от примера в предишната част на прегледа. За да започнем, ще създадем ново приложение Spring Boot чрез менюто: File -> New -> Project... В прозореца, който се отваря, изберете Spring Initializr и посочете Project SDK: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 2Щракнете върху бутона "Next". В следващия прозорец посочете „Проект Maven“ като тип на проекта, посочете „Група“ и „Артефакт“: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 3Щракнете върху бутона „Напред“. В следващия прозорец трябва да изберем компонентите на Spring Framework, необходими за проекта. Spring Web ще ни е достатъчен: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 4Кликнете върху бутона "Напред". Сега остава само да посочите името на проекта и местоположението му във файловата система: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 5Щракнете върху бутона "Край". Проектът е създаден и сега можем да видим неговата структура: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 6IDEA генерира дескриптор за внедряване на Maven (pom.xml) и основния клас на приложението ( RestExampleApplication) за нас. Ето How изглеждат:

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>
RestExampleApplication:

@SpringBootApplication
public class RestExampleApplication {

   public static void main(String[] args) {
       SpringApplication.run(RestExampleApplication.class, args);
   }

}

Създаване на REST функционалност

Нашето приложение е система за управление на клиенти. И така, първото нещо, което трябва да направим, е да създадем обект клиент. Това ще бъде POJO (обикновен стар Java обект) клас. Създайте modelпакет вътре в com.codegym.lessons.rest_exampleпакета. Вътре в modelпакета създайте 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;
   }
}
Услугата ще прилага CRUD операции на клиенти. Следващата стъпка е да създадете услуга, която ще изпълнява тези операции. В com.codegym.lessons.rest_exampleпакета създайте serviceпакет. И вътре в това създайте CustomerServiceинтерфейс. Ето codeа на интерфейса с коментари:

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);
}
След това трябва да внедрим този интерфейс. Сега a Map<Integer, Customer>ще съхранява нашите клиенти. Ключовете на картата ще бъдат идентификаторите на клиентите, а стойностите ще бъдат самите клиенти. Това се прави, за да не се претоварва този пример със спецификата на работа с реална база данни. В бъдеще обаче ще можем да напишем друга реализация на интерфейса, която ще направи възможно свързването с реална база данни. В serviceпакета създайте имплементация на 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;
   }
}
Анотацията @Serviceказва на пролетта, че този клас е услуга. Това е специален тип клас, който реализира няHowва логика на бизнес приложение. Впоследствие, благодарение на тази анотация, Spring ще използва инжектиране на зависимости, за да ни предостави екземпляр на този клас на всички места, където е необходимо. Сега е време да създадете контролер. Това е специален клас, в който ще внедрим логиката за обработка на клиентски заявки, изпратени до крайни точки (URI). За да направим всичко това по-ясно, ние ще създадем този клас постепенно. Първо създайте самия клас и добавете зависимост от CustomerService:

@RestController
public class CustomerController {

   private final CustomerService customerService;

   @Autowired
   public CustomerController(CustomerService customerService) {
       this.customerService = customerService;
   }
}
Нека обясним анотациите: @RestController казва на Spring, че този клас е REST контролер. С други думи, този клас реализира логиката за обработка на клиентски заявки. @Autowired казва на Spring, че тук трябва да се добави зависимост. Предаваме CustomerServiceинтерфейса на конструктора. По-рано маркирахме изпълнението на тази услуга с анотацията @Serviceи сега Spring ще може да прехвърли екземпляр на това изпълнение към конструктора на контролера. След това ще приложим всеки метод на контролер за обработка на CRUD операции. Нека започнем с операцията за създаване. За да направим това, ние пишем createметод:

@PostMapping(value = "/customers")
public ResponseEntity<?> create(@RequestBody Customer customer) {
   customerService.create(customer);
   return new ResponseEntity<>(HttpStatus.CREATED);
}
Нека анализираме този метод: @PostMapping(value = "/customers")означава, че този метод обработва POST заявки, изпратени до address "/customers". Методът връща ResponseEntity<?>. A ResponseEntityе специален клас за връщане на отговори. По-късно ще го използваме, за да върнем HTTP code на състоянието на клиента. Методът има @RequestBody Customer customerпараметър. Стойността на този параметър идва от тялото на заявката. Анотацията @RequestBodyпоказва това. Вътре в тялото на метода извикваме метода create()на предварително създадената услуга и го предаваме на клиентския контролер, получен в параметрите. След това връщаме състоянието „201 Created“, като създаваме нов ResponseEntityобект и предаваме съответното HttpStatusполе enum към него. След това ще приложимreadоперация: Първо ще приложим операцията, за да получим списък с всички налични клиенти:

@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")— всичко тук е подобно на @PostMappingанотацията, но сега обработваме GET заявки. Този път връщаме ResponseEntity<List<Customer>>, и в допълнение към HTTP статус, ще върнем и тяло на отговора, което ще бъде списъкът с клиенти. В REST контролерите на Spring всичко е POJO обекти и колекции от POJO обекти, които се връщат като тела на отговор и автоматично се сериализират в JSON, освен ако не е указано друго. Това ни устройва напълно. В рамките на метода използваме нашата услуга, за да получим списък с всички клиенти. След това, ако списъкът не е нула и не е празен, тогава използвамеResponseEntityклас, за да върне списъка с клиенти и HTTP codeа за състояние "200 OK". В противен случай просто връщаме HTTP статус codeа „404 не е намерен“. Сега ще приложим възможността да получите клиент, използвайки неговия 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);
}
Едно ново нещо тук е променливата път. Променливата е дефинирана в URI: value = "/customers/{id}". Посочваме го във фигурни скоби. И го получаваме като intпараметър на метода, използвайки @PathVariable(name = "id")анотацията. Този метод ще приема заявки, изпратени до URI във формуляра /customers/{id}, където {id}представлява произволна числова стойност. Тази стойност впоследствие се предава чрез int idпроменливата към параметъра на метода. В тялото получаваме обекта, Customerизползвайки нашата услуга и полученото id. И след това, по аналогия със списъка, връщаме or състоянието „200 OK“ и Customerсамия обект, or просто състоянието „404 Не е намерен“, ако системата няма клиент с това id. Все още трябва да приложим две операции: актуализиране и изтриване. Ето codeа за тези методи:

@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);
}
Няма нищо съществено ново в тези методи, така че ще пропуснем подробното описание. Единственото нещо, което си струва да се спомене е, че update()методът обработва PUT заявки ( @PutMappingанотация), а delete()методът обработва DELETE заявки ( DeleteMappingанотация). Ето пълния code за контролера:

@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);
   }
}
В резултат на това структурата на нашия проект е следната: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 7

Пускане и тестване

За да стартирате нашето приложение, просто стартирайте main()метода в RestExampleApplicationкласа. Но за да тестваме RESTful уеб услуги, трябва да изтеглим допълнителен софтуер. Факт е, че GET заявките са доста лесни за изпращане от обикновен браузър, но обикновен браузър не може да изпраща POST, PUT и DELETE заявки. Не се притеснявайте: можете да използвате програма, наречена Postman, за да изпращате всяHowви HTTP заявки. Можете да го изтеглите от тук . След като изтеглим и инсталираме Postman, започваме да тестваме нашето приложение. За да направите това, отворете програмата и създайте нова заявка: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 9Щракнете върху бутона "Ново" в горния ляв ъгъл. След това изберете „Заявка“: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 10След това му дайте име и го запазете. Сега нека се опитаме да изпратим POST заявка до сървъра и да създадем първия клиент: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 11По този начин създаваме няколко клиента. След това променяме типа заявка на GET и изпращаме заявката до сървъра: Преглед на REST.  Част 3: Изграждане на RESTful услуга на Spring Boot - 12

Резюме

Честито! Покрихме достатъчно REST. Имаше голям обем материал, но се надяваме, че е бил полезен за вас:
  1. Научихме Howво е REST.

  2. Научихме How се появи REST.

  3. Говорихме за ограниченията и принципите зад този архитектурен стил:

    • клиент-сървър архитектура
    • без гражданство
    • кеширане
    • единен интерфейс
    • слоеве
    • code при поискване (по избор)
  4. Проучихме предимствата, предоставени от REST

  5. Разгледахме подробно How сървърът и клиентът си взаимодействат помежду си чрез HTTP протокола.

  6. Разгледахме по-отблизо исканията и отговорите. Направихме дисекция на техните съставни части.

  7. И накрая, получихме практически опит, като написахме собствено малко RESTful приложение, използвайки Spring Boot. И дори се научихме How да го тестваме с помощта на Postman.

уф. Това беше много, но все още има Howво да направите като домашна работа.

Домашна работа

Опитайте следното:
  1. Следвайки описанието по-горе, създайте свой собствен проект Spring Boot и приложете същата логика като в урока. Повторете всичко точно.
  2. Стартирайте приложението.
  3. Изтеглете и конфигурирайте Postman (or всеки друг инструмент за изпращане на заявки, например curl).
  4. Тествайте заявките POST и GET по същия начин, описан в урока.
  5. Тествайте сами заявките PUT и DELETE.