1. 概述

在本篇文章中,我们将深入探讨 Spring 的 RestTemplate 在使用时可能抛出的异常:IllegalArgumentException: Not enough variables available to expand

首先,我们会详细分析该异常的主要成因;接着,通过一个实际示例展示如何触发这个异常;最后,给出几种可行的解决方案。

2. 异常成因

简单来说,这个异常通常出现在 尝试在 GET 请求中传递 JSON 数据 的场景下。

我们知道,RestTemplate 提供了 getForObject 方法来发起 GET 请求并获取响应结果。但问题在于,当 URL 中包含花括号 {} 时,RestTemplate 会默认将其视为 URI 变量占位符

如果我们没有为这些占位符提供对应的变量值,getForObject 就会抛出异常。

举个例子,如果我们在 URL 中直接拼接如下 JSON 字符串:

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

那么就会抛出异常:

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

这是因为 {"name":"HP EliteBook"} 中的 {} 被误认为是 URI 占位符,而我们并没有提供任何变量来填充它。

3. 示例应用

下面我们来看一个具体的例子,演示如何在使用 RestTemplate 时触发该异常。

为了简化操作,我们创建一个简单的商品管理 REST API,其中包含一个 GET 接口。

3.1 创建 Product 实体类

public class Product {

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

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

3.2 定义 Controller

@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":"name","value":"ASUS VivoBook"}

在这个示例中,我们使用 ObjectMapper 将 JSON 字符串转换为 Criterion 对象:

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

踩坑提醒:上面的 URL 中包含了 JSON 数据,导致 {} 被错误识别为占位符,从而抛出异常。

6. 解决方案

✅ 推荐做法:使用 POST 请求传递 JSON 数据

GET 请求本就不适合传输复杂结构的数据。建议改用 POST 请求,并将 JSON 数据放入请求体中

⚠️ 如果一定要用 GET,这里有两种绕过方式:

方式一:使用 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);
}

这里我们将 JSON 字符串作为变量值传入,避免了 {} 被误识别。

方式二:使用 UriComponentsBuilder 构造 URL

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

这种方式更加清晰,也更不容易出错。

7. 总结

本文主要分析了 RestTemplate 抛出 IllegalArgumentException: Not enough variables available to expand 异常的原因,并通过实际示例演示了异常的触发与解决方式。

虽然可以通过 URI 变量或 UriComponentsBuilder 来规避问题,但从设计角度出发,GET 请求不应该用来传输 JSON 数据。在实际开发中,建议遵循 HTTP 标准规范,合理使用请求方法。

📌 建议集合:遇到类似问题时快速查阅。

完整示例代码可在 GitHub 获取。


原始标题:Spring RestTemplate Exception: “Not enough variables available to expand” | Baeldung