Spring RestTemplate異常:“Not enough variables available to expand”

    1.概述

    在這個簡短的教程中,我們將仔細研究Spring的RestTemplate異常IllegalArgumentException :沒有足夠的變量可擴展。

    首先,我們將詳細討論此異常背後的主要原因。然後,我們將展示如何產生它,最後是如何解決它。

    2.原因

    簡而言之,當我們嘗試在GET request中發送JSON數據時,通常會發生異常。

    簡而言之, RestTemplate提供了getForObject方法來通過在指定的URL上執行GET請求來獲取表示。

    造成此異常的主要原因是**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.示例應用

    現在,讓我們看一個示例,說明如何使用RestTemplate產生此IllegalArgumentException

    為簡單起見,我們將使用單個GET端點創建用於產品管理的基本REST API。

    首先,讓我們創建模型類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字符串: propvalue

    prop鍵指的是產品屬性,因此它可以是一個ID,名稱或價格。

    如上所示,該條件作為字符串參數傳遞給處理程序方法。我們使用ObjectMapper類將JSON字符串轉換為Criterion對象。

    這是我們的Criterion類的外觀:

    public class Criterion {
    
    
    
     private String prop;
    
     private String value;
    
    
    
     // default constructor + getters + setters
    
     }

    最後,讓我們嘗試將GET請求發送到映射到處理程序方法get()的URL。

    @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的一部分{“prop”:”name”,”value”:”ASUS VivoBook”}

    5.解決方案

    根據經驗,我們應該始終使用POST請求發送JSON數據

    但是,儘管不建議這樣做,但使用GET的可能解決方案可能是定義一個包含我們標準的String對象,並在URL中提供真實的URI變量

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

    如我們所見,在將其傳遞給getForObject方法之前,我們使用UriComponentsBuilder類構造了具有查詢參數criterion URI .

    六,結論

    在這篇快速文章中,我們討論了導致RestTemplate引發IllegalArgumentException: “原因IllegalArgumentException: “沒有足夠的變量可擴展”。

    在此過程中,我們遍歷了一個實際示例,展示瞭如何產生異常並解決異常。