«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:

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

public class Product {

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

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

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

@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
}

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

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

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

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

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

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

public class Criterion {

    private String prop;
    private String value;

    // default constructor + getters + setters
}

5. Решение

@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);
    }
}

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

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

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

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

@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);
}

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

@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);
}

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

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

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

«

As always, the full source code of the examples is available over on GitHub.