在 Spring Boot 项目中调用外部服务(获取数据),核心是通过 HTTP 客户端 发送请求、设置参数并解析响应。Spring Boot 提供了多种主流方案,从简单到工程化(微服务场景)依次为:RestTemplate(基础同步)、WebClient(响应式非阻塞)、OpenFeign(声明式,微服务首选)。以下是每种方案的详细用法(覆盖「参数设置」「请求发送」「响应解析」核心场景)。
前置说明
调用外部服务的核心场景:
- 参数类型:路径参数(如
/user/{id})、GET 请求参数(如/user?name=张三)、POST 请求体(JSON/表单); - 响应类型:解析为 String、自定义实体类、JSON 对象等;
- 前提:确保目标服务可访问(IP/域名+端口+接口路径),且接口协议(HTTP/HTTPS)、请求方法(GET/POST)匹配。
方式 1:RestTemplate(基础同步,Spring 传统方案)
RestTemplate 是 Spring 内置的同步 HTTP 客户端,适配大部分简单场景,需手动配置 Bean(Spring Boot 不默认注入)。
步骤 1:配置 RestTemplate Bean
在配置类中注入 RestTemplate(全局复用):
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
步骤 2:不同参数场景的调用示例
假设目标服务接口:
| 接口类型 | 地址 | 说明 |
|---|---|---|
| GET | http://localhost:8081/user/{id} |
路径参数(获取单个用户) |
| GET | http://localhost:8081/user |
请求参数(分页查用户) |
| POST | http://localhost:8081/user |
JSON 请求体(新增用户) |
| POST | http://localhost:8081/user/form |
表单参数(登录) |
场景 1:GET 请求(路径参数)
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
// 获取单个用户(路径参数:{id})
public UserDTO getUserById(Long id) {
// 目标接口地址({id} 是路径参数占位符)
String url = "http://localhost:8081/user/{id}";
// 方式1:直接传参(参数按顺序匹配占位符)
UserDTO user = restTemplate.getForObject(url, UserDTO.class, id);
// 方式2:Map 传参(适合多路径参数)
// Map<String, Object> params = new HashMap<>();
// params.put("id", id);
// UserDTO user = restTemplate.getForObject(url, UserDTO.class, params);
return user;
}
}
// 响应实体类(与目标服务返回的 JSON 字段对应)
class UserDTO {
private Long id;
private String username;
private Integer age;
// 省略 getter/setter/toString
}
场景 2:GET 请求(请求参数/查询参数)
java
// 分页查询用户(请求参数:pageNum、pageSize)
public List<UserDTO> getUserList(Integer pageNum, Integer pageSize) {
// 方式1:拼接 URL(不推荐,易有编码问题)
// String url = "http://localhost:8081/user?pageNum=" + pageNum + "&pageSize=" + pageSize;
// 方式2:UriComponentsBuilder 构建(推荐,自动编码)
String baseUrl = "http://localhost:8081/user";
URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl)
.queryParam("pageNum", pageNum)
.queryParam("pageSize", pageSize)
.build()
.toUri();
// 解析响应为 List(注意:RestTemplate 直接解析 List 需用 ParameterizedTypeReference)
ParameterizedTypeReference<List<UserDTO>> type = new ParameterizedTypeReference<List<UserDTO>>() {};
ResponseEntity<List<UserDTO>> response = restTemplate.exchange(
uri,
HttpMethod.GET,
null, // GET 请求无请求体,传 null
type
);
return response.getBody(); // 获取响应体
}
场景 3:POST 请求(JSON 请求体)
java
// 新增用户(POST + JSON 请求体)
public Boolean addUser(UserDTO userDTO) {
String url = "http://localhost:8081/user";
// postForObject:发送 POST 请求,JSON 体自动序列化(基于 Jackson)
ResponseEntity<Boolean> response = restTemplate.postForEntity(
url,
userDTO, // 请求体(自动转为 JSON)
Boolean.class // 响应类型
);
// 可获取响应状态码、响应头
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
}
return false;
}
场景 4:POST 请求(表单参数)
java
// 用户登录(POST + 表单参数)
public String login(String username, String password) {
String url = "http://localhost:8081/user/form";
// 构建表单参数(MultiValueMap 对应 form-data/x-www-form-urlencoded)
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
formParams.add("username", username);
formParams.add("password", password);
// 设置请求头(表单格式)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 封装请求体 + 头
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formParams, headers);
// 发送请求
ResponseEntity<String> response = restTemplate.postForEntity(
url,
requestEntity,
String.class
);
return response.getBody();
}
RestTemplate 关键配置(超时、异常)
默认 RestTemplate 超时时间较长,可自定义客户端工厂配置超时:
java
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 连接超时 5s
factory.setReadTimeout(10000); // 读取超时 10s
return new RestTemplate(factory);
}
// 异常处理(RestTemplate 抛出 HttpClientErrorException 等)
try {
UserDTO user = restTemplate.getForObject(url, UserDTO.class, id);
} catch (HttpClientErrorException e) {
// 4xx 错误(如 404、400)
System.out.println("请求失败:" + e.getStatusCode() + ",原因:" + e.getResponseBodyAsString());
} catch (ResourceAccessException e) {
// 5xx 或连接超时
System.out.println("服务连接失败:" + e.getMessage());
}
方式 2:WebClient(响应式非阻塞,Spring 5+ 推荐)
WebClient 是 Spring 5 引入的响应式 HTTP 客户端(基于 Reactor),非阻塞、高并发场景首选,替代 RestTemplate(RestTemplate 已标记为维护状态)。
步骤 1:引入依赖
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
步骤 2:配置 WebClient(可选)
java
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:8081") // 基础地址(可省略,调用时指定)
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 连接超时 5s
.responseTimeout(Duration.ofSeconds(10)) // 读取超时 10s
))
.build();
}
}
步骤 3:WebClient 调用示例
场景 1:GET 路径参数
java
@Service
public class UserWebClientService {
@Autowired
private WebClient webClient;
// 响应式获取用户(返回 Mono 类型,非阻塞)
public Mono<UserDTO> getUserById(Long id) {
return webClient.get()
.uri("/user/{id}", id) // 路径参数(baseUrl 已配,只需写路径)
.retrieve() // 发起请求并获取响应
.onStatus(HttpStatus::is4xxClientError, resp -> {
// 自定义 4xx 异常处理
return resp.bodyToMono(String.class).map(msg -> new RuntimeException("客户端错误:" + msg));
})
.onStatus(HttpStatus::is5xxServerError, resp -> {
// 自定义 5xx 异常处理
return resp.bodyToMono(String.class).map(msg -> new RuntimeException("服务端错误:" + msg));
})
.bodyToMono(UserDTO.class); // 解析响应为 UserDTO
}
// 同步调用(如需阻塞获取结果,用 block())
public UserDTO getUserByIdSync(Long id) {
return getUserById(id).block(); // 仅测试/简单场景用,非阻塞场景避免
}
}
场景 2:GET 请求参数
java
// 分页查询用户(返回 Flux 列表)
public Flux<UserDTO> getUserList(Integer pageNum, Integer pageSize) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/user")
.queryParam("pageNum", pageNum)
.queryParam("pageSize", pageSize)
.build())
.retrieve()
.bodyToFlux(UserDTO.class); // 解析为列表(Flux)
}
场景 3:POST JSON 请求体
java
// 新增用户(POST + JSON 体)
public Mono<Boolean> addUser(UserDTO userDTO) {
return webClient.post()
.uri("/user")
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(userDTO), UserDTO.class) // 请求体
.retrieve()
.bodyToMono(Boolean.class);
}
方式 3:OpenFeign(声明式 HTTP 客户端,微服务首选)
OpenFeign 是 Netflix 开源的声明式 HTTP 客户端,Spring Cloud 对其封装后可无缝集成 Spring Boot,代码最简洁(无需手动拼接 URL/参数),微服务场景优先使用。
步骤 1:引入依赖
xml
<!-- pom.xml(Spring Boot 3.x 需用 spring-cloud-starter-openfeign 4.x+) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<!-- 如未引入 Spring Cloud 父依赖,需指定版本 -->
<!-- <version>4.1.0</version> -->
</dependency>
<!-- 如需配置超时等,引入 spring-cloud-starter-loadbalancer(Feign 依赖负载均衡) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
步骤 2:开启 Feign 功能
在启动类添加 @EnableFeignClients 注解:
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // 开启 Feign 客户端
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
步骤 3:定义 Feign 接口(核心)
通过注解声明请求方式、参数、响应,无需手动实现:
java
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// @FeignClient:指定目标服务名称/地址(微服务场景用服务名,单机用 URL)
@FeignClient(name = "user-service", url = "http://localhost:8081")
public interface UserFeignClient {
// 1. GET 路径参数(@PathVariable 必须指定 value)
@GetMapping("/user/{id}")
UserDTO getUserById(@PathVariable("id") Long id);
// 2. GET 请求参数(@RequestParam)
@GetMapping("/user")
List<UserDTO> getUserList(
@RequestParam("pageNum") Integer pageNum,
@RequestParam("pageSize") Integer pageSize
);
// 3. POST JSON 请求体(@RequestBody)
@PostMapping("/user")
Boolean addUser(@RequestBody UserDTO userDTO);
// 4. POST 表单参数(@RequestParam + 表单类型)
@PostMapping(value = "/user/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
String login(
@RequestParam("username") String username,
@RequestParam("password") String password
);
}
步骤 4:使用 Feign 接口
直接注入接口调用,Feign 自动实现 HTTP 请求:
java
@Service
public class UserFeignService {
@Autowired
private UserFeignClient userFeignClient;
public UserDTO getUserById(Long id) {
return userFeignClient.getUserById(id);
}
public List<UserDTO> getUserList(Integer pageNum, Integer pageSize) {
return userFeignClient.getUserList(pageNum, pageSize);
}
public Boolean addUser(UserDTO userDTO) {
return userFeignClient.addUser(userDTO);
}
}
Feign 关键配置(超时、日志)
在 application.yml 中配置:
yaml
# Feign 配置
feign:
client:
config:
default: # 全局配置(也可指定服务名:user-service)
connectTimeout: 5000 # 连接超时 5s
readTimeout: 10000 # 读取超时 10s
# 日志配置(需配合 Logger.Level 使用)
logger:
level:
com.demo.feign.UserFeignClient: FULL # 打印 Feign 完整日志(请求/响应/头)
# 日志级别(确保 Feign 日志能输出)
logging:
level:
com.demo.feign: DEBUG
通用注意事项
- 参数编码 :避免手动拼接 URL(易出现中文/特殊字符乱码),优先用
UriComponentsBuilder(RestTemplate)、uriBuilder(WebClient)、Feign 注解(自动编码); - 响应解析 :
- 优先定义实体类(与目标服务 JSON 字段匹配,字段名不一致可加
@JsonProperty("xxx")); - 不确定响应结构时,可先解析为
String或Map<String, Object>调试;
- 优先定义实体类(与目标服务 JSON 字段匹配,字段名不一致可加
- 异常处理 :
- RestTemplate/WebClient/Feign 都会抛出 HTTP 异常,需捕获并处理(如 404、500、超时);
- Feign 可自定义异常解码器(
ErrorDecoder)统一处理响应异常;
- HTTPS 调用 :若目标服务是 HTTPS,需配置证书信任(RestTemplate 需自定义
SSLContext,WebClient/Feign 同理); - 微服务场景 :
- 若目标服务是微服务(如注册到 Nacos/Eureka),Feign 可直接用服务名(无需
url参数),配合负载均衡自动选服务实例; - 推荐用 Feign(代码简洁、易维护),其次 WebClient(高并发),RestTemplate 仅用于简单场景。
- 若目标服务是微服务(如注册到 Nacos/Eureka),Feign 可直接用服务名(无需
方案对比
| 方案 | 特点 | 适用场景 |
|---|---|---|
| RestTemplate | 同步阻塞、API 简单 | 简单场景、低并发、快速开发 |
| WebClient | 响应式非阻塞、高并发 | 高并发场景、响应式编程项目 |
| OpenFeign | 声明式、代码极简、易维护 | 微服务场景、团队协作、高复用 |
根据项目场景选择即可:普通单体项目用 RestTemplate/WebClient,微服务项目优先 OpenFeign。