OpenFeign调用第三方接口更加优雅
OpenFeign 提供了一种声明式的 HTTP 客户端方式,通过接口和注解的方式调用第三方 API,将远程 HTTP 调用转化为简单的 Java 方法调用,从而大大简化了代码的编写和维护。关键点包括:
-
简单易用:只需声明接口及对应的映射关系,开发者无需关注底层的 HTTP 交互细节。
-
统一配置:可以通过配置类统一管理请求头、超时、日志级别等设置,确保各个调用场景的一致性。
-
优雅的容错机制:结合 fallback 机制和异常处理,可在调用失败时返回默认数据或进行相应的降级处理,提升系统的健壮性。
-
灵活的日志记录:通过 AOP 或自定义注解的方式,可以灵活控制是否记录调用日志,并将关键信息(如请求参数、响应结果、耗时等)记录到 MongoDB 等持久化存储,便于后期追踪和问题排查。
OpenFeign 实现
-
OpenFeign 接口定义:
ILServiceClient
示例,部分方法使用了注解 -
Feign 配置:
LFeignConfig
- Fallback/FallbackFactory:通过在 FeignClient 注解中添加 fallback 或 fallbackFactory 属性,实现降级策略。
-
-
MongoDB 日志实体:实体类
FeignCallLog
,记录接口调用的各种信息 -
AOP 切面:
FeignCallLogAspect
拦截标记方法,记录调用前后状态、耗时、异常信息,并写入 MongoDB
Feign 配置:LFeignConfig.java
import feign.Logger;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LFeignConfig {
// 示例:统一添加请求头(如鉴权信息)
@Bean
public RequestInterceptor headerInterceptor() {
return requestTemplate -> {
// 如需要:requestTemplate.header("Authorization", "Bearer your_token");
requestTemplate.header("X-Custom-Header", "customValue");
};
}
// 设置 Feign 的日志级别为 FULL,便于排查问题
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
Feign 接口定义:ILServiceClient.java
import com.example.demo.annotation.LogFeignCall; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import java.util.Map; @FeignClient(name = "lServiceClient", path = "${api.l.path:lService}", url = "${api.l.url:}", configuration = com.example.demo.config.LFeignConfig.class, fallback = LServiceFallback.class) public interface ILServiceClient { @LogFeignCall("创建") @PostMapping(value = "/city/create", consumes = MediaType.APPLICATION_JSON_VALUE) String create(@RequestBody Map<String, Object> json, @RequestHeader Map<String, String> headers); @PostMapping(value = "/city/delete", consumes = MediaType.APPLICATION_JSON_VALUE) String delete(@RequestBody Map<String, Object> json, @RequestHeader Map<String, String> headers); }
容错机制:fallback 类
@Component public class LServiceFallback implements ILServiceClient { @Override public String create(Map<String, Object> json, Map<String, String> headers) { return "Fallback: 创建制证失败"; } @Override public String delete(Map<String, Object> json, Map<String, String> headers) { return "Fallback: 删除附件失败"; } }
自定义日志注解:LogFeignCall.java
import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogFeignCall { // 可选描述信息,如接口名称或用途 String value() default ""; }
日志实体类:FeignCallLog.java
import java.time.LocalDateTime; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.Data; @Data @Document("feign_call_log") public class FeignCallLog { @Id private String id; /** 接口描述(通过注解传入) */ private String apiName; /** 调用方法:类名.方法名 */ private String methodName; /** 请求参数(序列化后的字符串或对象) */ private Object requestArgs; /** 响应内容 */ private Object response; /** 调用是否成功 */ private boolean success; /** HTTP 状态码,如自定义或标准状态码 */ private int httpStatus; /** 异常信息(如果发生异常) */ private String errorMessage; /** 调用耗时(毫秒) */ private Long elapsedTimeMs; /** 日志记录时间 */ private LocalDateTime timestamp; }
Feign日志AOP 切面:FeignCallLogAspect.java
import com.example.demo.annotation.LogFeignCall; import com.example.demo.entity.FeignCallLog; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.time.LocalDateTime; import java.util.Arrays; @Slf4j @Aspect @Component @RequiredArgsConstructor public class FeignCallLogAspect { private final MongoTemplate mongoTemplate; @Around("@annotation(logFeignCall)") public Object around(ProceedingJoinPoint joinPoint, LogFeignCall logFeignCall) throws Throwable { FeignCallLog logEntry = new FeignCallLog(); logEntry.setApiName(logFeignCall.value()); logEntry.setTimestamp(LocalDateTime.now()); Signature signature = joinPoint.getSignature(); if (signature instanceof MethodSignature) { MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); String className = method.getDeclaringClass().getSimpleName(); logEntry.setMethodName(className + "." + method.getName()); } else { logEntry.setMethodName(signature.toString()); } // 序列化请求参数(这里只是简单调用 Arrays.toString(),可根据实际情况自定义) Object[] args = joinPoint.getArgs(); logEntry.setRequestArgs(args != null ? Arrays.toString(args) : "无参数"); long start = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); logEntry.setResponse(result != null ? result.toString() : "null"); logEntry.setSuccess(true); logEntry.setHttpStatus(HttpStatus.OK.value()); return result; } catch (Exception ex) { logEntry.setSuccess(false); logEntry.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); logEntry.setErrorMessage(ExceptionUtils.getStackTrace(ex)); throw ex; } finally { long end = System.currentTimeMillis(); logEntry.setElapsedTimeMs(end - start); // 将日志记录到 MongoDB mongoTemplate.save(logEntry); log.debug("Feign调用日志记录: {}", logEntry); } } }