- Home »

Пример Spring RestTemplate — как делать HTTP-запросы
Когда разрабатываешь приложение на Spring, рано или поздно сталкиваешься с необходимостью делать HTTP-запросы к внешним API. Раньше многие использовали для этого RestTemplate — классический инструмент из арсенала Spring для работы с REST API. Хотя сейчас Spring рекомендует WebClient как более современную альтернативу, RestTemplate всё ещё широко используется в legacy-проектах и новых разработках, где нужна простота и знакомый синтаксис.
Эта статья поможет тебе быстро освоить RestTemplate: от базовой настройки до продвинутых кейсов использования. Разберём практические примеры, покажем типичные ошибки и их решения, а также сравним с современными альтернативами.
Как работает RestTemplate
RestTemplate — это синхронный HTTP-клиент, который предоставляет удобные методы для выполнения HTTP-запросов. Он работает по принципу шаблона Template Method, инкапсулируя всю логику создания запросов, обработки ответов и конвертации данных.
Основные компоненты RestTemplate:
- HttpMessageConverter — конвертирует Java-объекты в HTTP-тела запросов и обратно
- ClientHttpRequestFactory — создаёт HTTP-соединения
- ResponseErrorHandler — обрабатывает HTTP-ошибки
- ClientHttpRequestInterceptor — позволяет перехватывать и модифицировать запросы
Базовая настройка RestTemplate
Для начала работы нужно создать бин RestTemplate в конфигурации Spring:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// Более продвинутая конфигурация с таймаутами
@Bean
public RestTemplate restTemplateWithTimeouts() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}
}
Для production-окружения лучше использовать Apache HttpClient с пулом соединений:
@Bean
public RestTemplate restTemplateWithPool() {
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
Практические примеры HTTP-запросов
GET-запросы
@Service
public class ApiService {
@Autowired
private RestTemplate restTemplate;
// Простой GET-запрос
public String getPlainText() {
return restTemplate.getForObject("https://api.example.com/data", String.class);
}
// GET с параметрами в URL
public User getUserById(Long id) {
String url = "https://api.example.com/users/{id}";
return restTemplate.getForObject(url, User.class, id);
}
// GET с query параметрами
public List getUsers(String role, int page) {
String url = "https://api.example.com/users?role={role}&page={page}";
Map params = Map.of("role", role, "page", page);
ResponseEntity response = restTemplate.getForEntity(url, User[].class, params);
return Arrays.asList(response.getBody());
}
// GET с заголовками
public ResponseEntity getWithHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer token123");
headers.set("Accept", "application/json");
HttpEntity entity = new HttpEntity<>(headers);
return restTemplate.exchange(
"https://api.example.com/protected",
HttpMethod.GET,
entity,
String.class
);
}
}
POST-запросы
// Создание пользователя
public User createUser(User user) {
String url = "https://api.example.com/users";
return restTemplate.postForObject(url, user, User.class);
}
// POST с кастомными заголовками
public ResponseEntity createUserWithHeaders(User user) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-API-Key", "secret123");
HttpEntity entity = new HttpEntity<>(user, headers);
return restTemplate.postForEntity(
"https://api.example.com/users",
entity,
User.class
);
}
// Отправка файла
public ResponseEntity uploadFile(MultipartFile file) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap body = new LinkedMultiValueMap<>();
body.add("file", file.getResource());
body.add("description", "Uploaded file");
HttpEntity> entity = new HttpEntity<>(body, headers);
return restTemplate.postForEntity(
"https://api.example.com/upload",
entity,
String.class
);
}
PUT, PATCH и DELETE
// Обновление пользователя
public void updateUser(Long id, User user) {
String url = "https://api.example.com/users/{id}";
restTemplate.put(url, user, id);
}
// PATCH-запрос
public ResponseEntity patchUser(Long id, Map updates) {
String url = "https://api.example.com/users/{id}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity
Обработка ошибок и исключений
RestTemplate по умолчанию бросает исключения для HTTP-статусов 4xx и 5xx. Вот как правильно их обрабатывать:
@Service
public class ApiServiceWithErrorHandling {
@Autowired
private RestTemplate restTemplate;
public Optional getUserSafely(Long id) {
try {
String url = "https://api.example.com/users/{id}";
User user = restTemplate.getForObject(url, User.class, id);
return Optional.ofNullable(user);
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
}
throw new ApiException("Client error: " + e.getMessage(), e);
} catch (HttpServerErrorException e) {
throw new ApiException("Server error: " + e.getMessage(), e);
} catch (ResourceAccessException e) {
throw new ApiException("Network error: " + e.getMessage(), e);
}
}
}
Можно также создать кастомный обработчик ошибок:
@Component
public class CustomResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR ||
response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
// Логируем клиентские ошибки
log.warn("Client error: {}", response.getStatusCode());
} else if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
// Логируем серверные ошибки
log.error("Server error: {}", response.getStatusCode());
}
}
}
@Bean
public RestTemplate restTemplateWithErrorHandler() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
return restTemplate;
}
Interceptors и кастомизация запросов
Interceptors полезны для добавления общих заголовков, логирования или аутентификации:
@Component
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
log.info("Request: {} {}", request.getMethod(), request.getURI());
log.debug("Headers: {}", request.getHeaders());
ClientHttpResponse response = execution.execute(request, body);
log.info("Response: {}", response.getStatusCode());
return response;
}
}
@Component
public class AuthInterceptor implements ClientHttpRequestInterceptor {
@Value("${api.token}")
private String apiToken;
@Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + apiToken);
return execution.execute(request, body);
}
}
@Bean
public RestTemplate restTemplateWithInterceptors() {
RestTemplate restTemplate = new RestTemplate();
List interceptors = Arrays.asList(
new LoggingInterceptor(),
new AuthInterceptor()
);
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
Работа с SSL и сертификатами
При работе с HTTPS-API иногда нужно настроить SSL:
@Bean
public RestTemplate restTemplateWithSSL() throws Exception {
SSLContext sslContext = SSLContextBuilder
.create()
.loadTrustMaterial(new File("path/to/truststore.jks"), "password".toCharArray())
.loadKeyMaterial(new File("path/to/keystore.jks"), "password".toCharArray(), "password".toCharArray())
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
// Для тестирования - отключение проверки SSL (НЕ используй в production!)
@Bean
@Profile("test")
public RestTemplate restTemplateWithoutSSL() throws Exception {
SSLContext sslContext = SSLContextBuilder
.create()
.loadTrustMaterial((certificate, authType) -> true)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier((hostname, session) -> true)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
Типичные ошибки и их решения
Проблема | Причина | Решение |
---|---|---|
ResourceAccessException | Таймаут соединения | Увеличить таймауты или настроить retry |
HttpMessageNotReadableException | Проблемы с десериализацией JSON | Проверить структуру классов и аннотации Jackson |
UnsupportedMediaTypeException | Неправильный Content-Type | Явно указать MediaType в заголовках |
HttpClientErrorException: 401 | Проблемы с аутентификацией | Проверить токены и заголовки авторизации |
Сравнение с альтернативами
Критерий | RestTemplate | WebClient | OkHttp | Apache HttpClient |
---|---|---|---|---|
Синхронность | Синхронный | Асинхронный | Оба варианта | Синхронный |
Реактивность | Нет | Да (Reactor) | Нет | Нет |
Простота использования | Высокая | Средняя | Средняя | Низкая |
Производительность | Средняя | Высокая | Высокая | Средняя |
Поддержка Spring | Полная | Полная | Частичная | Частичная |
Продвинутые кейсы использования
Retry механизм
@Component
public class RetryableApiService {
@Autowired
private RestTemplate restTemplate;
@Retryable(
value = {HttpServerErrorException.class, ResourceAccessException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public String callExternalApi() {
return restTemplate.getForObject("https://api.example.com/data", String.class);
}
@Recover
public String recoverFromFailure(Exception ex) {
return "Fallback response";
}
}
Кэширование ответов
@Service
public class CachedApiService {
@Autowired
private RestTemplate restTemplate;
@Cacheable(value = "apiResponses", key = "#id")
public User getUserById(Long id) {
String url = "https://api.example.com/users/{id}";
return restTemplate.getForObject(url, User.class, id);
}
@CacheEvict(value = "apiResponses", key = "#user.id")
public User updateUser(User user) {
String url = "https://api.example.com/users/{id}";
restTemplate.put(url, user, user.getId());
return user;
}
}
Мониторинг и метрики
@Component
public class MetricsInterceptor implements ClientHttpRequestInterceptor {
private final MeterRegistry meterRegistry;
public MetricsInterceptor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
Timer.Sample sample = Timer.start(meterRegistry);
try {
ClientHttpResponse response = execution.execute(request, body);
sample.stop(Timer.builder("http.client.requests")
.tag("method", request.getMethod().name())
.tag("status", response.getStatusCode().toString())
.register(meterRegistry));
return response;
} catch (Exception e) {
sample.stop(Timer.builder("http.client.requests")
.tag("method", request.getMethod().name())
.tag("status", "ERROR")
.register(meterRegistry));
throw e;
}
}
}
Тестирование RestTemplate
Для тестирования кода с RestTemplate используй MockRestServiceServer:
@ExtendWith(SpringExtension.class)
@SpringBootTest
class ApiServiceTest {
@Autowired
private ApiService apiService;
@Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
@BeforeEach
void setUp() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
@Test
void testGetUserById() {
// Arrange
User expectedUser = new User(1L, "John Doe", "john@example.com");
mockServer.expect(requestTo("https://api.example.com/users/1"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(objectToJson(expectedUser), MediaType.APPLICATION_JSON));
// Act
User actualUser = apiService.getUserById(1L);
// Assert
assertThat(actualUser).isEqualTo(expectedUser);
mockServer.verify();
}
@Test
void testHandleNotFoundError() {
// Arrange
mockServer.expect(requestTo("https://api.example.com/users/999"))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
// Act & Assert
Optional result = apiService.getUserSafely(999L);
assertThat(result).isEmpty();
mockServer.verify();
}
}
Деплой и настройка на сервере
При деплое приложения с RestTemplate на сервер учти следующие моменты:
- Настройка прокси: Если сервер находится за корпоративным прокси, настрой системные свойства
- DNS и сетевые настройки: Проверь, что сервер может резолвить внешние домены
- Файрволл: Убедись, что исходящие HTTP/HTTPS соединения разрешены
- Мониторинг: Настрой логирование и метрики для отслеживания внешних вызовов
Для production-окружения понадобится качественный VPS с хорошим каналом связи, а для высоконагруженных систем лучше использовать выделенный сервер.
Интересные факты и нестандартные применения
- Переиспользование соединений: RestTemplate может держать до 2 параллельных соединений к одному хосту по умолчанию
- Интеграция с Spring Security: Можно автоматически передавать SecurityContext между микросервисами
- Кастомные конверторы: RestTemplate поддерживает XML, JSON, протобуф и другие форматы через MessageConverter
- Загрузка больших файлов: Для стриминга файлов используй ResponseExtractor вместо стандартных методов
Автоматизация и скрипты
RestTemplate отлично подходит для создания CLI-утилит и скриптов интеграции:
@Component
@ConditionalOnProperty(name = "app.mode", havingValue = "cli")
public class DataMigrationTool {
@Autowired
private RestTemplate restTemplate;
@EventListener(ApplicationReadyEvent.class)
public void migrateData() {
// Получаем данные из старого API
String oldApiUrl = "https://old-api.example.com/data";
List oldData = Arrays.asList(
restTemplate.getForObject(oldApiUrl, LegacyData[].class)
);
// Конвертируем и отправляем в новое API
String newApiUrl = "https://new-api.example.com/data";
oldData.stream()
.map(this::convertToNewFormat)
.forEach(data -> restTemplate.postForObject(newApiUrl, data, NewData.class));
}
}
Заключение и рекомендации
RestTemplate остаётся надёжным и простым инструментом для HTTP-интеграций в Spring приложениях. Вот основные рекомендации по его использованию:
- Используй для простых случаев: Когда нужен синхронный HTTP-клиент без сложной реактивной логики
- Настраивай таймауты: Всегда устанавливай reasonable timeouts для production
- Добавляй пул соединений: Для высокой нагрузки используй Apache HttpClient с пулом
- Обрабатывай ошибки: Не полагайся только на исключения, используй ResponseEntity
- Логируй запросы: Добавь interceptors для мониторинга и отладки
- Тестируй с MockRestServiceServer: Это стандартный способ тестирования REST-интеграций
Для новых проектов рассмотри переход на WebClient — он более современный и производительный. Но если у тебя уже есть рабочий код на RestTemplate, не спеши его переписывать. RestTemplate будет поддерживаться ещё долго и прекрасно справляется с большинством задач.
Помни, что качество HTTP-интеграций сильно зависит от стабильности и скорости сервера. Правильно настроенная инфраструктура — это основа для надёжных API-взаимодействий.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.