Рубрики
Без рубрики

Весенний Ботинок: Кулинарная книга Ученика

Spring Boot – это веб-фреймворк, построенный поверх фреймворка Spring. Он предназначен для более легкого использования… С тегом tutorial, java, spring boot.

Spring Boot – это веб-фреймворк, построенный поверх фреймворка Весна . Он предназначен для более простого использования и более быстрого внедрения. Он делает это, настраивая приложение и его среду как можно более автоматически. Как новичок, я могу сказать, что это делает фреймворк действительно легким для понимания.

Мои знания привели меня к прочтению большей части справочной документации , которая хорошо написана и дает вам много информации о внутреннем поведении Spring Boot. В этой документации содержится много подробностей, поэтому цель этой статьи – использовать встречный подход и точно определить концепции, которые вам понадобятся для реализации API с использованием Spring Boot. Я дополню каждый раздел набором ссылок на соответствующую документацию, возможно, вы захотите копнуть глубже.

В качестве примечания, в этом документе будет использоваться версия 2.4.2 фреймворка в проекте Java, использующем Gradle в качестве системы сборки. Однако эта информация остается применимой к любому совместимому языку и системе сборки.

В этой статье будут рассмотрены следующие аспекты создания API с помощью Spring Boot:

  • Начальная загрузка проекта
  • Создание конечных точек REST
  • Обрабатывать ошибки
  • Подключение к постоянному уровню
  • Разбивка результатов на страницы
  • Протестируйте приложение
  • Упакуйте приложение

Начальная загрузка проекта

Эта часть может быть самой простой, так как Spring Boot предоставляет генератор пакетов по адресу https://start.spring.io/ . Мы можем выбрать все необходимые модули и получить архивированный проект с системой сборки, зависимостями и основным классом приложения.

Вне этого генератора, чтобы объявить RESTful API, наш проект должен определить зависимость Spring Boot starter web . Зависимости starter представляют собой набор готовых к использованию функций, упакованных Spring Boot.

plugins {
  id 'org.springframework.boot' version '2.4.2'
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
}

Основной метод приложения должен содержаться в любом классе, к которому мы должны применить аннотацию @SpringBootApplication . Эта аннотация отвечает за множество автоматических конфигураций, а именно за внедрение компонентов и запуск веб-сервера.

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

Запустить сервер так же просто, как с помощью встроенной команды ./gradlew Запуск загрузки . Сервер запустится, но на данный момент у нас нет никакой конечной точки для обслуживания.

Ссылки на документацию: @SpringBootApplication Список начальных зависимостей

Создайте конечную точку REST

Чтобы создать контроллер, нам просто нужно аннотировать любой класс с помощью @RestController . Затем мы можем настроить любой метод внутри этого контроллера в качестве конечной точки, используя @RequestMapping .

@RequestMapping помогите нам настроить конечную точку, указав URL-адрес, HTTP-глагол, ожидаемый тип данных и многое другое. Он может быть применен как к классу, так и к методу, конфигурации, применяемые к классу, будут унаследованы нижележащими методами, а путь объединен.

Чтобы управлять кодами состояния конечной точки, мы вернем ResponseEntity , содержащий как ответное сообщение, так и Http Status .

@RestController
@RequestMapping(value = "/hello",
        consumes = MediaType.ALL_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public class HelloWorldController {

    @RequestMapping(value = "/world", method = RequestMethod.GET)
    public ResponseEntity> index() {
        HashMap output = new HashMap<>();
        output.put("message", "Hello World!");
        return new ResponseEntity<>(output, HttpStatus.OK);
    }
}

ResponseEntity будет автоматически преобразован в HTTP-ответ, используя Http Status в качестве кода ответа и преобразуя сообщение в объект JSON. Помимо преобразования Maps в объекты JSON, Spring Boot configure Jackson для сопоставления всех общедоступных атрибутов или геттеров любого класса с объектом JSON.

$ curl -i "localhost:8080/hello/world"
HTTP/1.1 200
{"Hello":"World"}

Ссылки на документацию: @RestController и @RequestMapping @RequestMapping API doc Настройка сериализации Json

Расширенная конфигурация конечной точки

Теперь, когда у нас есть контроллер, мы можем захотеть определить динамические конечные точки HTTP. Для этого необходимо иметь в виду следующие основные примечания::

  • @RequestBody : Определяет структуру тела с помощью класса java.
  • @Переменный путь : Определяет переменную часть URL-адреса конечной точки.
  • @RequestParam : Определяет параметр запроса.

Контроллер ниже демонстрирует три аннотации с двумя конечными точками, каждая из которых возвращает пользовательский “Привет, мир” в зависимости от запроса.

@RestController
@RequestMapping(value = "/hello",
        consumes = MediaType.ALL_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public class HelloWorldController {

    // The behavior is not representative of a typical POST request
    // and only here as a matter of example.
    @RequestMapping(value = "", method = RequestMethod.POST)
    public ResponseEntity> greetFromBody(@RequestBody HelloBody helloBody) {
        HashMap output = new HashMap<>();
        output.put("message", "Hello " + helloBody.getName());
        return new ResponseEntity<>(output, HttpStatus.OK);
    }

    @RequestMapping(value = "/{name}", method = RequestMethod.GET)
    public ResponseEntity> greet(@PathVariable String name,
                                                     @RequestParam(required = false,
                                                                   defaultValue = "0") int amount_exclamation) {
        HashMap output = new HashMap<>();
        StringBuilder b = new StringBuilder("Hello ");
        b.append(name);
        for (int i = 0; i < amount_exclamation; i++) {
            b.append("!");
        }
        output.put("message", b.toString());
        return new ResponseEntity<>(output, HttpStatus.OK);
    }
}

class HelloBody {
    String name;

    public HelloBody() {
        // Used by Jackson
    }

    public String getName() {
        return this.name;
    }
}

Конечные точки, определенные выше, могут быть использованы следующим образом:

curl -i "localhost:8080/hello/jack?amount_exclamation=4"
HTTP/1.1 200
{"message":"Hello jack!!!!"}

# -d automatically creates a POST request.
$ curl -i "localhost:8080/hello" -d '{"name": "Bob"}' -H "Content-Type: application/json"
HTTP/1.1 200
{"message":"Hello Bob"}

Ссылки на документацию: @RequestBody @PathVariable @RequestParam

Обрабатывать ошибки

По умолчанию Spring Boot вернет HTTP-код 200 для любого успешного запроса, 404, если конечная точка не зарегистрирована, и 500 для любой ошибки. Мы уже видели, что использование ResponseEntity позволяет нам переопределять это поведение для успешных запросов, но нам все еще нужно более точно обрабатывать коды ошибок.

Для этого мы определим пользовательские исключения API, которые будут автоматически преобразованы в HTTP-коды. Это преобразование выполняется классом, расширяющим ResponseEntityExceptionHandler и аннотированный с помощью @ControllerAdvice . В этом классе мы можем определить методы для обработки исключений, используя аннотации @Обработчик исключений и @ResponseStatus .

@ControllerAdvice
public class MyApplicationControllerAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ApiException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public void handleBadRequest() {
    }

    @ExceptionHandler(NotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleNotFound() {
    }
}

public class ApiException extends Exception {
}

public class NotFoundException extends ApiException {
}

После определения ControllerAdvice в вашем проекте любое исключение, создаваемое вашими контроллерами, будет проанализировано и преобразовано в связанный Статус ответа .

@RestController
@RequestMapping(value = "/exception")
public class ExceptionController {

    @RequestMapping(value = "/404", method = RequestMethod.GET)
    public ResponseEntity> notFound() throws NotFoundException {
        throw new NotFoundException();
    }    

    @RequestMapping(value = "/400", method = RequestMethod.GET)
    public ResponseEntity> badRequest() throws ApiException {
        throw new ApiException();
    }

    @RequestMapping(value = "/500", method = RequestMethod.GET)
    public ResponseEntity> ise() throws Exception {
        throw new Exception();
    }
}
$ curl -i "localhost:8080/exception/500"
HTTP/1.1 500

$ curl -i "localhost:8080/exception/404"
HTTP/1.1 404

$ curl -i "localhost:8080/exception/400"
HTTP/1.1 400

Наша обработка исключений очень проста и не возвращает никакой полезной нагрузки, но можно реализовать синтаксический анализ исключений в методах ResponseEntityExceptionHandler .

Ссылки на документацию: ResponseEntityExceptionHandler @ControllerAdvice @ExceptionHandler @ResponseStatus Статус ответа

Подключение к постоянному уровню

Конфигурация

Чтобы использовать базу данных, нам понадобится пакет Java Persistence API (JPA) и реализация любого уровня сохраняемости. Первый установит интерфейсные API, в то время как второй предоставит реализации и драйверы.

Чтобы точно определить минимальные изменения, необходимые для переключения между двумя различными базами данных, мы покажем интеграцию с обоими PostgreSQL и H2 одновременно. Во-первых, давайте объявим наши зависимости:

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

  // Dependencies to your used dbms
  implementation 'org.postgresql:postgresql:42.2.1'
  implementation 'com.h2database:h2:1.4.200'
}

Второй шаг – настроить доступы в application.properties . Файл свойств – это первый и последний раз, когда нам придется беспокоиться о нашей конфигурации сохранения. В этом файле закомментированные 3 строки являются единственной частью, которую необходимо изменить для перехода с PostgreSQL на H2.

spring.datasource.username=user
spring.datasource.password=password
spring.datasource.generate-unique-name=true
# Automatically create & update the database schema from code.
spring.jpa.hibernate.ddl-auto=update

#spring.datasource.url=jdbc:h2:mem:database_name
spring.datasource.url=jdbc:postgresql://localhost:5432/database_name

#spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.driver-class-name=org.postgresql.Driver

#spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL10Dialect

Ссылки на документацию: Конфигурация базы данных Доступные свойства

Определите модель

Определить модель так же просто, как использовать аннотации, определенные в JSR-317 . Эти аннотации доступны через пакет javax.persistence, который доступен через зависимость JPA.

Например, приведенный ниже код создает объект Delivery . Наш идентификатор сущности – это поле id , которое будет автоматически инициализироваться и увеличиваться при каждой новой сохраненной сущности в базе данных благодаря аннотации @Сгенерированное значение .

Примечание: Все общедоступные атрибуты будут заданы в JSON-представлении сущности в ответах API.

@Entity
@Table(name = "delivery")
public class Delivery {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    long id;

    @Column(nullable = false)
    @NotNull
    @Enumerated(EnumType.STRING)
    DeliveryState state;

    @Column(nullable = false)
    @NotNull
    String location;

    public Delivery() {
        // Used by Jackson2
    }

    public Delivery(@NotNull DeliveryState state, @NotNull String location) {
        this.state = state;
        this.location = location;
    }

    public long getId() {
        return id;
    }

    public DeliveryState getState() {
        return state;
    }

    public void setState(DeliveryState state) {
        this.state = state;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

enum DeliveryState {
    PENDING, DELIVERING, WAITING_AT_ARRIVAL, RETURNING, RETURNED, PICKED_UP;
}

Чтобы обеспечить согласованность нашего класса данных, мы применили @NotNull проверки из JSR-303 , эти проверки могут быть применены к конечным точкам, как мы увидим в следующем разделе. Ограничения содержатся в пакете javax.validation.constraints , доступный через зависимость spring-boot-starter-validation .

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-validation'
}

Ссылки на документацию: Объявление сущности документация javax.persistence API (@Entity, @Column, @Enumerate, …) @GeneratedValue документация по API javax.validation.constraints (@NotNull)

Раскройте модель

Чтобы взаимодействовать с нашими моделями, мы должны определить Репозиторий , например,/| CrudRepository . Сделать это так же просто, как расширить класс с помощью пустого класса. Spring Boot автоматически реализует функции для взаимодействия с сущностью.

@Repository
public interface DeliveryRepository extends CrudRepository {
}

Мы аннотируем этот компонент @Repository , чтобы сделать его доступным для внедрения зависимостей. Затем мы можем внедрить и использовать репозиторий в любом классе, например, непосредственно в контроллере. Использование @Autowired автоматически извлекет @Repository , объявленный выше.

Примечание: @Репозиторий и @Сервис вести себя точно так же, как основная аннотация инъекции @Компонент , это просто позволяет отметить семантическое различие.

@RestController
@RequestMapping(value = "/delivery",
        consumes = MediaType.APPLICATION_JSON_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public class DeliveryController {

    private final DeliveryRepository deliveryRepository;

    @Autowired
    public DeliveryController(DeliveryRepository deliveryRepository) {
        this.deliveryRepository = deliveryRepository;
    }

    @RequestMapping(value = "", method = RequestMethod.POST)
    public ResponseEntity post(@Valid @RequestBody Delivery delivery) throws ApiException {
        try {
            delivery = deliveryRepository.save(delivery);
        } catch (Exception e) {
            throw new ApiException();
        }
        return new ResponseEntity<>(delivery, HttpStatus.OK);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public ResponseEntity get(@PathVariable long id) throws ApiException {
        Optional delivery = deliveryRepository.findById(id);
        if (delivery.isEmpty()) {
            throw new NotFoundException();
        }
        return new ResponseEntity<>(delivery.get(), HttpStatus.OK);
    }
}

Мы использовали аннотацию @Valid чтобы убедиться, что наши ограничения, определенные выше, выполняются в теле отправленного доставки .

$ curl -i "localhost:8080/delivery" -H 'Content-Type: application/json' \
  -X POST -d '{"state": "PENDING"}'                  
HTTP/1.1 400 

$ curl -i "localhost:8080/delivery/1" -H 'Content-Type: application/json'
HTTP/1.1 404 

$ curl -i "localhost:8080/delivery" -H 'Content-Type: application/json' \
  -X POST -d '{"state": "PENDING", "location":"Budapest"}'
HTTP/1.1 200 
{"id":1,"state":"PENDING","location":"Budapest"}

$ curl -i "localhost:8080/delivery/1" -H 'Content-Type: application/json'                                                                                 130 ↵
HTTP/1.1 200
{"id":1,"state":"PENDING","location":"Budapest"}

Примечание: H2 – это база данных в памяти, поэтому данные будут удаляться при каждом перезапуске сервера.

Ссылки на документацию: Документация по API CrudRepository Объявление компонента Spring Документация по API javax.validation (@Valid)

Разбивка результатов на страницы

В этом разделе показано, насколько хорошо Spring Boot интегрирует некоторые классические функции веб-API. Чтобы разбить доступ на страницы к нашему предыдущему объекту Доставки, нам просто нужно изменить расширенный класс репозитория с CrudRepository на PagingAndSortingRepository .

@Repository
public interface DeliveryRepository extends PagingAndSortingRepository {
}

Эта реализация репозитория предоставляет новый метод findAll(Pageable) , возвращающий Страницу . Класс Pageable настраивает возвращаемую страницу и размер страницы.

@RestController
@RequestMapping(value = "/delivery",
        consumes = MediaType.APPLICATION_JSON_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public class DeliveryController {

    private final DeliveryRepository deliveryRepository;

    @Autowired
    public DeliveryController(DeliveryRepository deliveryRepository) {
        this.deliveryRepository = deliveryRepository;
    }

    @RequestMapping(value = "", method = RequestMethod.GET)
    public ResponseEntity> index(@RequestParam(required = false, defaultValue = "0") int page) {
        Pageable pageable = PageRequest.of(page, 50);
        return new ResponseEntity<>(deliveryRepository.findAll(pageable), HttpStatus.OK);
    }
}

Затем конечная точка будет обслуживать весь Страница данные объекта по запросу.

$ curl "localhost:8080/delivery" -H 'Content-Type: application/json' | jq                                                                                   4 ↵
{
  "content": [
    {
      "id": 1,
      "state": "PENDING",
      "location": "Budapest"
    }
  ],
  "pageable": {
    "sort": {
      "sorted": false,
      "unsorted": true,
      "empty": true
    },
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 50,
    "paged": true,
    "unpaged": false
  },
  "totalPages": 1,
  "totalElements": 1,
  "last": true,
  "first": true,
  "size": 50,
  "number": 0,
  "sort": {
    "sorted": false,
    "unsorted": true,
    "empty": true
  },
  "numberOfElements": 1,
  "empty": false
}

Ссылки на документацию: Документация API PagingAndSortingRepository Документация API PageRequest Документация по API страницы

Протестируйте приложение

Spring Boot предоставляет все инструменты для легкого тестирования контроллеров с помощью набора API и mocks . В основном, MockMvc позволит нам отправлять запросы и утверждать содержимое ответа, не беспокоясь о технических деталях.

В качестве примера мы тестируем конечную точку POST из приведенного выше раздела. Один из этих тестов успешно создает объект Delivery , а второй имитирует ошибку, поступающую из базы данных.

Чтобы не полагаться на физический экземпляр уровня сохраняемости, мы внедрили наш экземпляр хранилища доставки с помощью @MockBean , который создает и внедряет макет нашего компонента.

@SpringBootTest
@AutoConfigureMockMvc
class DeliveryControllerTest {
    @Autowired
    private MockMvc mvc;

    @MockBean
    DeliveryRepository deliveryRepository;

    @Test
    void testPostDeliveryOk() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Map delivery = getValidDelivery();
        String body = mapper.writeValueAsString(delivery);
        MockHttpServletRequestBuilder accept =
                MockMvcRequestBuilders.post("/delivery")
                        .accept(MediaType.APPLICATION_JSON)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(body);
        mvc.perform(accept).andExpect(status().isOk());
    }

    @Test
    void testPostPersistIssue() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Map delivery = getValidDelivery();
        String body = mapper.writeValueAsString(delivery);
        Mockito.when(deliveryRepository.save(Mockito.any())).thenThrow(new RuntimeException());

        MockHttpServletRequestBuilder accept =
                MockMvcRequestBuilders.post("/delivery")
                        .accept(MediaType.APPLICATION_JSON)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(body);

        mvc.perform(accept).andExpect(status().is4xxClientError());
    }

    private Map getValidDelivery() {
        Map delivery = new HashMap<>();
        delivery.put("state", "PENDING");
        delivery.put("location", "Rome");
        return delivery;
    }
}

Ссылки на документацию: @Sprintbooster @AutoConfiguredMockMvc @MockBean Документация по api MockMvc

Упакуйте приложение

Spring boot также упрощает упаковку приложения либо в виде отдельного jar-файла, либо в виде образа docker.

  • Чтобы создать готовый к запуску fat jar , выполните ./gradlew загрузочный Jar .
  • Чтобы создать образ docker , выполните ./gradlew загрузочный Образ Сборки .

Обратите внимание, что docker не любит символы верхнего регистра в названии изображения, но мы можем легко настроить имя и версию изображения.

// Only use lowercase on docker image name
tasks.named("bootBuildImage") {
    imageName = "${rootProject.name.toLowerCase()}:${version}"
}

Ссылки на документацию: Создание приложения fat jar Настройка образа Docker

Вывод

Spring Boot можно использовать с несколькими аннотациями, и он будет управлять большей частью конфигурации за вас. Однако большая часть конфигурации может быть переопределена, чтобы при необходимости обеспечить ваше собственное поведение. Это делает его хорошей основой для разработки доказательств концепций, сохраняя при этом возможности для оптимизации, если у проекта возникают особые потребности.

Если вы хотите узнать больше о фреймворке, я не могу не подчеркнуть качество справочной документации , которая дает действительно хорошие детали.

Если вы хотите поиграть с некоторым кодом, вы можете найти все эти концепции в примере API доставки на GitHub .

Оригинал: “https://dev.to/aveuiller/spring-boot-apprentice-cookbook-naj”