51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

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

1、概览 {#1概览}

本文将带你了解 Spring RestTemplate 抛出 IllegalArgumentException: Not enough variables available to expand 异常的原因以及解决办法。

2、原因 {#2原因}

简而言之,当试图在 GET 请求参数中发送 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、示例应用 {#3示例应用}

创建一个只有一个 GET 端点的基本 REST API 示例,来复现 RestTemplate 抛出 IllegalArgumentException 异常的情况。

首先,创建 Product Model 类:

public class Product {

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

    // 构造函数、get、set 方法省略 
}

接着,定义一个 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());
        }

        // 按其他属性(id、price)检索

        return null;
    }

    private Product findByName(String name) {
        for (Product product : this.productList) {
            if (product.getName().equals(name)) {
                return product;
            }
        }
        return null;
    }

    // 其他方法
}

4、示例应用说明 {#4示例应用说明}

Handler 方法 get() 根据指定的 Criterion 检索 Product 对象。

Criterion 可以表示为一个 JSON 字符串,其中有两个 Key:propvalue

prop 指的是 Product 属性,因此可以是 idname 称或 price

如上所示,Criterion 作为字符串参数传递给 Handler 方法。然后,使用 ObjectMapper 类将 JSON 字符串转换为 Criterion 对象。

Criterion 类如下:

public class Criterion {

    private String prop;
    private String value;

    // 构造函数、get、set 方法省略
}

最后,尝试向映射到 Handler 方法 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);
    }
}

单元测试会抛出 IllegalArgumentException 异常,因为我们试图传递 {"prop": "name", "value": "ASUS VivoBook"} 作为 URL 的一部分。

5、解决办法 {#5解决办法}

根据经验,应始终使用 POST 请求来发送 JSON 数据。

虽然不推荐使用 GET,但一个可行的解决方案是定义一个包含 criterionString 对象,并在 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);
}

如你所见,在将 URI 传递给 getForObject 方法之前,使用 UriComponentsBuilder 类来构建带有查询参数 criterion 的 URI。

6、总结 {#6总结}

本文介绍了 Spring RestTemplate 抛出 IllegalArgumentException: Not enough variables available to expand 异常的原因以及解决办法。


Ref:https://www.baeldung.com/spring-not-enough-variables-available

赞(2)
未经允许不得转载:工具盒子 » Spring RestTemplate 异常:“Not enough variables available to expand”