SpringCloud的声明式服务调用 Feign 全面解析

Feign基本用法-Feign 概述 阅读详情

一、Feign 介绍

1. 什么是 Feign?

Feign 是 Spring Cloud 提供的声明式 HTTP 客户端,让服务调用变得更加简单优雅。

核心特性:

  • ✅ 声明式调用:只需定义接口和注解,无需实现具体调用逻辑

  • ✅ 基于注解:支持 Spring MVC 注解,学习成本低

  • ✅ 集成 Ribbon:内置客户端负载均衡

  • ✅ 集成 Hystrix:支持服务熔断和降级(旧版本)

  • ✅ 可插拔编码器/解码器:支持自定义序列化

  • ✅ 请求/响应压缩:支持 GZIP 压缩

  • ✅ 日志配置:支持请求/响应日志

核心优势对比:

特性

RestTemplate + Ribbon

Feign

调用方式

编程式,硬编码

声明式,接口定义

代码简洁性

中等

极高

可读性

一般

优秀

维护性

一般

优秀

负载均衡

需要手动集成

内置集成

注解支持

有限

完整 Spring MVC 注解

一句话总结:​ Feign = RestTemplate + Ribbon,但更加优雅简洁!

2. Feign 依赖

<!-- Spring Cloud 2020+ 使用 OpenFeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- 如果使用旧版本 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>

二、Feign 快速入门

1. 项目结构

springcloud-feign-demo/
├── feign-provider/           # 服务提供者
├── feign-interface/          # Feign 接口模块
├── feign-consumer/           # 服务消费者
└── pom.xml                  # 父工程

2. 服务提供者(Provider)

ProviderApplication.java

@SpringBootApplication
@EnableDiscoveryClient
public class FeignProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignProviderApplication.class, args);
    }
}

ProviderController.java

@RestController
@RequestMapping("/provider")
@Slf4j
public class ProviderController {
    
    @Value("${server.port}")
    private String port;
    
    /**
     * GET 请求:查询用户
     */
    @GetMapping("/getUserById/{id}")
    public User getUserById(@PathVariable Integer id) {
        log.info("Provider [{}] 收到请求,用户ID: {}", port, id);
        return User.builder()
                .id(id)
                .name("用户-" + id)
                .age(25)
                .email("user" + id + "@example.com")
                .phone("1380013800" + (id % 10))
                .createdAt(new Date())
                .providerPort(port)
                .build();
    }
    
    /**
     * POST 请求:创建用户
     */
    @PostMapping("/createUser")
    public User createUser(@RequestBody User user) {
        log.info("Provider [{}] 创建用户: {}", port, user.getName());
        user.setId(new Random().nextInt(1000));
        user.setCreatedAt(new Date());
        user.setProviderPort(port);
        return user;
    }
    
    /**
     * GET 请求:搜索用户
     */
    @GetMapping("/search")
    public List<User> searchUsers(@RequestParam String keyword,
                                  @RequestParam(defaultValue = "1") Integer page,
                                  @RequestParam(defaultValue = "10") Integer size) {
        log.info("Provider [{}] 搜索用户: {}, 页码: {}, 大小: {}", 
                port, keyword, page, size);
        
        List<User> users = new ArrayList<>();
        for (int i = 1; i <= 3; i++) {
            users.add(User.builder()
                    .id(i)
                    .name(keyword + "-用户" + i)
                    .age(20 + i)
                    .providerPort(port)
                    .build());
        }
        return users;
    }
}

application.yml

server:
  port: 8081  # 可启动多个实例,如 8082, 8083
spring:
  application:
    name: feign-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: public
        group: DEFAULT_GROUP
        metadata:
          version: 1.0.0

User.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    private String phone;
    private Date createdAt;
    private String providerPort;  // 用于区分不同实例
}

3. Feign 接口模块

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <parent>
        <artifactId>springcloud-feign-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0.0</version>
    </parent>
    
    <modelVersion>4.0.0</modelVersion>
    <artifactId>feign-interface</artifactId>
    
    <dependencies>
        <!-- Feign 核心依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        
        <!-- 公共模块(包含 User 类) -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>springcloud-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        
        <!-- Web 支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

UserFeignClient.java

/**
 * Feign 客户端接口
 * 重要:接口路径与服务提供者的 Controller 路径一致
 */
@FeignClient(
    value = "feign-provider",  // 服务名称,对应 Nacos 中的服务
    path = "/provider",         // 统一前缀,避免在每个方法上重复
    configuration = FeignConfig.class,  // 自定义配置
    fallbackFactory = UserFeignFallbackFactory.class  // 降级处理
)
public interface UserFeignClient {
    
    /**
     * RESTful 风格传参
     * 对应:GET /provider/getUserById/{id}
     */
    @GetMapping("/getUserById/{id}")
    User getUserById(@PathVariable("id") Integer id);
    
    /**
     * 查询参数传参
     * 对应:GET /provider/search?keyword=xxx&page=1&size=10
     */
    @GetMapping("/search")
    List<User> searchUsers(
        @RequestParam("keyword") String keyword,
        @RequestParam(value = "page", defaultValue = "1") Integer page,
        @RequestParam(value = "size", defaultValue = "10") Integer size
    );
    
    /**
     * POJO 对象传参(JSON)
     * 对应:POST /provider/createUser
     */
    @PostMapping("/createUser")
    User createUser(@RequestBody User user);
    
    /**
     * PUT 请求示例
     */
    @PutMapping("/updateUser/{id}")
    User updateUser(@PathVariable("id") Integer id, @RequestBody User user);
    
    /**
     * DELETE 请求示例
     */
    @DeleteMapping("/deleteUser/{id}")
    void deleteUser(@PathVariable("id") Integer id);
    
    /**
     * 多参数复杂查询示例
     */
    @GetMapping("/complexQuery")
    List<User> complexQuery(
        @RequestParam String name,
        @RequestParam(required = false) Integer minAge,
        @RequestParam(required = false) Integer maxAge,
        @RequestParam(required = false) String email
    );
}

FeignConfig.java - 自定义配置

@Configuration
public class FeignConfig {
    
    /**
     * 配置日志级别
     * NONE: 不记录任何日志
     * BASIC: 仅记录请求方法和URL以及响应状态码和执行时间
     * HEADERS: 记录BASIC级别的基础上,记录请求和响应的header
     * FULL: 记录请求和响应的header、body和元数据
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
    /**
     * 配置超时时间
     */
    @Bean
    public Request.Options options() {
        return new Request.Options(
            5000,  // 连接超时:5秒
            10000  // 读取超时:10秒
        );
    }
    
    /**
     * 配置拦截器(添加认证头等)
     */
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            // 添加认证头
            requestTemplate.header("Authorization", "Bearer token-xxx");
            // 添加自定义头
            requestTemplate.header("X-Request-Source", "feign-client");
            // 添加时间戳
            requestTemplate.header("X-Timestamp", String.valueOf(System.currentTimeMillis()));
        };
    }
    
    /**
     * 配置编解码器
     */
    @Bean
    public Encoder feignEncoder() {
        return new SpringEncoder(new ObjectFactory<>() {
            @Override
            public HttpMessageConverters getObject() {
                return new HttpMessageConverters(
                    new MappingJackson2HttpMessageConverter()
                );
            }
        });
    }
    
    /**
     * 配置错误解码器
     */
    @Bean
    public ErrorDecoder errorDecoder() {
        return new FeignErrorDecoder();
    }
}

FeignErrorDecoder.java - 错误处理

@Component
public class FeignErrorDecoder implements ErrorDecoder {
    
    private final ErrorDecoder defaultErrorDecoder = new Default();
    
    @Override
    public Exception decode(String methodKey, Response response) {
        if (response.status() >= 400 && response.status() <= 499) {
            // 客户端错误
            return new FeignClientException(
                response.status(),
                "客户端错误: " + response.reason()
            );
        } else if (response.status() >= 500) {
            // 服务端错误
            return new FeignServerException(
                response.status(),
                "服务端错误: " + response.reason()
            );
        }
        
        return defaultErrorDecoder.decode(methodKey, response);
    }
    
    // 自定义异常类
    public static class FeignClientException extends RuntimeException {
        private final int status;
        
        public FeignClientException(int status, String message) {
            super(message);
            this.status = status;
        }
        
        public int getStatus() {
            return status;
        }
    }
    
    public static class FeignServerException extends RuntimeException {
        private final int status;
        
        public FeignServerException(int status, String message) {
            super(message);
            this.status = status;
        }
        
        public int getStatus() {
            return status;
        }
    }
}

UserFeignFallbackFactory.java - 服务降级

@Component
@Slf4j
public class UserFeignFallbackFactory implements FallbackFactory<UserFeignClient> {
    
    @Override
    public UserFeignClient create(Throwable cause) {
        log.error("Feign客户端调用失败,触发降级", cause);
        
        return new UserFeignClient() {
            @Override
            public User getUserById(Integer id) {
                return User.builder()
                        .id(id)
                        .name("降级用户")
                        .age(0)
                        .email("fallback@example.com")
                        .build();
            }
            
            @Override
            public List<User> searchUsers(String keyword, Integer page, Integer size) {
                return Collections.singletonList(
                    User.builder()
                        .id(-1)
                        .name("降级搜索结果: " + keyword)
                        .age(0)
                        .build()
                );
            }
            
            @Override
            public User createUser(User user) {
                return User.builder()
                        .id(-1)
                        .name("创建失败,服务降级")
                        .build();
            }
            
            @Override
            public User updateUser(Integer id, User user) {
                return user;
            }
            
            @Override
            public void deleteUser(Integer id) {
                log.warn("删除用户失败,用户ID: {}", id);
            }
            
            @Override
            public List<User> complexQuery(String name, Integer minAge, Integer maxAge, String email) {
                return Collections.emptyList();
            }
        };
    }
}

4. 服务消费者(Consumer)

ConsumerApplication.java

/**
 * 启动类配置
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(
    basePackages = "com.example.feign.client",  // 指定 Feign 接口扫描包
    defaultConfiguration = GlobalFeignConfig.class  // 全局 Feign 配置
)
public class FeignConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignConsumerApplication.class, args);
    }
}

ConsumerController.java

@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
    
    @Autowired
    private UserFeignClient userFeignClient;
    
    /**
     * 测试 GET 请求
     */
    @GetMapping("/user/{id}")
    public ResponseData<User> getUserById(@PathVariable Integer id) {
        log.info("消费者查询用户,ID: {}", id);
        try {
            User user = userFeignClient.getUserById(id);
            return ResponseData.success(user);
        } catch (Exception e) {
            log.error("查询用户失败", e);
            return ResponseData.error(500, "查询失败: " + e.getMessage());
        }
    }
    
    /**
     * 测试 POST 请求
     */
    @PostMapping("/user")
    public ResponseData<User> createUser(@RequestBody User user) {
        log.info("消费者创建用户: {}", user.getName());
        User createdUser = userFeignClient.createUser(user);
        return ResponseData.success(createdUser);
    }
    
    /**
     * 测试查询参数
     */
    @GetMapping("/search")
    public ResponseData<List<User>> searchUsers(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size) {
        
        log.info("消费者搜索用户,关键词: {}, 页码: {}, 大小: {}", keyword, page, size);
        List<User> users = userFeignClient.searchUsers(keyword, page, size);
        return ResponseData.success(users);
    }
    
    /**
     * 测试负载均衡
     * 调用多次查看不同实例的响应
     */
    @GetMapping("/testLoadBalance")
    public ResponseData<List<String>> testLoadBalance() {
        List<String> results = new ArrayList<>();
        
        for (int i = 0; i < 5; i++) {
            User user = userFeignClient.getUserById(i + 1);
            results.add(String.format("第 %d 次调用 -> 服务端口: %s", 
                i + 1, user.getProviderPort()));
            
            // 模拟延迟
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        return ResponseData.success(results);
    }
    
    /**
     * 测试复杂查询
     */
    @GetMapping("/complex")
    public ResponseData<List<User>> complexQuery(
            @RequestParam String name,
            @RequestParam(required = false) Integer minAge,
            @RequestParam(required = false) Integer maxAge) {
        
        List<User> users = userFeignClient.complexQuery(name, minAge, maxAge, null);
        return ResponseData.success(users);
    }
}

ResponseData.java - 统一响应

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResponseData<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    
    public static <T> ResponseData<T> success(T data) {
        return ResponseData.<T>builder()
                .code(200)
                .message("成功")
                .data(data)
                .timestamp(System.currentTimeMillis())
                .build();
    }
    
    public static <T> ResponseData<T> error(Integer code, String message) {
        return ResponseData.<T>builder()
                .code(code)
                .message(message)
                .timestamp(System.currentTimeMillis())
                .build();
    }
}

GlobalFeignConfig.java - 全局配置

@Configuration
public class GlobalFeignConfig {
    
    /**
     * 全局 Feign 配置
     */
    @Bean
    public Contract feignContract() {
        return new SpringMvcContract();
    }
    
    /**
     * 配置连接池
     */
    @Bean
    public Client feignClient() {
        return new Client.Default(
            new PoolingHttpClientConnectionManager(),
            new DefaultHttpRequestRetryHandler(3, true)  // 重试3次
        );
    }
    
    /**
     * 配置解码器
     */
    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
    }
    
    private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
        return () -> new HttpMessageConverters(
            new StringHttpMessageConverter(StandardCharsets.UTF_8),
            new MappingJackson2HttpMessageConverter()
        );
    }
}

application.yml

server:
  port: 8080
spring:
  application:
    name: feign-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        
# Feign 配置
feign:
  client:
    config:
      default:  # 全局默认配置
        connectTimeout: 5000   # 连接超时时间
        readTimeout: 10000     # 读取超时时间
        loggerLevel: full      # 日志级别
        requestInterceptors:   # 拦截器
          - com.example.feign.interceptor.AuthRequestInterceptor
      feign-provider:  # 针对特定服务的配置
        connectTimeout: 3000
        readTimeout: 5000
        
  # 启用压缩
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true
      
  # 启用 Hystrix(旧版本)
  hystrix:
    enabled: true
    
  # 启用 Sentinel(新版本推荐)
  sentinel:
    enabled: true
    
# 日志配置
logging:
  level:
    com.example.feign.client.UserFeignClient: DEBUG  # Feign 接口日志

三、Feign 高级功能

1. 文件上传

@FeignClient(name = "file-service")
public interface FileUploadFeignClient {
    
    /**
     * 单文件上传
     */
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    String uploadFile(@RequestPart("file") MultipartFile file);
    
    /**
     * 多文件上传
     */
    @PostMapping(value = "/uploadMultiple", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    String uploadFiles(@RequestPart("files") MultipartFile[] files);
    
    /**
     * 文件 + 参数上传
     */
    @PostMapping(value = "/uploadWithParam", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    String uploadFileWithParam(
        @RequestPart("file") MultipartFile file,
        @RequestParam("description") String description
    );
}

2. 自定义请求头

@FeignClient(name = "auth-service")
public interface AuthFeignClient {
    
    /**
     * 动态请求头
     */
    @GetMapping("/userInfo")
    UserInfo getUserInfo(@RequestHeader("Authorization") String token);
    
    /**
     * 多个请求头
     */
    @PostMapping("/auth")
    AuthResult authenticate(
        @RequestHeader("X-Client-Id") String clientId,
        @RequestHeader("X-Client-Secret") String clientSecret,
        @RequestBody AuthRequest request
    );
}

3. 继承特性

// 基础接口
public interface BaseFeignClient<T> {
    
    @GetMapping("/{id}")
    T getById(@PathVariable("id") Long id);
    
    @PostMapping
    T create(@RequestBody T entity);
    
    @PutMapping("/{id}")
    T update(@PathVariable("id") Long id, @RequestBody T entity);
    
    @DeleteMapping("/{id}")
    void delete(@PathVariable("id") Long id);
}

// 具体实现
@FeignClient(name = "user-service")
public interface UserFeignClient extends BaseFeignClient<User> {
    
    // 可以添加额外的方法
    @GetMapping("/search")
    List<User> search(@RequestParam("keyword") String keyword);
}

4. 请求/响应日志

@Configuration
@Slf4j
public class FeignLogConfig {
    
    /**
     * 自定义日志
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
    /**
     * 日志拦截器
     */
    @Bean
    public RequestInterceptor logRequestInterceptor() {
        return template -> {
            log.info("Feign请求URL: {}", template.url());
            log.info("Feign请求方法: {}", template.method());
            log.info("Feign请求头: {}", template.headers());
            
            if (template.body() != null) {
                log.info("Feign请求体: {}", new String(template.body()));
            }
        };
    }
}

四、Feign 工作原理

1. 启动流程

/**
 * @EnableFeignClients 工作流程:
 * 1. 扫描被 @FeignClient 注解的接口
 * 2. 为每个接口创建动态代理
 * 3. 注册到 Spring 容器
 * 4. 生成 RequestTemplate
 * 5. 发起 HTTP 请求
 */
@EnableFeignClients
↓
FeignClientsRegistrar.registerFeignClients()
    ↓
ClassPathScanningCandidateComponentProvider.scan()
    ↓
ReflectiveFeign.newInstance()
    ↓
InvocationHandler.invoke()
    ↓
SynchronousMethodHandler.invoke()
    ↓
RequestTemplate.create()  // 创建请求模板
    ↓
Client.execute()  // 执行HTTP请求

2. 动态代理实现

// Feign 通过 JDK 动态代理实现
public class ReflectiveFeign extends Feign {
    
    public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> methodToHandler = new LinkedHashMap<>();
        
        for (Method method : target.type().getMethods()) {
            if (method.getDeclaringClass() == Object.class) {
                continue;
            }
            
            // 创建方法处理器
            methodToHandler.put(method.getName(), 
                new SynchronousMethodHandler(target, method));
        }
        
        // 创建动态代理
        InvocationHandler handler = (proxy, method, args) -> {
            MethodHandler methodHandler = methodToHandler.get(method.getName());
            return methodHandler.invoke(args);
        };
        
        return (T) Proxy.newProxyInstance(
            target.type().getClassLoader(),
            new Class<?>[]{target.type()},
            handler
        );
    }
}

五、Feign 传参方式详解

1. 查询参数(? 传参)

@FeignClient("user-service")
public interface UserFeign {
    
    // 基本类型参数
    @GetMapping("/users")
    List<User> getUsers(@RequestParam("page") Integer page,
                        @RequestParam("size") Integer size);
    
    // 可选参数
    @GetMapping("/search")
    List<User> search(@RequestParam(value = "keyword", required = false) String keyword);
    
    // 默认值参数
    @GetMapping("/list")
    List<User> list(@RequestParam(value = "sort", defaultValue = "id") String sort);
    
    // 数组/列表参数
    @GetMapping("/byIds")
    List<User> getByIds(@RequestParam("ids") List<Long> ids);
    
    // Map参数
    @GetMapping("/query")
    List<User> query(@RequestParam Map<String, Object> params);
}

2. RESTful 路径参数

@FeignClient("user-service")
public interface UserFeign {
    
    // 单个路径参数
    @GetMapping("/users/{id}")
    User getById(@PathVariable("id") Long id);
    
    // 多个路径参数
    @GetMapping("/departments/{deptId}/users/{userId}")
    User getByDeptAndUser(@PathVariable("deptId") Long deptId,
                          @PathVariable("userId") Long userId);
    
    // 正则表达式限制
    @GetMapping("/users/{id:[0-9]+}")
    User getByIdWithRegex(@PathVariable("id") Long id);
    
    // 路径参数 + 查询参数
    @GetMapping("/users/{id}/posts")
    List<Post> getUserPosts(@PathVariable("id") Long id,
                            @RequestParam("status") String status);
}

3. 请求体参数(JSON)

@FeignClient("user-service")
public interface UserFeign {
    
    // 简单对象
    @PostMapping("/users")
    User createUser(@RequestBody User user);
    
    // 复杂嵌套对象
    @PutMapping("/users/{id}")
    User updateUser(@PathVariable("id") Long id, 
                    @RequestBody UserDTO userDTO);
    
    // 多个对象(不推荐,建议封装)
    @PostMapping("/batch")
    List<User> batchCreate(@RequestBody BatchCreateRequest request);
    
    // 集合参数
    @PostMapping("/users/batch")
    List<User> createUsers(@RequestBody List<User> users);
}

4. 表单参数

@FeignClient("user-service")
public interface UserFeign {
    
    // 表单提交
    @PostMapping(value = "/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    LoginResult login(@RequestParam("username") String username,
                      @RequestParam("password") String password);
    
    // 混合参数
    @PostMapping(value = "/register", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    RegisterResult register(@RequestParam("username") String username,
                            @RequestParam("password") String password,
                            @RequestParam("email") String email);
}

5. 请求头参数

@FeignClient("user-service")
public interface UserFeign {
    
    // 单个请求头
    @GetMapping("/profile")
    UserProfile getProfile(@RequestHeader("Authorization") String token);
    
    // 多个请求头
    @GetMapping("/secure")
    SecureData getSecureData(@RequestHeader("X-API-Key") String apiKey,
                             @RequestHeader("X-Client-Version") String version);
    
    // Map形式请求头
    @PostMapping("/custom")
    void customRequest(@RequestHeader Map<String, String> headers,
                       @RequestBody RequestData data);
}

6. Cookie 参数

@FeignClient("user-service")
public interface UserFeign {
    
    @GetMapping("/session")
    SessionInfo getSession(@CookieValue("JSESSIONID") String sessionId);
    
    @PostMapping("/login")
    void login(@CookieValue(value = "rememberMe", defaultValue = "false") String remember,
               @RequestBody LoginRequest request);
}

六、最佳实践

1. 接口设计规范

// 1. 统一接口前缀
@FeignClient(name = "user-service", path = "/api/v1/users")
public interface UserFeignClient {
    // 方法不需要重复写路径前缀
}

// 2. 统一响应格式
public interface BaseFeignClient<T, ID> {
    ResponseData<T> getById(ID id);
    ResponseData<List<T>> list();
    ResponseData<T> create(T entity);
    ResponseData<T> update(ID id, T entity);
    ResponseData<Void> delete(ID id);
}

// 3. 异常处理
@ControllerAdvice
public class FeignExceptionHandler {
    
    @ExceptionHandler(FeignException.class)
    public ResponseEntity<ErrorResponse> handleFeignException(FeignException e) {
        // 统一处理Feign异常
    }
}

2. 性能优化配置

# application.yml
feign:
  client:
    config:
      default:
        connectTimeout: 3000
        readTimeout: 10000
        loggerLevel: basic
        
  # 启用HTTP连接池
  okhttp:
    enabled: true
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50
    
# 启用压缩
compression:
  request:
    enabled: true
    mime-types: text/xml,application/xml,application/json
  response:
    enabled: true
    
# 启用缓存
cache:
  enabled: true
  cacheNames: feign-cache
  ttl: 300000  # 5分钟

3. 监控与日志

@Configuration
@Slf4j
public class FeignMonitorConfig {
    
    @Bean
    public RequestInterceptor monitorInterceptor() {
        return template -> {
            long startTime = System.currentTimeMillis();
            template.requestTemplate().attribute("startTime", startTime);
        };
    }
    
    @Bean
    public ResponseInterceptor responseInterceptor() {
        return (response, request) -> {
            Long startTime = (Long) request.requestTemplate().attribute("startTime");
            if (startTime != null) {
                long duration = System.currentTimeMillis() - startTime;
                log.info("Feign调用耗时: {}ms, URL: {}", duration, request.url());
                
                // 监控指标
                Metrics.counter("feign_request_duration", 
                    "url", request.url(),
                    "status", String.valueOf(response.status()))
                    .record(duration);
            }
            return response;
        };
    }
}

七、常见问题与解决方案

1. 404 错误

# 问题:服务找不到
# 解决方案:
# 1. 检查服务名是否正确
# 2. 检查服务是否注册到注册中心
# 3. 检查接口路径是否正确

# 开启详细日志
logging:
  level:
    org.springframework.cloud.openfeign: DEBUG
    feign: DEBUG

2. 超时问题

// 方案1:配置文件调整
@Configuration
public class FeignTimeoutConfig {
    
    @Bean
    public Request.Options options() {
        return new Request.Options(10000, 30000);  // 连接10s,读取30s
    }
}

// 方案2:使用Hystrix
@Configuration
public class HystrixConfig {
    
    @Bean
    public Setter setter() {
        return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("FeignGroup"))
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                .withExecutionTimeoutInMilliseconds(10000));
    }
}

3. 序列化问题

// 配置自定义编解码器
@Bean
public Encoder feignEncoder() {
    return new SpringEncoder(feignHttpMessageConverter());
}

@Bean
public Decoder feignDecoder() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.registerModule(new JavaTimeModule());
    
    return new ResponseEntityDecoder(new SpringDecoder(
        () -> new HttpMessageConverters(
            new MappingJackson2HttpMessageConverter(mapper)
        )
    ));
}

总结

Feign 作为声明式的 HTTP 客户端,极大地简化了微服务之间的调用。通过本指南,你应该能够:

  1. ✅ 理解 Feign 的核心概念和工作原理

  2. ✅ 掌握各种传参方式的正确使用

  3. ✅ 配置自定义的 Feign 客户端

  4. ✅ 实现服务降级和异常处理

  5. ✅ 优化 Feign 的性能和稳定性

  6. ✅ 解决常见的 Feign 使用问题

记住:Feign = 接口声明 + 注解,让服务调用变得像调用本地方法一样简单!

SpringCloud之OpenFeign入门与实战系列 本文主要介绍OpenFeign的超时重试机制,以及如何自定义超时重试机制。 阅读详情

相关推荐

Feign解读

Feign 简介 Feign 是 Netflix 开发的声明式、模板化的 HTTP 客户端,它使得 HTTP 请求变得更简单,Feign 底层的使用的 HTTP 通信框架是 HttpClient。使用 Feign 只需要创建一个接口并添加注解。它具有可插拔注释支持,包括 Feign 注解和 JAX-RS 注解、Feign 还支持可插拔编码器和解码器,Spring Cloud 增加了对 Spring MVC 注释的支持。Feign 默认集成了Ribbon和 Hystrix,并和 Eureka 结合,默认实现了

桐花思雨 1109

Feign简介与简单应用

一 点睛 Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。 在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。 Spring Cloud对Feign进行了增强,使Feign支持了Sprin...

实践求真知 19万+

半导体制造中的通孔缺陷与冗余通孔技术解析

通孔(Via)是半导体制造中连接不同金属层的关键结构,其可靠性直接影响芯片良率。随着工艺节点演进至28nm及更先进制程,通孔缺陷导致的良率损失日益显著,主要表现为完全开路和高阻态两种失效模式。冗余通孔技术通过在关键位置增加备用连接,可有效提升芯片可靠性,其实现涉及物理冗余与电气冗余两种方案。该技术在7nm/5nm工艺中已证明可将通孔相关缺陷率降低10-100倍,但面临设计规则复杂性、规模处理效率和动态合规验证等工业化挑战。智能双切通孔插入流程通过动态DRC规则解析、奇偶层级并行处理和增量式验证等创新方法,显

weixin_35982453的博客 382

Feign基本使用(超详细)

当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下。提示:以下是本篇文章正文内容,下面案例可供参考Feign是Spring Cloud提供的声明式、模板化的HTTP客户端, 它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。Spring Cloud集成Feign并对其进行了增强,使Feign支持了Spring MVC注解;...

F0217911的博客 4万+

1.30 Cubemx_STM32H743 SD_Card纳入文件管理系统

Cubemx_STM32H743 SD_Card纳入文件管理系统1、简介2、CubeMX配置 1、简介 和前面文章原理其实一样的。 1.29 Cubemx_STM32F429\H743 nandflash、spifalsh纳入文件管理系统 现在一并将SD卡、NandFlash、SPIFlash一并作为文件管理。 下面直接介绍SDcard,其他参考前面文章。 2、CubeMX配置 ...

oDuanYanGuHong的博客 1079

ICC II 时钟树综合(CTS)前奏:从约束定义到NDR规则的全方位设置指南

本文详细介绍了ICC II中时钟树综合(CTS)的前期准备工作,包括约束定义、NDR规则设置和时钟平衡点控制等关键步骤。通过实际案例和代码示例,指导工程师如何优化时钟偏斜、插入延迟和信号质量,确保芯片设计的时序收敛和性能稳定。特别强调了非默认布线规则(NDR)在时钟网络设计中的重要性。

weixin_42530458的博客 212

微服务实战SpringCloud之Feign源码分析

上一篇简单介绍了springcloud声明式服务调用Feign的使用,接下来分析下Feign的源码,具体实现及为什么如此实现。 启动时Feign的处理 启动类上使用了@EnableFeignClients注解,我们来看下这个注解在哪里使用了,使用idea只要在EnableFeignClients类上按住command同时点击类名就可以查看到这个类在哪里使用了,发现除了启动类,只在FeignClie...

shuaiteng_wang的博客 748

kettle信息发送钉钉

由于其他的作业比较多,想要在job中加入一个转化发送消息到钉钉,其他的配置说明直接获取系统的信息。这样也避免直接修改其他的转换和大量的工作量。 步骤: 1、新建一个转换 拉取获取系统信息、获取变量、JavaScript代码、获取access_token组件。 2、获取系统信息设置 拿到需要的信息,比如作业的读写更新删除行数,job的日志内容 这个日志内容同在作业执行页面下输出的日志,所以可以在执行作业时选择是详细日志、基本日志还是错误日志。 3、获取变量 获取变量主要是想要去拿到job的名称,文件名。按

Deng_7788的博客 1446

SpringCloud 声明式服务调用 —— Feign 全面解析(入门 + 原理 + 优化)

指定调用的服务名接口方法写法 ≈ Controller 接口写法@Bean// 权重✅ Feign 是声明式、简化微服务调用的神器​✅ 底层集成 Ribbon,支持负载均衡✅ 通过接口 + 注解即可完成服务调用✅ 实际项目中建议开启日志、连接池、压缩、超时配置📌适合人群:Spring Cloud 初学者 / 微服务入门📌推荐搭配如果你愿意,我也可以帮你再补一篇:👉《Feign vs RestTemplate 实战对比》或《Feign 源码深度解析》

StackNoOverflow的博客 269

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

输入电压采集上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, 由于二极 管的正向导通特性, 用户反接电源时亦不会烧坏后级电路。图中 R8、R9 组成分压电路, 由单片机 AD 采集 IO 口 P14 采集 AD 值, 并由计算公式电压 =133300(AD/4096)mV;

Naiva的博客 1994

如何利用air780e与stm32c8c6进行通信实现给手机发送一条英文短信的功能?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 1237

SpringCloud中的远端服务调用(2种方式)

一.基于RestTemplate对象实现远端服务调用 步骤1:在服务消费方的启动类中添加如下方法,用于创建RestTemplate对象 @Bean public RestTemplate restTemplate(){//基于此对象实现远端服务调用 return new RestTemplate(); } 步骤2:定义服务消费方Controller层,在此对象方法内部实现远端服务调用 package com.jt.consumer.controller; import org.s..

cfdsscv的博客 2797

044、镜头脏污与异物检测:产线 AOI 检测方案与 ISP 脏污补偿

文章摘要(148字) 本文针对镜头生产中的脏污检测问题,提出产线AOI检测与ISP补偿结合的解决方案。产线端采用明场、暗场、偏振三通道联合检测技术,将油污漏检率从8.3%降至0.2%。ISP端通过RAW域局部梯度分析和频域补偿算法,动态修复产线漏检的微小脏污。作者结合实战案例,详细分析了传统灰度阈值法的局限性,并给出多模态检测的代码实现要点,强调必须建立"产线拦截+终端补偿"的双重防护机制。

sundong_81的博客 227

SpringCloud踩坑记(五)SpringCloud Feign使用

前言 先前我们学习了Spring Cloud注册服务和使用Ribbon+RestTemplate进行消费服务。现在 我们再来学习另外一种调用服务方式——Fegin。 介绍 Feign是一种声明式、模板化的HTTP客户端。与Ribbo相比使用起来更加方便的。 Fegin采用接口的方式,只需要创建一个接口,然后在上面添加注解即可, 将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建 htt...

李哥的博客 510

面向业务角色的 SAP Fiori Space 与 Page 创建全流程实战指南

业务角色导航设计:从Space与Page规划到实施 摘要:本文探讨了SAP S/4HANA Cloud和BTP ABAP环境中基于业务角色的导航设计方法。传统Fiori Launchpad的Group+Tile模式存在应用堆积问题,而新的Space与Page模型将导航按工作领域(如应付会计、销售代表)组织,每个Space包含多个任务导向的Page。文章详细介绍了从业务角色建模到Space/Page创建、分配的完整流程,包括核心概念解析、规划思路和具体操作步骤。重点阐述了如何根据业务需求选择创建方式(基于预交

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 183

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

SpringCloud03_Ribbon客户端负载均衡和Feign组件

SpringCloud03_Ribbon客户端负载均衡和Feign组件 Ribbon 基于HTTP和TCP的客户端负载均衡工具(默认轮询策略,默认超时时间1秒) 服务端负载均衡 负载均衡算法在服务端 由负载均衡器维护服务地址列表 客户端负载均衡 负载均衡算法在客户端 客户端维护服务地址列表 简化RestTemplate远程调用 在RestTemplateConfig类加上@LoadBalanced注解 @Configuration public class RestTemplat

weixin_48672332的博客 305

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

SpringCloud feign工作原理

摘要:Spring Cloud Feign是一个基于动态代理的声明式HTTP客户端,通过注解简化微服务间调用。其工作原理包括:1)启动时扫描@FeignClient注解生成代理对象;2)解析方法注解构建HTTP请求;3)通过底层HTTP客户端(可配置)发送请求;4)集成负载均衡和服务发现;5)支持熔断降级机制。相比直接使用RestTemplate,Feign使代码更简洁、易维护,支持类似本地方法的远程调用方式,并提供了超时配置、日志等特性。核心优势在于将HTTP调用封装为接口方法,结合Spring生态实现完

jakeswang的博客 1260

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

SpringCloud中@FeignClient的使用

在微服务架构中总需要各个服务相互调用,各个服务又是以接口方式暴露,所以需要Http远程调用,于是我们就用到了Feign。 Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。 首先加入依赖 <!-- spring...

weixin_41036287的博客 2万+

springcloud之Ribbon负载均衡及Feign消费者调用服务

springcloud之Ribbon负载均衡及Feign消费者调用服务1、简单了解Ribbon微服务调用Ribbon2、Ribbon负载均衡Ribbon负载均衡3、Feign简介及应用微服务调用Ribbon 1、简单了解Ribbon Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自...

weixin_45346741的博客 225

Spring Cloud(二)Ribbon负载均衡器与声明性REST客户端Feign

​Spring Cloud(二)Ribbon负载均衡器与声明性REST客户端Feign 2018年摄于奈良唐招提寺 微信公众号 文中源码已上传至: https://github.com/TenaciousDWang/Eureka/tree/master/ribbon-eureka https://github.com/TenaciousDWang/Eureka/tre...

JavaBoy王皓 314

SpringCloud

SpringCloud的基础功能: 服务治理:Spring Cloud Eureka 客户端负载均衡:Spring Cloud Ribbon 服务容错保护:Spring Cloud Hystrix 声明式服务调用:Spring Cloud Feign API网关服务:Spring Cloud Zuul 分布式配置中心:Spring Cloud Config SpringCloud的高级功能:...

qq_24948379的博客 258

springCloud面试

什么是springCloud 微服务是一种架构方式,而springCloud就是其中一种技术架构。 springCloud将其他技术整合到一起,如配置管理,服务发现,智能路由,熔断器,集群状态。 - Eureka:服务治理组件,包含服务注册中心,服务注册与发现机制的实现。(服务治理, 服务注册/发现) - Zuul:网关组件,提供智能路由,访问过滤功能 - Ribbo...

qq_35010942的博客 230

SpringCloud基础知识整理

(SOA:面向服务架构) 微服务开发的缺点: 1、代码冗余,重复开发; 问题: 调用关系错综复杂; 维护困难 springCloud:springCloud就是一个容器,即注册中心,所有的服务方与调用方都需要在注册中心进行注册。微服务架构的解决方案,是很多组件的集合。 eureka:注册中心,服务的注册与发现; zuul:网关组件,路由请求,过滤器; ribbon:负载均衡组件 hystrix:熔断组件 feign:远程调...

lilaobanlove的博客 373

SpringCloud各组件入门以及小Demo

SpringCloud入门 微服务是一种架构方式,最终需要技术架构去实施。springcloud是一种微服务架构的解决方案,是很多组件的集合 主要涉及的组件 Eureka 注册中心,服务注册/发现 Zuul 网关组件,提供智能路由,访问过滤 (集成 Ribbon Hystrix) Ribbon 客户端负载均衡 Feign 服务调用,给与Ribbon和Hystrix的声明式事务调用组件 Hyst...

chenchen818的博客 285

Spring Cloud/Feign学习记录

Spring Cloud SpringCloud旨在快速搭建分布式微服务架构,实现大型项目的功能拆分,将不同服务功能拆分为单独的模块。 SpringCloud的基础功能: 服务治理: Spring Cloud Eureka 客户端负载均衡: Spring Cloud Ribbon 服务容错保护: Spring Cloud Hystrix 声明式服务调用: Spring Cloud FeignAPI 网关服务:Spring Cloud Zuul 分布式配置中心: Spring Cloud Config 当搭建

panda9527z的博客 175

SpringCloud-openFeign

openFeign和Feign 什么是Feign Feign 是一个声明式的我web服务客户端,使得编写web服务客户端变得非常容易 只要 创建一个 接口就行,然后后面加上注解 就可以 feign Feign 只在使编写java Http 客户端变得更容易 前面在使用 Ribbon +RsetTemplate时利用RsetTemplate对Http请求的封装处理,形成了一套模板化的调用方法,但是在实际 开发中,由于对服务依赖的调用可能不止一个,往往一个接口会被多处调用,所以通常会针对 每一为服务自行封装一些

m0_67400972的博客 215
上一篇: SpringCloud的负载均衡
下一篇: SpringCloud 声明式服务调用 —— Feign 全面解析(入门 + 原理 + 优化)
StackNoOverflow
博客等级 码龄1年 22粉丝 48原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值