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 .