程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • 微服务基础

    • 了解SpringCloud微服务架构
    • 启动多个微服务项目
    • Eureka 服务注册
    • Ribbon 负载均衡
    • Nacos 下载与安装
    • Nacos 服务注册
    • Nacos 配置管理
    • openFeign 远程调用
    • OpenFeign 最佳实践
      • 1. 问题描述
      • 2. 通过继承实现代码复用
        • 代码实现
        • 继承的优点
        • 继承的缺点
      • 3. 通过抽取公共模块实现代码复用
        • 1. 为什么要进行抽取?
        • 2. 实现步骤
        • 2.1 创建公共模块 feign-api
        • 2.2 定义公共 API 接口
        • 2.3 定义 Feign 客户端接口
        • 2.4 引入公共模块并使用 Feign
        • 3. 解决 Feign 扫描问题
        • 4. 抽取后的调用示例
    • Gateway 网关
    • Gateway 进阶使用
    • Sentinel 服务保护
    • Sentinel 整合 Feign
    • 分布式事务 Seata
    • 什么是分布式
    • 什么是RPC框架
  • 微服务之DDD架构思想

  • 微服务
  • 微服务基础
scholar
2024-08-24
目录

OpenFeign 最佳实践

# OpenFeign 最佳实践

前言

在使用 Feign 时,服务消费者的 Feign 客户端与服务提供者的 Controller 代码往往非常相似,比如接口路径、参数定义、方法声明等。为减少代码重复,我们可以通过以下两种优化方式:继承方式和抽取方式。

# 1. 问题描述

以下是 Feign 客户端和服务提供者的代码对比:

  • Feign 客户端:

Feign Client

  • UserController:

UserController

可以看到,这两部分代码非常相似。有没有办法简化这种重复的代码呢?接下来介绍两种优化方法。

# 2. 通过继承实现代码复用

我们可以通过将相同的代码抽取到一个公共接口,服务提供者和消费者(Feign 客户端)都继承该接口,从而减少重复代码。

实现步骤

  1. 定义公共 API 接口:在接口中声明服务提供者和消费者共用的方法。
  2. 服务提供者的 Controller 和 Feign 客户端都继承该接口。

# 代码实现

公共 API 接口:

package com.example.api;

import com.example.pojo.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * 公共 API 接口,用于定义服务接口
 */
public interface UserApi {

    /**
     * 通过用户 ID 获取用户信息
     * @param id 用户 ID
     * @return 用户信息
     */
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

说明:UserApi 接口中使用了 SpringMVC 注解定义了接口路径、请求方法和参数类型。该接口可以被服务提供者和消费者共用。

服务提供者(Controller):

package com.example.controller;

import com.example.api.UserApi;
import com.example.pojo.User;
import org.springframework.web.bind.annotation.RestController;

/**
 * 服务提供者,继承 UserApi 接口
 */
@RestController
public class UserController implements UserApi {

    @Override
    public User findById(Long id) {
        // 模拟数据库查询,返回用户信息
        return new User(id, "张三");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

说明:UserController 继承了 UserApi 接口,因此服务提供者只需实现接口中的方法,而不需要重新定义路径和注解。

Feign 客户端:

package com.example.feign;

import com.example.api.UserApi;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * Feign 客户端,继承 UserApi 接口
 */
@FeignClient("userservice")
public interface UserClient extends UserApi {
    // Feign 客户端不需要额外的实现,只需继承 UserApi 即可
}
1
2
3
4
5
6
7
8
9
10
11
12

说明:UserClient 通过继承 UserApi 接口,实现了与服务提供者一致的接口定义,同时通过 @FeignClient("userservice") 注解指定要调用的服务名称。

# 继承的优点

  • 代码复用性高:统一接口定义,减少重复代码。
  • 一致性强:服务提供者和消费者之间的接口一致,避免了定义不一致导致的问题。

# 继承的缺点

  • 耦合性高:服务提供者和消费者之间存在一定的耦合。
  • 注解不继承:参数列表中的注解不会被继承,因此 Controller 中需要重新声明注解。

# 3. 通过抽取公共模块实现代码复用

在微服务架构中,多个服务之间常常需要通过 Feign 实现远程调用。如果每个服务都分别定义 Feign 客户端接口、数据对象(POJO )类和相关配置,必然会导致大量的重复代码。随着项目规模扩大,代码的重复性和维护成本会显著增加。因此,进行公共模块的抽取是一个有效的解决方案。

# 1. 为什么要进行抽取?

将 Feign 客户端、数据对象(POJO)和配置抽取到一个独立的模块,带来的好处包括:

  1. 代码复用性高:所有服务都可以依赖同一个模块,避免重复编写相同的 Feign 客户端和数据对象。
  2. 维护成本低:公共模块中的代码一旦需要更新,只需在公共模块中修改,所有依赖该模块的服务都会自动更新。
  3. 一致性强:通过抽取公共模块,所有服务的 Feign 调用方式、数据结构和配置保持一致,减少了代码冗余和错误的可能性。

# 2. 实现步骤

实现步骤

  1. 创建公共模块 feign-api:用于存放公共接口、POJO 和配置。
  2. 将 Feign 客户端接口、POJO 和配置移动到 feign-api 模块中。
  3. 服务消费者引入 feign-api 依赖,直接复用公共模块中的接口和配置。

# 2.1 创建公共模块 feign-api

首先,创建一个新的模块 feign-api,用于存放公共的 Feign 接口、POJO 类和配置。

feign-api 模块结构:

feign-api 模块结构

在这个模块中,我们将 Feign 客户端接口、数据对象(POJO)和配置进行集中管理。

feign-api 中的依赖:

在 pom.xml 中添加 Feign 的依赖,使该模块可以使用 Feign 相关的注解和功能:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1
2
3
4

# 2.2 定义公共 API 接口

在 feign-api 模块中定义服务提供者和消费者共用的 API 接口:

package com.example.api;

import com.example.pojo.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * 公共 API 接口,用于定义服务接口
 */
public interface UserApi {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

代码说明:这个接口使用了 SpringMVC 的注解,定义了服务的路径、请求方法和参数格式。服务提供者的 Controller 和消费者的 Feign 客户端都可以继承这个接口,从而避免重复代码。

# 2.3 定义 Feign 客户端接口

在 feign-api 模块中定义 Feign 客户端接口,并让其继承公共的 API 接口:

package com.example.feign;

import com.example.api.UserApi;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * 公共模块中的 Feign 客户端接口
 * 
 * @FeignClient 注解指定要调用的服务名称 "userservice"
 */
@FeignClient("userservice")
public interface UserClient extends UserApi {
    // Feign 客户端不需要额外的实现,只需继承 UserApi 即可
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

代码说明:

  • @FeignClient("userservice"):指定调用的服务名称为 userservice,Feign 会自动去注册中心查找该服务并发起调用。
  • 通过继承 UserApi,可以将 Feign 客户端的定义与服务提供方保持一致,减少代码重复。

# 2.4 引入公共模块并使用 Feign

在需要使用该 Feign 客户端的服务中(如 order-service),引入 feign-api 模块的依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>
1
2
3
4
5

在 order-service 中使用 Feign:

package com.example.order.service;

import com.example.feign.UserClient;
import com.example.pojo.User;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class OrderService {

    @Autowired
    private UserClient userClient; // 引入抽取后的 Feign 客户端

    /**
     * 通过用户 ID 查询用户信息
     * @param id 用户 ID
     * @return 用户信息
     */
    public User queryUserById(Long id) {
        // 使用 Feign 客户端发起远程调用,查询用户信息
        return userClient.findById(id);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

代码说明:在业务服务中,通过注入 Feign 客户端接口即可完成远程调用。所有相关代码已经在 feign-api 模块中统一管理,服务消费者只需简单调用即可。

# 3. 解决 Feign 扫描问题

由于 UserClient 现在位于 feign-api 模块的包下,可能不在服务消费者的默认扫描路径中。可以通过以下方式解决:

方式一:指定扫描包路径:

@EnableFeignClients(basePackages = "com.example.feign")
1

方式二:指定需要加载的 Feign 客户端接口:

@EnableFeignClients(clients = {UserClient.class})
1

# 4. 抽取后的调用示例

抽取后,使用 Feign 客户端的方法在业务逻辑中调用非常简单:

package com.example.order.controller;

import com.example.order.service.OrderService;
import com.example.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/order/{userId}")
    public User getUserById(@PathVariable Long userId) {
        // 调用 OrderService 中的方法,通过 Feign 客户端获取用户信息
        return orderService.queryUserById(userId);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在浏览器中访问 http://localhost:8080/order/101 即可看到结果。

结论

通过抽取公共模块,可以有效减少代码重复,提升代码复用性。这种方式在多微服务项目中尤为适用,能够大幅降低维护成本,提高代码一致性。

编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
openFeign 远程调用
Gateway 网关

← openFeign 远程调用 Gateway 网关→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式