Spring编程常见错误–Spring补充篇-21 |Spring Rest Template 常见错误

本文介绍了使用Spring RestTemplate时常见的两个问题:使用HashMap而非MultiValueMap提交表单导致的错误及URL特殊字符处理不当引发的问题,并提供了相应的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二十一、Spring Rest Template 常见错误

一般而言,微服务之间的通信大多都是使用 HTTP 方式进行的,这自然少不了使用 HttpClient。在不使用 Spring 之前,一般都是直接使用 Apache HttpClient 和 Ok HttpClient 等,而一旦引入 Spring,就有了一个更好的选择,这就是RestTemplate。

一.参数类型是 MultiValueMap(HashMap会被转换为JSON对象,使用 RestTemplate 提交表单必须是 MultiValueMap)

1、Controller代码

@RestController
public class HelloWorldController {
    @RequestMapping(path = "hi", method = RequestMethod.POST)
    public String hi(@RequestParam("para1") String para1, @RequestParam("para2") String para2){
        return "helloworld:" + para1 + "," + para2;
    };
}

2、客户端请求


RestTemplate template = new RestTemplate();
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("para1", "001");
paramMap.put("para2", "002");

String url = "http://localhost:8080/hi";
String result = template.postForObject(url, paramMap, String.class);
System.out.println(result);

代码定义了一个 Map,包含了 2 个表单参数,然后使用 RestTemplate 的 postForObject 提交这个表单。
执行之后,返回提示 400 错误,即请求出错:
在这里插入图片描述

3、案例解析在这里插入图片描述

使用Wireshark 抓包工具进行抓包
从上图可以看出,实际上是将定义的表单数据以 JSON 请求体(Body)的形式提交过去了,所以接口处理自然取不到任何表单参数。
当我们使用的 Body 是一个 HashMap 时,会进行JSON 序列化的,将一个MAP转换为JSON对象。
使用 RestTemplate 提交表单必须是 MultiValueMap,而我们案例定义的就是普通的 HashMap,最终是按请求 Body 的方式发送出去的。

二.当 URL 中含有特殊字符(当 URL 中含有特殊字符时,一定要注意 URL 的组装方式,最好对URL进行提前组装)

1、Controller代码

@RestController
public class HelloWorldController {
    @RequestMapping(path = "hi", method = RequestMethod.GET)
    public String hi(@RequestParam("para1") String para1){
        return "helloworld:" + para1;
    };
}

2、客户端调用

String url = "http://localhost:8080/hi?para1=1#2";
HttpEntity<?> entity = new HttpEntity<>(null);

RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.GET,entity,String.class);
System.out.println(response.getBody());

客户端调用hi,入参是para1=1#2,我们期望获得controller方法输出的是helloworld:1#2,但是实际输出的是helloworld:1,因为服务器并不认为#2是para1的内容。

3、案例解析

在这里插入图片描述
debug模式下可以看到,#2并不是消失了,而是以Fragment(锚点) 的方式被记录下来了。

1.URL 的格式定义
URL 的格式定义:protocol://hostname[:port]/path/[?query]#fragment
1.Query(查询参数)

页面加载请求数据时需要的参数,用 & 符号隔开,每个参数的名和值用 = 符号隔开。

2.Fragment(锚点)

#开始,字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用 Fragment 直接定位到某一名词的解释。例如定位网页滚动的位置,可以参考下面一些使用示例:

http://example.com/data.csv#row=4Selects the 4th row.
http://example.com/data.csv#col=2Selects 2nd column.

4、问题修正(提前对url进行编码)


String url = "http://localhost:8080/hi?para1=1#2";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
URI uri = builder.build().encode().toUri();
HttpEntity<?> entity = new HttpEntity<>(null);

RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET,entity,String.class);

System.out.println(response.getBody());

关键步骤

String url = "http://localhost:8080/hi?para1=1#2";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
URI uri = builder.build().encode().toUri();

当 URL 中含有特殊字符时,一定要注意 URL 的组装方式,尤其是要区别下面这两种方式:

三.小心多次 URL Encoder(使用正确的URL组装方式,避免多次编码)

1、Controller


@RestController
public class HelloWorldController {
    @RequestMapping(path = "hi", method = RequestMethod.GET)
    public String hi(@RequestParam("para1") String para1){
        return "helloworld:" + para1;
    };

}

2、客户端调用

RestTemplate restTemplate = new RestTemplate();

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/hi");
builder.queryParam("para1", "开发测试 001");
String url = builder.toUriString();

ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
System.out.println(forEntity.getBody());

期望结果是"helloworld: 开发测试 001"
但是实际结果却是helloworld:%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001

3、案例解析

关键点就在于

String url = builder.toUriString();

builder.toUriString()执行的是UriComponentsBuilder.toUriString:


public String toUriString() {
   return this.uriVariables.isEmpty() ?
         build().encode().toUriString() :
         buildInternal(EncodingHint.ENCODE_TEMPLATE).toUriString();
}

.encode()
可以看到又进行了一次编码


public final UriComponents encode() {
   return encode(StandardCharsets.UTF_8);
}

4、问题修正


RestTemplate restTemplate = new RestTemplate();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/hi");
builder.queryParam("para1", "开发测试 001");
URI url = builder.encode().build().toUri();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
System.out.println(forEntity.getBody());

避免多次转化而发生多次编码。
关键点在于

5、正确组装URL的方法

RestTemplate restTemplate = new RestTemplate();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/hi");
builder.queryParam("para1", "开发测试 001");
URI url = builder.encode().build().toUri();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
System.out.println(forEntity.getBody());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值