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 字符串形式传入,包含两个字段:prop
和 value
。例如:
{"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 获取。