SpringCloud-RestTemplate发送Http请求讲解
一、RestTemplate 讲解
1. 没有 RestTemplate 时,Java 怎么发 HTTP 请求?
1.1 用 JDK 原生的 HttpURLConnection
// 发一个 GET 请求,获取用户信息 public String getUser(Long id) { try { URL url = new URL("http://api.example.com/users/" + id); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); // 读取响应 BufferedReader reader = new BufferedReader( new InputStreamReader(conn.getInputStream()) ); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); return response.toString(); // 返回的是 JSON 字符串 } catch (IOException e) { throw new RuntimeException(e); } }弊端:
每发一个请求要写 20 行样板代码
没有自动的 JSON 转换(返回的是字符串,你需要自己用
ObjectMapper转成对象)错误处理繁琐(HTTP 404、500 需要手动判断
conn.getResponseCode())没有连接复用(每次
openConnection()都新建 TCP 连接)没有请求模板化(每个接口都要重复写超时、请求头设置)
1.2 所以 Spring 设计了 RestTemplate
RestTemplate的核心设计目标:
"像调用本地方法一样,发起 HTTP 请求"
2. RestTemplate 是什么?
它是Spring 提供的同步 HTTP 客户端,封装了 JDK 的HttpURLConnection(或 Apache HttpClient、OkHttp),让你用更简洁的方式发送 HTTP 请求。
它在 Spring 生态中的定位:
你的 Service 层 ↓ RestTemplate (发 HTTP 请求) ↓ HttpMessageConverter (对象 ↔ JSON/XML 转换) ↓ ClientHttpRequestFactory (实际网络层:HttpURLConnection / HttpClient / OkHttp) ↓ 目标服务器3. 基本用法:对比原生代码
3.1 GET 请求
原生写法(对比):
// 返回 JSON 字符串,自己解析 String json = getUser(1L); User user = objectMapper.readValue(json, User.class);RestTemplate 写法:
@Autowired private RestTemplate restTemplate; public User getUser(Long id) { // 直接返回 User 对象,JSON 转换自动完成 User user = restTemplate.getForObject( "http://api.example.com/users/{id}", // URL,{id} 是占位符 User.class, // 返回类型 id // 占位符的值 ); return user; }为什么能直接返回User对象?
因为RestTemplate内部注册了MappingJackson2HttpMessageConverter,它自动把响应的 JSON 字符串反序列化成User对象。
3.2 POST 请求(带请求体)
public User createUser(UserDTO dto) { // 发送 POST 请求,dto 自动序列化为 JSON User user = restTemplate.postForObject( "http://api.example.com/users", dto, // 请求体,自动转 JSON User.class // 返回类型 ); return user; }3.3 带请求头(如 Token 鉴权)
public User getUserWithAuth(Long id, String token) { // 构建请求头 HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + token); headers.setContentType(MediaType.APPLICATION_JSON); // 构建请求实体(头 + 体) HttpEntity<Void> requestEntity = new HttpEntity<>(headers); // 发送请求 ResponseEntity<User> response = restTemplate.exchange( "http://api.example.com/users/{id}", HttpMethod.GET, requestEntity, User.class, id ); // 获取状态码、响应头、响应体 HttpStatus status = response.getStatusCode(); User user = response.getBody(); return user; }exchange是最通用的方法,可以处理所有 HTTP 方法,并且能拿到完整的响应信息(状态码、Header、Body)。
4. 核心设计:为什么 RestTemplate 要这样设计?
4.1 为什么有getForObject和getForEntity两个方法?
// 方法 1:只关心返回的数据 User user = restTemplate.getForObject(url, User.class, id); // 方法 2:关心完整响应(状态码 + Header + Body) ResponseEntity<User> response = restTemplate.getForEntity(url, User.class, id); HttpStatus status = response.getStatusCode(); HttpHeaders headers = response.getHeaders(); User user = response.getBody();设计原因:
80% 的场景你只需要数据,所以
getForObject简化了调用20% 的场景你需要判断 HTTP 状态码或读取响应头,所以提供
getForEntity
4.2 为什么 URL 支持{id}占位符?
restTemplate.getForObject( "http://api.example.com/users/{id}/orders/{orderId}", Order.class, 1L, // 对应 {id} 100L // 对应 {orderId} );设计原因:
避免字符串拼接
url + "/" + id,容易出错且可读性差内部自动做 URL 编码(比如参数里有空格、中文会自动转义)
4.3 HttpMessageConverter:自动转换的秘密
RestTemplate能自动处理 JSON,是因为它内部维护了一个转换器列表:
RestTemplate ├── MappingJackson2HttpMessageConverter ← 处理 application/json ├── StringHttpMessageConverter ← 处理 text/plain ├── FormHttpMessageConverter ← 处理 application/x-www-form-urlencoded ├── ByteArrayHttpMessageConverter ← 处理二进制 └── ...执行流程:
发请求时: UserDTO 对象 ↓ 遍历 converters,找到能处理 application/json 的 ↓ MappingJackson2HttpMessageConverter.write() ↓ JSON 字符串 → 发送 收响应时: 响应 Content-Type: application/json ↓ 遍历 converters,找到能处理 JSON 的 ↓ MappingJackson2HttpMessageConverter.read() ↓ User 对象5. 企业级用法:RestTemplate 的配置
5.1 为什么需要手动创建 RestTemplate?
Spring Boot 不会自动注入RestTemplate(不像RedisTemplate),你需要自己配置。
原因:HTTP 客户端的配置差异很大(超时时间、连接池、SSL、代理),框架无法预设一个适合所有场景的默认值。
5.2 企业级配置类
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { // 使用 Apache HttpClient 作为底层(比 JDK 的 HttpURLConnection 性能更好) HttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(200) // 最大连接数 .setMaxConnPerRoute(50) // 每个路由的最大连接(如 api.example.com) .setConnectionTimeToLive(30, TimeUnit.SECONDS) // 连接存活时间 .build(); // 创建工厂 HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); factory.setConnectTimeout(5000); // 连接超时 5 秒 factory.setReadTimeout(10000); // 读取超时 10 秒 RestTemplate restTemplate = new RestTemplate(factory); // 添加拦截器(如统一加请求头、记录日志) restTemplate.getInterceptors().add(new LoggingInterceptor()); return restTemplate; } }5.3 为什么要用连接池?
没有连接池的问题:
每次请求都新建 TCP 连接(三次握手)
高并发时,TCP 连接数暴涨,导致
TIME_WAIT状态过多响应延迟高
使用 Apache HttpClient 连接池后:
连接复用,避免重复握手
控制最大连接数,防止资源耗尽
支持 HTTP/1.1 的 Keep-Alive
6. RestTemplate 的局限:为什么 Spring 官方不推荐在新项目使用?
6.1 它是同步阻塞的
User user = restTemplate.getForObject(url, User.class); // 这行代码会阻塞当前线程,直到 HTTP 响应返回 // 在高并发下,线程池很快被耗尽弊端:
线程阻塞等待网络 IO,CPU 空转
不适合高并发场景(如网关、聚合服务需要同时调多个下游接口)
6.2 替代方案:WebClient(Spring 5 引入)
@Autowired private WebClient webClient; public Mono<User> getUser(Long id) { return webClient.get() .uri("http://api.example.com/users/{id}", id) .retrieve() .bodyToMono(User.class); // 非阻塞,返回 Mono(响应式) }WebClient的优势:
基于 Netty,异步非阻塞
一个线程可以并发处理多个请求
适合微服务架构下的高并发调用
6.3 但 RestTemplate 仍然广泛使用
因为:
代码简单直观,适合大多数 CRUD 场景
团队熟悉度高,维护成本低
很多老项目仍在使用
7. 总结
| 问题 | RestTemplate 的解决方案 |
|---|---|
| 发 HTTP 请求代码繁琐 | 封装为getForObject、postForObject等一行调用 |
| JSON 转换麻烦 | HttpMessageConverter自动序列化/反序列化 |
| URL 拼接易出错 | 占位符{id}自动替换和编码 |
| 需要处理响应状态码 | ResponseEntity封装完整响应 |
| 连接管理低效 | 可配置ClientHttpRequestFactory使用连接池 |
| 同步阻塞性能差 | 新项目推荐WebClient |
一句话:RestTemplate是 Spring 对 HTTP 客户端的封装,让你用调用本地方法的方式发起远程 HTTP 请求,屏蔽了网络通信的底层细节。
二、ObjectMapper 讲解
1. 没有 ObjectMapper 时,Java 怎么处理 JSON?
1.1 手动拼接 JSON 字符串
假设你有一个User对象,要返回给前端:
public class User { private Long id; private String name; private Integer age; private LocalDateTime createTime; // getter / setter }没有 ObjectMapper,你只能手动拼字符串:
public String userToJson(User user) { return "{" + "\"id\":" + user.getId() + "," + "\"name\":\"" + user.getName() + "\"," + "\"age\":" + user.getAge() + "," + "\"createTime\":\"" + user.getCreateTime() + "\"" + "}"; }弊端:
字段多了根本没法维护
字符串拼接容易漏逗号、引号,语法错误
如果
name里有特殊字符(如"或换行),直接破坏 JSON 格式日期格式不统一(有的接口返回
2024-01-01,有的返回毫秒时间戳)嵌套对象(
User里有Address)时,拼接复杂度爆炸
1.2 手动解析 JSON 更痛苦
前端传过来一个 JSON 字符串,你要转成 Java 对象:
{"id":1,"name":"张三","age":25,"createTime":"2024-01-15 10:30:00"}没有 ObjectMapper,你只能手动解析:
public User jsonToUser(String json) { // 用字符串截取?正则?根本不可行 // 只能用 org.json 或 fastjson 的底层 API,一行行 get JSONObject obj = new JSONObject(json); User user = new User(); user.setId(obj.getLong("id")); user.setName(obj.getString("name")); user.setAge(obj.getInt("age")); // ... 每个字段都要手动映射 return user; }弊端:
字段多了写死人
字段名拼写错误在运行时才暴露
类型转换容易出错(比如 JSON 里
age传了字符串"25",手动getInt可能抛异常)嵌套对象需要递归解析
1.3 所以 Jackson 库被创建出来
Jackson 是 Java 世界最流行的 JSON 处理库,而
ObjectMapper是它的核心入口类。
2. ObjectMapper 是什么?
ObjectMapper是 Jackson 库提供的"对象映射器",负责:
| 方向 | 方法 | 俗称 |
|---|---|---|
| Java 对象 → JSON 字符串 | writeValueAsString() | 序列化(Serialization) |
| JSON 字符串 → Java 对象 | readValue() | 反序列化(Deserialization) |
3. 核心用法演示
3.1 序列化:对象 → JSON
User user = new User(); user.setId(1L); user.setName("张三"); user.setAge(25); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(user); System.out.println(json); // 输出:{"id":1,"name":"张三","age":25}对比手动拼接:
一行代码搞定
自动处理引号、转义、特殊字符
字段再多也不怕
3.2 反序列化:JSON → 对象
String json = "{\"id\":1,\"name\":\"张三\",\"age\":25}"; ObjectMapper mapper = new ObjectMapper(); User user = mapper.readValue(json, User.class); System.out.println(user.getName()); // 输出:张三对比手动解析:
一行代码,自动映射所有字段
类型安全(
id自动转Long,age自动转Integer)字段名对应不上时报错明确
3.3 处理集合和嵌套对象
// List 序列化 List<User> list = Arrays.asList(user1, user2); String json = mapper.writeValueAsString(list); // 输出:[{"id":1,"name":"张三"},{"id":2,"name":"李四"}] // List 反序列化 List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {});TypeReference是什么?
因为 Java 泛型擦除,mapper.readValue(json, List.class)无法知道 List 里是什么类型。TypeReference是 Jackson 提供的技巧,利用匿名内部类保留泛型信息。
4. Spring Boot 中 ObjectMapper 的自动配置
4.1 你几乎不需要手动 new ObjectMapper()
在 Spring Boot 项目中,当你引入spring-boot-starter-web,Jackson 作为默认 JSON 库被引入,Spring Boot 自动配置了一个ObjectMapperBean:
// Spring Boot 自动配置的源码(简化) @Bean @Primary public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); // 自动应用 application.yml 中的配置 // 自动注册 classpath 下的模块 return mapper; }这意味着:
@RestController返回对象时,Spring 自动用这个ObjectMapper转 JSON@RequestBody接收 JSON 时,Spring 自动用这个ObjectMapper转对象你可以直接
@Autowired注入使用
4.2 实际使用方式
方式一:让 Spring 自动处理(最常用)
@RestController public class UserController { @GetMapping("/user/{id}") public User getUser(@PathVariable Long id) { User user = userService.getById(id); return user; // Spring 自动用 ObjectMapper 转成 JSON 返回 } @PostMapping("/user") public User createUser(@RequestBody UserDTO dto) { // Spring 自动用 ObjectMapper 把请求体的 JSON 转成 UserDTO return userService.save(dto); } }方式二:手动注入使用(特殊场景)
@Service public class UserService { @Autowired private ObjectMapper objectMapper; public void cacheUser(User user) { // 手动转成 JSON 字符串,存入 Redis String json = objectMapper.writeValueAsString(user); redisTemplate.opsForValue().set("user:" + user.getId(), json); } }5. ObjectMapper 的常用配置
5.1 为什么需要配置?
默认的ObjectMapper行为不一定符合企业需求,比如:
日期字段默认序列化为时间戳(如
1705312200000),前端看不懂空对象可能抛异常
字段命名风格可能不匹配(Java 用驼峰
userName,前端要下划线user_name)
5.2 通过 application.yml 配置(推荐)
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss # 日期格式 time-zone: GMT+8 # 时区 default-property-inclusion: non_null # 忽略 null 字段,不输出 property-naming-strategy: SNAKE_CASE # 驼峰转下划线效果对比:
public class User { private Long id; private String name; private String email; // 假设为 null private LocalDateTime createTime; } // 默认配置输出: // {"id":1,"name":"张三","email":null,"createTime":1705312200000} // 加上面配置后输出: // {"id":1,"name":"张三","create_time":"2024-01-15 10:30:00"} // 注意:email 为 null 被忽略了,createTime 变成了下划线 + 可读日期5.3 通过代码配置(更灵活)
@Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); // 1. 日期格式:转字符串,不用时间戳 mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); // 2. 忽略 null 字段 mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 3. 驼峰转下划线 mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); // 4. 允许单引号(前端有时传单引号 JSON) mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); // 5. 未知字段不报错(前端传了后端没有的字段,忽略而不是抛异常) mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); return mapper; }6. 核心配置项详解(企业级常用)
| 配置 | 作用 | 场景 |
|---|---|---|
WRITE_DATES_AS_TIMESTAMPS | 控制日期序列化格式 | 禁用后输出2024-01-15,启用后输出1705312200000 |
NON_NULL | 忽略值为 null 的字段 | 减少 JSON 体积,前端不用判空 |
SNAKE_CASE | 驼峰命名转下划线 | Java 用userName,JSON 用user_name |
FAIL_ON_UNKNOWN_PROPERTIES | 遇到未知字段是否报错 | 禁用后,前端多传字段不会导致 400 错误 |
ALLOW_SINGLE_QUOTES | 允许 JSON 用单引号包裹 | 兼容不规范的前端 JSON |
7. 注意事项
7.1 ObjectMapper 是线程安全的
// 正确:全局复用一个实例 @Service public class UserService { @Autowired private ObjectMapper mapper; // Spring 注入的单例,线程安全 } // 错误:每次方法调用都 new 一个(浪费资源) public void bad() { ObjectMapper mapper = new ObjectMapper(); // 不要这样写 }设计原因:ObjectMapper内部有大量缓存(如类结构反射信息),创建成本高,应该复用。
7.2 不要混用不同的 ObjectMapper 实例
如果你手动new ObjectMapper(),它的配置和 Spring 自动配置的那个可能不一样,导致:
Controller 返回的 JSON 格式是一套规则
你手动序列化存 Redis 的 JSON 是另一套规则
两边不一致,反序列化时可能出问题
企业级做法:统一使用 Spring 注入的ObjectMapper,或者确保手动创建的实例配置完全一致。
8. 总结
| 问题 | ObjectMapper 的解决方案 |
|---|---|
| 手动拼 JSON 容易出错 | writeValueAsString()自动序列化 |
| 手动解析 JSON 繁琐 | readValue()自动反序列化 |
| 日期格式不统一 | 配置date-format |
| 字段命名风格不一致 | 配置SNAKE_CASE |
| null 字段污染 JSON | 配置NON_NULL |
| 前端多传字段导致报错 | 配置FAIL_ON_UNKNOWN_PROPERTIES |
一句话:
ObjectMapper是 Java 世界处理 JSON 的"翻译官",负责 Java 对象和 JSON 字符串之间的双向转换。
在 Spring Boot 中,它已经被自动配置好,你只需要通过application.yml或代码调整它的行为,就能统一控制整个应用的 JSON 格式。