Spring RestTemplate Exception: “Not enough variables available to expand” – Spring RestTemplate Exception: “没有足够的变量可以展开&#8221

最后修改: 2021年 2月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this short tutorial, we’ll take a close look at Spring’s RestTemplate exception IllegalArgumentException: Not enough variables available to expand.

在这个简短的教程中,我们将仔细研究Spring的RestTemplate异常IllegalArgumentException:没有足够的变量可供展开。

First, we’ll discuss in detail the main cause behind this exception. Then, we’ll showcase how to produce it, and finally, how to solve it.

首先,我们将详细讨论这个异常背后的主要原因。然后,我们将展示如何产生它,最后,如何解决它。

2. The Cause

2.原因

In short, the exception typically occurs when we’re trying to send JSON data in a GET request.

简而言之,该异常通常发生在我们试图在GET请求中发送JSON数据时

Simply put, RestTemplate provides the getForObject method to get a representation by making a GET request on the specified URL.

简单地说,RestTemplate提供了getForObject方法,通过对指定的URL进行GET请求来获得一个表示。

The main cause of the exception is that RestTemplate considers the JSON data encapsulated in the curly braces as a placeholder for URI variables.

异常的主要原因是,RestTemplate认为大括号中封装的JSON数据是URI变量的占位符

Since we don’t provide any values for the expected URI variables, the getForObject method throws the exception.

由于我们没有为预期的URI变量提供任何值,getForObject方法抛出了这个异常。

For example, attempting to send {“name”:”HP EliteBook”} as value:

例如,试图发送{“name”: “HP EliteBook”}作为值。

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

Will simply cause RestTemplate to throw the exception:

将简单地导致RestTemplate抛出异常。

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

3. Example Application

3.应用实例

Now, let’s see an example of how we can produce this IllegalArgumentException using RestTemplate.

现在,让我们看一个例子,看看我们如何使用RestTemplate产生这个IllegalArgumentException

To keep things simple, we’re going to create a basic REST API for product management with a single GET endpoint.

为了保持简单,我们将为产品管理创建一个基本的REST API,并提供一个单一的GET端点。

First, let’s create our model class Product:

首先,让我们创建我们的模型类Product

public class Product {

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

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

Next, we’re going to define a spring controller to encapsulate the logic of our REST API:

接下来,我们要定义一个spring controller来封装我们的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. Example Application Explained

4.解释的应用实例

The basic idea of the handler method get() is to retrieve a product object based on a specific criterion.

处理方法get()的基本思想是根据特定的标准来检索一个产品对象

The criterion can be represented as a JSON string with two keys: prop and value.

该标准可以表示为一个有两个键的JSON字符串。propvalue

The prop key refers to a product property, so it can be an id, a name, or a price.

propkey指的是一个产品属性,所以它可以是一个id、一个名称或一个价格。

As shown above, the criterion is passed as a string argument to the handler method. We used the ObjectMapper class to convert our JSON string to an object of Criterion.

如上所示,标准被作为一个字符串参数传递给处理方法。我们使用ObjectMapper类来转换我们的JSON字符串为Criterion的对象。

This is how our Criterion class looks:

这就是我们的Criterion类的样子。

public class Criterion {

    private String prop;
    private String value;

    // default constructor + getters + setters
}

Finally, let’s try to send a GET request to the URL mapped to the handler method get().

最后,让我们尝试向映射到处理方法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);
    }
}

Indeed, the unit test throws IllegalArgumentException because we’re attempting to pass {“prop”:”name”,”value”:”ASUS VivoBook”} as part of the URL.

事实上,单元测试抛出了IllegalArgumentException,因为我们试图将{“prop”: “name”, “value”: “ASUS VivoBook”}作为URL的一部分。

5. The Solution

5.解决方案

As a rule of thumb, we should always use a POST request to send JSON data.

作为一个经验法则,我们应该始终使用POST请求来发送JSON数据

However, although not recommended, a possible solution using GET could be to define a String object containing our criterion and provide a real URI variable in the URL.

然而,虽然不推荐,但使用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);
}

Let’s look at another solution using the UriComponentsBuilder class:

让我们看看另一个使用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);
}

As we can see, we used the UriComponentsBuilder class to construct our URI with the query parameter criterion before passing it to the getForObject method.

正如我们所看到的,我们使用UriComponentsBuilder类来构建我们的URI,并将查询参数criterion传递给getForObject方法

6. Conclusion

6.结论

In this quick article, we discussed what causes RestTemplate to throw the IllegalArgumentException: “Not enough variables available to expand”.

在这篇快速文章中,我们讨论了什么原因导致RestTemplate抛出IllegalArgumentException。”没有足够的变量可供展开”。

Along the way, we walked through a practical example showing how to produce the exception and solve it.

一路走来,我们通过一个实际的例子,展示了如何产生异常并解决它。

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

一如既往,这些示例的完整源代码可在GitHub上获得over