在springboot项目中怎么发送请求,设置参数,获取另外一个服务上的数据

在 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

通用注意事项

  1. 参数编码 :避免手动拼接 URL(易出现中文/特殊字符乱码),优先用 UriComponentsBuilder(RestTemplate)、uriBuilder(WebClient)、Feign 注解(自动编码);
  2. 响应解析
    • 优先定义实体类(与目标服务 JSON 字段匹配,字段名不一致可加 @JsonProperty("xxx"));
    • 不确定响应结构时,可先解析为 StringMap<String, Object> 调试;
  3. 异常处理
    • RestTemplate/WebClient/Feign 都会抛出 HTTP 异常,需捕获并处理(如 404、500、超时);
    • Feign 可自定义异常解码器(ErrorDecoder)统一处理响应异常;
  4. HTTPS 调用 :若目标服务是 HTTPS,需配置证书信任(RestTemplate 需自定义 SSLContext,WebClient/Feign 同理);
  5. 微服务场景
    • 若目标服务是微服务(如注册到 Nacos/Eureka),Feign 可直接用服务名(无需 url 参数),配合负载均衡自动选服务实例;
    • 推荐用 Feign(代码简洁、易维护),其次 WebClient(高并发),RestTemplate 仅用于简单场景。

方案对比

方案 特点 适用场景
RestTemplate 同步阻塞、API 简单 简单场景、低并发、快速开发
WebClient 响应式非阻塞、高并发 高并发场景、响应式编程项目
OpenFeign 声明式、代码极简、易维护 微服务场景、团队协作、高复用

根据项目场景选择即可:普通单体项目用 RestTemplate/WebClient,微服务项目优先 OpenFeign。

相关推荐
7哥♡ۣۖᝰꫛꫀꪝۣℋ1 小时前
SpringBoot 配置⽂件
java·spring boot·后端
TroubleBoy丶1 小时前
Docker可用镜像
java·linux·jvm·docker
a3722107741 小时前
HikariCP配置 高并发下连接泄漏避免
java·数据库·oracle
CaliXz1 小时前
取出51.la统计表格内容为json数据 api
java·javascript·json
带刺的坐椅1 小时前
Solon AI 开发学习16 - generate - 生成模型(图、音、视)
java·ai·llm·openai·solon
jiayong231 小时前
Spring Bean 生命周期详解
java·后端·spring
猎人everest1 小时前
Django Rest Framework (DRF) 核心知识体系梳理与深度讲解
后端·python·django
9号达人1 小时前
大家天天说的'银弹'到底是个啥?看完这篇你就明白了
前端·后端·程序员
卿雪1 小时前
缓存异常:缓存击穿、缓存穿透、缓存雪崩 及其解决方案
java·数据库·redis·python·mysql·缓存·golang