Feign 客户端实战教程
bash
复制代码
📁 需要创建的文件
com/payslip/feign/
├── WeatherClient.java # Feign接口
├── WeatherResult.java # 响应对象
└── WeatherFeignConfig.java # Feign配置(可选)
com/payslip/service/
└── WeatherService.java # 业务层
com/payslip/api/
└── WeatherController.java # 控制器(测试用)
1.WeatherClient.java(Feign接口)
java
复制代码
package com.payslip.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 天气服务 Feign 客户端
*
* 【核心知识点】
* 1. @FeignClient 声明这是一个Feign客户端
* 2. value: 客户端名称,用于标识
* 3. url: 目标服务地址(也可以从配置文件读取:${api.weather.url})
* 4. configuration: 自定义配置类(可选)
*/
@FeignClient(
value = "weatherClient",
url = "${api.weather.url:https://api.openweathermap.org}", // 从配置读取,默认值兜底
configuration = WeatherFeignConfig.class
)
public interface WeatherClient {
/**
* 获取天气信息
*
* 【注解说明】
* @GetMapping: 对应HTTP GET请求
* @RequestParam: 对应URL查询参数 ?q=Beijing&appid=xxx
*
* 最终请求: GET https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx
*/
@GetMapping("/data/2.5/weather")
WeatherResult getWeather(
@RequestParam("q") String city, // 城市名
@RequestParam("appid") String apiKey // API密钥
);
/**
* 获取天气预报(扩展示例)
*/
@GetMapping("/data/2.5/forecast")
WeatherResult getForecast(
@RequestParam("q") String city,
@RequestParam("appid") String apiKey,
@RequestParam(value = "cnt", required = false) Integer count // 可选参数
);
}
2.WeatherResult.java(响应对象)
java
复制代码
package com.payslip.feign;
import lombok.Data;
import java.util.List;
/**
* 天气API响应对象
*
* 【核心知识点】
* 1. 字段名要和API返回的JSON字段对应
* 2. 可以只定义需要的字段,其他字段会被忽略
* 3. 嵌套对象用内部类或单独的类
*/
@Data
public class WeatherResult {
/** 城市名 */
private String name;
/** 主要天气数据 */
private Main main;
/** 天气描述列表 */
private List<Weather> weather;
/** 风力信息 */
private Wind wind;
/** 响应码(200表示成功) */
private Integer cod;
/**
* 主要数据(温度、湿度等)
*/
@Data
public static class Main {
/** 当前温度(开尔文,需要转换) */
private Double temp;
/** 体感温度 */
private Double feels_like;
/** 最低温度 */
private Double temp_min;
/** 最高温度 */
private Double temp_max;
/** 湿度(百分比) */
private Integer humidity;
/** 气压 */
private Integer pressure;
}
/**
* 天气描述
*/
@Data
public static class Weather {
private Integer id;
/** 天气类型:Clear, Clouds, Rain等 */
private String main;
/** 详细描述 */
private String description;
/** 图标代码 */
private String icon;
}
/**
* 风力信息
*/
@Data
public static class Wind {
/** 风速 */
private Double speed;
/** 风向(角度) */
private Integer deg;
}
// ========== 工具方法 ==========
/**
* 开尔文转摄氏度
*/
public Double getTempCelsius() {
if (main != null && main.getTemp() != null) {
return main.getTemp() - 273.15;
}
return null;
}
/**
* 判断请求是否成功
*/
public boolean isSuccess() {
return cod != null && cod == 200;
}
}
3.WeatherFeignConfig.java(Feign配置)
java
复制代码
package com.payslip.feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Weather Feign 客户端配置
*
* 【核心知识点】
* 1. 配置类不需要加 @Configuration(Feign会自动扫描)
* 2. 可以配置:日志级别、请求拦截器、错误处理器等
*/
@Slf4j
public class WeatherFeignConfig {
/**
* 日志级别
* NONE: 不记录
* BASIC: 只记录请求方法、URL、响应状态码、执行时间
* HEADERS: 记录请求和响应的头信息
* FULL: 记录所有(包括请求体和响应体)
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
/**
* 请求拦截器
* 在每个请求发送前执行,可以添加公共参数、Header等
*/
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
// 添加公共Header
template.header("Accept", "application/json");
template.header("User-Agent", "PayslipApp/1.0");
// 记录请求日志
log.info("Feign请求: {} {}", template.method(), template.url());
};
}
/**
* 错误处理器
* 处理HTTP错误响应
*/
@Bean
public ErrorDecoder errorDecoder() {
return (methodKey, response) -> {
log.error("Feign调用失败: {} - {}", response.status(), methodKey);
switch (response.status()) {
case 401:
return new RuntimeException("API密钥无效");
case 404:
return new RuntimeException("城市不存在");
case 429:
return new RuntimeException("请求过于频繁");
default:
return new RuntimeException("天气服务调用失败: " + response.status());
}
};
}
}
4.WeatherService.java(业务层)
java
复制代码
package com.payslip.service;
import com.payslip.feign.WeatherClient;
import com.payslip.feign.WeatherResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* 天气服务
*/
@Service
@Slf4j
public class WeatherService {
@Autowired
private WeatherClient weatherClient;
/** 从配置文件读取API密钥 */
@Value("${api.weather.key:your_api_key}")
private String apiKey;
/**
* 获取天气信息
*
* @param city 城市名(英文,如:Beijing, Shanghai)
* @return 天气结果
*/
public WeatherResult getWeather(String city) {
log.info("查询天气: city={}", city);
try {
// 调用Feign客户端(像调用本地方法一样!)
WeatherResult result = weatherClient.getWeather(city, apiKey);
if (result.isSuccess()) {
log.info("天气查询成功: {} - {}°C",
result.getName(),
String.format("%.1f", result.getTempCelsius()));
}
return result;
} catch (Exception e) {
log.error("天气查询失败: city={}, error={}", city, e.getMessage());
throw new RuntimeException("天气查询失败: " + e.getMessage());
}
}
/**
* 获取天气信息(带缓存,进阶版)
*/
// @Cacheable(value = "weather", key = "#city", unless = "#result == null")
public WeatherResult getWeatherWithCache(String city) {
return getWeather(city);
}
}
5.WeatherController.java(测试接口)
java
复制代码
package com.payslip.api;
import com.payslip.feign.WeatherResult;
import com.payslip.service.WeatherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 天气接口(测试用)
*/
@RestController
@RequestMapping("/api/weather")
public class WeatherController {
@Autowired
private WeatherService weatherService;
/**
* 查询天气
* GET /api/weather?city=Beijing
*/
@GetMapping
public Map<String, Object> getWeather(@RequestParam String city) {
Map<String, Object> response = new HashMap<>();
try {
WeatherResult result = weatherService.getWeather(city);
response.put("code", 0);
response.put("message", "success");
response.put("data", Map.of(
"city", result.getName(),
"temp", String.format("%.1f°C", result.getTempCelsius()),
"humidity", result.getMain().getHumidity() + "%",
"weather", result.getWeather().get(0).getDescription()
));
} catch (Exception e) {
response.put("code", -1);
response.put("message", e.getMessage());
}
return response;
}
}
6.配置文件(application.yml 或 bootstrap.yml)
java
复制代码
# 天气API配置
api:
weather:
url: https://api.openweathermap.org
key: your_api_key_here # 去 openweathermap.org 注册获取
# Feign日志配置(需要配合logging.level使用)
logging:
level:
com.payslip.feign.WeatherClient: DEBUG
7.核心知识点总结
java
复制代码
Feign 注解对照表
| 注解 | 作用 | 示例 | |------|------|------|
@FeignClient | 声明Feign客户端 | @FeignClient(value="xxx", url="xxx")
@GetMapping | GET请求 | @GetMapping("/path")
@PostMapping | POST请求 | @PostMapping("/path")
@RequestParam | URL查询参数 | ?key=value
@PathVariable | 路径参数 | /user/{id}
@RequestBody | 请求体(JSON) | POST的body
@RequestHeader | 请求头 | Authorization: xxx
8.Feign 工作流程
java
复制代码
1. 你调用接口方法
weatherClient.getWeather("Beijing", "xxx")
↓
2. Feign动态代理拦截
↓
3. 解析注解,构建HTTP请求
GET https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx
↓
4. RequestInterceptor 添加公共Header
↓
5. 发送HTTP请求
↓
6. 接收响应,JSON反序列化为 WeatherResult
↓
7. 返回结果
9.常见问题
java
复制代码
404错误 → 检查URL和路径是否正确
序列化失败 → 检查响应对象字段名是否和JSON对应
连接超时 → 配置Feign超时时间
找不到Bean → 确保启动类加了 @EnableFeignClients
10.启用 Feign
java
复制代码
确保启动类有这个注解:
@SpringBootApplication
@EnableFeignClients // 启用Feign客户端扫描
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}