Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Запросы к другим API: RestClient

Иногда серверу самому нужно сходить в чужой API: узнать курс валют, проверить адрес, забрать данные. В учебнике вы делали такие запросы через java.net.http.HttpClient. В Spring для этого есть RestClient — клиент с цепочкой вызовов, который сам превращает JSON ответа в объект Java. Научим блог импортировать статью из тестового API JSONPlaceholder.

Подключение

В Spring Boot 4 клиент — отдельный стартер (на start.spring.io — HTTP Client):

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-restclient</artifactId>
</dependency>

Адрес чужого API вынесем в настройки:

# src/main/resources/application.yaml
blog:
  import:
    base-url: https://jsonplaceholder.typicode.com

Ответ чужого API

Запрос https://jsonplaceholder.typicode.com/posts/1 возвращает JSON с полями userId, id, title и body. Опишем запись только с нужными полями — лишние Spring пропустит:

// src/main/java/com/example/blog/ExternalPost.java
package com.example.blog;
 
public record ExternalPost(Long id, String title, String body) {
}

Сервис импорта

// src/main/java/com/example/blog/ImportService.java
package com.example.blog;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
 
@Service
public class ImportService {
 
    private final RestClient restClient;
    private final PostService postService;
 
    public ImportService(RestClient.Builder builder,
                         @Value("${blog.import.base-url}") String baseUrl,
                         PostService postService) {
        this.restClient = builder.baseUrl(baseUrl).build();
        this.postService = postService;
    }
 
    public PostResponse importPost(Long externalId, String username) {
        ExternalPost external = restClient.get()
                .uri("/posts/{id}", externalId)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
                    throw new IllegalArgumentException("Нет внешней статьи " + externalId);
                })
                .body(ExternalPost.class);
 
        var request = new NewPostRequest(external.title(), external.body());
        return postService.create(request, username);
    }
}

RestClient.Builder Spring Boot создаёт сам и уже настраивает в нём Jackson. uri("/posts/{id}", externalId) подставляет номер в адрес, retrieve() выполняет запрос, body(ExternalPost.class) превращает JSON в запись. onStatus перехватывает ответы 4xx и бросает наше исключение — его обработчик из главы об ошибках превратит в 400.

Адрес импорта

// src/main/java/com/example/blog/ImportController.java
@PostMapping("/api/import/{externalId}")
@ResponseStatus(HttpStatus.CREATED)
public PostResponse importPost(@PathVariable Long externalId,
                               Principal principal) {
    return importService.importPost(externalId, principal.getName());
}

Импорт доступен только вошедшим, поэтому запрос отправляем с токеном из урока о JWT:

curl -X POST http://localhost:8080/api/import/1 -H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:8080/api/import/9999 -H "Authorization: Bearer $TOKEN"
{"id":65,"title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body":"quia et suscipit\nsuscipit recusandae ...","author":"anna","createdAt":"2026-10-04T09:10:31.243724400Z"}
{"detail":"Нет внешней статьи 9999","instance":"/api/import/9999","status":400,"title":"Bad Request"}

Первая статья пришла из JSONPlaceholder (там тексты-заглушки на латыни) и сохранилась в блоге от имени Анны. На несуществующий номер чужой API ответил 404, и мы вернули клиенту понятное сообщение.

Другие методы

ЗадачаВызов
Отправить JSONrestClient.post().uri("/posts").body(request).retrieve()
Заголовок.header("Authorization", "Bearer ...")
Ответ с кодом и заголовками.retrieve().toEntity(ExternalPost.class)
Список.body(new ParameterizedTypeReference<List<ExternalPost>>() {})

Частая ошибка

Без стартера spring-boot-starter-restclient приложение не запустится:

No qualifying bean of type 'org.springframework.web.client.RestClient$Builder' available: expected at least 1 bean which qualifies as autowire candidate.

В Spring Boot 3 клиент входил в веб-стартер, а в Spring Boot 4 его подключают отдельно. Добавьте зависимость — и бин RestClient.Builder появится.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.