Перейти к основному содержимому

Исключение Spring RestTemplate: «Недостаточно переменных для расширения»

· 4 мин. чтения

1. Обзор

В этом кратком руководстве мы подробно рассмотрим исключение Spring RestTemplate IllegalArgumentException : недостаточно переменных для расширения.

Во-первых, мы подробно обсудим основную причину этого исключения. Затем мы покажем, как его создать, и, наконец, как его решить.

2. Причина

Короче говоря, исключение обычно возникает , когда мы пытаемся отправить данные JSON в запросе GET .

Проще говоря, RestTemplate предоставляет метод getForObject для получения представления путем выполнения запроса GET по указанному URL-адресу.

Основная причина исключения заключается в том, что RestTemplate рассматривает данные JSON, инкапсулированные в фигурные скобки, в качестве заполнителя для переменных URI .

Поскольку мы не предоставляем никаких значений для ожидаемых переменных URI, метод getForObject выдает исключение.

Например, попытка отправить {"name":"HP EliteBook"} в качестве значения:

String url = "http://products.api.com/get?key=a123456789z&criterion={\"name\":\"HP EliteBook\"}";
Product product = restTemplate.getForObject(url, Product.class);

Просто приведет к тому, что RestTemplate выдаст исключение:

java.lang.IllegalArgumentException: Not enough variable values available to expand 'name'

3. Пример приложения

Теперь давайте посмотрим на пример того, как мы можем создать это исключение IllegalArgumentException с помощью RestTemplate .

Для простоты мы создадим базовый REST API для управления продуктом с одной конечной точкой GET.

Во-первых, давайте создадим класс нашей модели Product :

public class Product {

private int id;
private String name;
private double price;

// default constructor + all args constructor + getters + setters
}

Далее мы собираемся определить контроллер Spring для инкапсуляции логики нашего REST API:

@RestController
@RequestMapping("/api")
public class ProductApi {

private List<Product> productList = new ArrayList<>(Arrays.asList(
new Product(1, "Acer Aspire 5", 437),
new Product(2, "ASUS VivoBook", 650),
new Product(3, "Lenovo Legion", 990)
));

@GetMapping("/get")
public Product get(@RequestParam String criterion) throws JsonMappingException, JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Criterion crt = objectMapper.readValue(criterion, Criterion.class);
if (crt.getProp().equals("name")) {
return findByName(crt.getValue());
}

// Search by other properties (id,price)

return null;
}

private Product findByName(String name) {
for (Product product : this.productList) {
if (product.getName().equals(name)) {
return product;
}
}
return null;
}

// Other methods
}

4. Объяснение примера применения

Основная идея метода-обработчика get() заключается в извлечении объекта продукта на основе определенного критерия .

Критерий можно представить в виде строки JSON с двумя ключами: prop и value .

Ключ свойства относится к свойству продукта, поэтому это может быть идентификатор, имя или цена.

Как показано выше, критерий передается в качестве строкового аргумента в метод-обработчик. Мы использовали класс ObjectMapper для преобразования нашей строки JSON в объект Criterion .

Вот как выглядит наш класс Criterion :

public class Criterion {

private String prop;
private String value;

// default constructor + getters + setters
}

Наконец, давайте попробуем отправить запрос GET на URL-адрес, сопоставленный с методом обработчика get() .

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { RestTemplate.class, RestTemplateExceptionApplication.class })
public class RestTemplateExceptionLiveTest {

@Autowired
RestTemplate restTemplate;

@Test(expected = IllegalArgumentException.class)
public void givenGetUrl_whenJsonIsPassed_thenThrowException() {
String url = "http://localhost:8080/spring-rest/api/get?criterion={\"prop\":\"name\",\"value\":\"ASUS VivoBook\"}";
Product product = restTemplate.getForObject(url, Product.class);
}
}

Действительно, модульный тест выдает исключение IllegalArgumentException , потому что мы пытаемся передать {"prop":"name", "value":"ASUS VivoBook"} как часть URL-адреса.

5. Решение

Как правило, мы всегда должны использовать запрос POST для отправки данных JSON .

Однако, хотя это и не рекомендуется, возможным решением с использованием GET может быть определение объекта String , содержащего наш критерий, и предоставление реальной переменной URI в URL-адресе .

@Test
public void givenGetUrl_whenJsonIsPassed_thenGetProduct() {
String criterion = "{\"prop\":\"name\",\"value\":\"ASUS VivoBook\"}";
String url = "http://localhost:8080/spring-rest/api/get?criterion={criterion}";
Product product = restTemplate.getForObject(url, Product.class, criterion);

assertEquals(product.getPrice(), 650, 0);
}

Давайте рассмотрим другое решение с использованием класса UriComponentsBuilder :

@Test
public void givenGetUrl_whenJsonIsPassed_thenReturnProduct() {
String criterion = "{\"prop\":\"name\",\"value\":\"Acer Aspire 5\"}";
String url = "http://localhost:8080/spring-rest/api/get";

UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url).queryParam("criterion", criterion);
Product product = restTemplate.getForObject(builder.build().toUri(), Product.class);

assertEquals(product.getId(), 1, 0);
}

Как мы видим, мы использовали класс UriComponentsBuilder для построения нашего URI с критерием параметра запроса перед передачей его методу getForObject .

6. Заключение

В этой быстрой статье мы обсудили, что заставляет RestTemplate вызывать исключение IllegalArgumentException : « Недостаточно переменных, доступных для расширения».

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

Как всегда, полный исходный код примеров доступен на GitHub .