SpringCloud Alibaba实战(8:使用OpenFeign服务调用)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介: SpringCloud Alibaba实战(8:使用OpenFeign服务调用)

源码地址:https://gitee.com/fighter3/eshop-project.git

持续更新中……


在上一个章节,我们已经成功地将服务注册到了Nacos注册中心,实现了服务注册和服务发现,接下来我们要做的是服务间调用。

想一下,我们日常调用接口有哪些方式呢?常见有的有JDK自带的网络连接类HttpURLConnection、Apache Common封装的HttpClient、Spring封装的RestTemplate。这些调用接口工具也许在你看来都并不困难那,但是如果引入feign,使用声明式调用,调用远程服务像调用本地api一样丝滑。

OpenFeign项目地址:https://github.com/OpenFeign/feign

1、Feign简介

Feign是一种声明式、模板化的HTTP客户端。使用Feign,可以做到声明式调用。

尽管Feign目前已经不再迭代,处于维护状态,但是Feign仍然是目前使用最广泛的远程调用框架之一。

在SpringCloud Alibaba的生态体系内,有另一个应用广泛的远程服务调用框架Dubbo,在后面我们会接触到。

接下来,我们开始学习Feign的使用,非常简单!

2、Feign使用

2.1、引入OpenFeign

在前面的章节里,我们已经引入了SpringCloud,现在我们只需要在需要引入的子模块中添加依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2.2、Feign远程调用

我们现在来完成一个业务:添加商品

image.png

这个业务涉及两个子服务,添加商品的时候同时要添加库存,查询商品的时候,同时要查询库存。商品服务作为消费者,库存服务作为生产者。

2.2.1、服务提供者

作为服务提供者的库存服务很简单,提供两个接口添加库存根据商品ID获取库存量

  • 控制层
@RestController
@RequestMapping("/shop-stock/api")
@Slf4j
@Api(value = "商品服务对外接口", tags = "商品服务对外接口")
public class ShopStockApiController {
    @Autowired
    private IShopStockService shopStockService;
    @PostMapping(value = "/add")
    @ApiOperation("添加库存")
    public Integer addStock(@RequestBody StockAddDTO stockAddDTO) {
        log.info("client call add stock interface,param:{}", stockAddDTO);
        return this.shopStockService.addStockApi(stockAddDTO);
    }
    @GetMapping(value = "/account/get")
    @ApiOperation("根据商品ID获取库存量")
    public Integer getAccountById(@RequestParam Integer goodsId) {
        return this.shopStockService.getAccountById(goodsId);
    }
}

注意看,为了演示出本地调用类似的效果,这两个接口和普通的前后端接口不同。

我们没有返回之前定下的统一返回结果CommonResult,而是直接返回了数据。

  • 业务层
    普通的增、查而已
    /**
     * 添加库存-直接返回主键
     *
     * @param stockAddDTO
     * @return
     */
    public Integer addStockApi(StockAddDTO stockAddDTO) {
        ShopStock stock = new ShopStock();
        stock.setGoodsId(stockAddDTO.getGoodsId());
        stock.setInventory(stockAddDTO.getAccount());
        log.info("准备添加库存,参数:{}", stock.toString());
        this.baseMapper.insert(stock);
        Integer stockId =stock.getStockId();
        log.info("添加库存成功,stockId:{}", stockId);
        return stockId;
    }
    /**
     * 根据商品ID获取商品库存
     *
     * @param goodsId
     * @return
     */
    public Integer getAccountById(Integer goodsId) {
        ShopStock stock = this.getOne(Wrappers.<ShopStock>lambdaQuery().eq(ShopStock::getGoodsId, goodsId));
        Integer account = stock.getInventory();
        return account;
    }
  • 添加库存实体类
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "库存添加", description = "")
public class StockAddDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "商品主键")
    private Integer goodsId;
    @ApiModelProperty(value = "数量")
    private Integer account;
}

至此,我们的服务提供者的相关开发到此完成,打开地址 http://localhost:8050/doc.html ,可以看到我们开发的接口:

2.2.2、服务消费者

好了,接下里要开始我们的服务消费者,也就是商品服务的开发。

  • 远程调用Feign客户端

声明式调用——看一下Feign客户端的代码,你就知道什么是声明式调用:

/**
 * @Author: 三分恶
 * @Date: 2021/5/26
 * @Description: 库存服务feign客户端
 **/
@FeignClient(value = "stock-service")
public interface StockClientFeign {
    /**
     * 调用添加库存接口
     *
     * @param stockAddDTO
     * @return
     */
    @PostMapping(value = "/shop-stock/api/add")
    Integer addStock(@RequestBody StockAddDTO stockAddDTO);
    /**
     * 调用根据商品ID获取库存量接口
     *
     * @param goodsId
     * @return
     */
    @GetMapping(value = "/shop-stock/api/account/get")
    Integer getAccountById(@RequestParam(value = "goodsId") Integer goodsId);
}
  • 定义完成之后,我们还要在启动类上加上注解@EnableFeignClients去扫描Feign客户端。
@SpringBootApplication
@MapperScan("cn.fighter3.mapper")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "cn.fighter3.client")
public class EshopGoodsApplication {
    public static void main(String[] args) {
        SpringApplication.run(EshopGoodsApplication.class, args);
    }
}

使用Feign客户端也很简单,直接在需要使用的地方注入就行了。

@Autowired
private StockClientFeign stockClientFeign;
  • 商品服务控制层
/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author 三分恶
 * @since 2021-05-18
 */
@RestController
@RequestMapping("/shop-goods")
@Api(value = "商品管理接口", tags = "商品接口")
@Slf4j
public class ShopGoodsController {
    @Autowired
    private IShopGoodsService goodsService;
    @PostMapping(value = "/add")
    @ApiOperation(value = "添加商品")
    public CommonResult addGoods(@RequestBody GoodsAddDTO goodsAddDTO) {
        return this.goodsService.addGoods(goodsAddDTO);
    }
    @GetMapping(value = "/get/by-id")
    @ApiOperation(value = "根据ID获取商品")
    public CommonResult<GoodsVO> getGoodsById(@RequestParam Integer goodsId) {
        return this.goodsService.getGoodsById(goodsId);
    }
}
  • 服务层

在服务层除了对商品库的操作之外,还通过Feign客户端远程调用库存服务的接口。

@Service
@Slf4j
public class ShopGoodsServiceImpl extends ServiceImpl<ShopGoodsMapper, ShopGoods> implements IShopGoodsService {
    @Autowired
    private StockClientFeign stockClientFeign;
    /**
     * 添加商品
     *
     * @param goodsAddDTO
     * @return
     */
    public CommonResult addGoods(GoodsAddDTO goodsAddDTO) {
        ShopGoods shopGoods = new ShopGoods();
        BeanUtils.copyProperties(goodsAddDTO, shopGoods);
        this.baseMapper.insert(shopGoods);
        log.info("添加商品,商品主键:{}", shopGoods.getGoodsId());
        log.info(shopGoods.toString());
        StockAddDTO stockAddDTO = StockAddDTO.builder().goodsId(shopGoods.getGoodsId()).account(goodsAddDTO.getAccount()).build();
        log.info("准备添加库存,参数:{}", stockAddDTO.toString());
        Integer stockId = this.stockClientFeign.addStock(stockAddDTO);
        log.info("添加库存结束,库存主键:{}", stockId);
        return CommonResult.ok();
    }
    /**
     * 获取商品
     *
     * @param goodsId
     * @return
     */
    public CommonResult<GoodsVO> getGoodsById(Integer goodsId) {
        GoodsVO goodsVO = new GoodsVO();
        //获取商品基本信息
        ShopGoods shopGoods = this.baseMapper.selectById(goodsId);
        BeanUtils.copyProperties(shopGoods, goodsVO);
        //获取商品库存数量
        Integer account = this.stockClientFeign.getAccountById(goodsId);
        log.info("商品数量:{}", account);
        goodsVO.setAccount(account);
        return CommonResult.ok(goodsVO);
    }
}
  • 实体类
    添加库存实体类和库存服务相同,略过,商品展示实体类
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "商品", description = "")
public class GoodsVO implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "商品主键")
    private Integer goodsId;
    @ApiModelProperty(value = "商品名称")
    private String goodsName;
    @ApiModelProperty(value = "价格")
    private BigDecimal price;
    @ApiModelProperty(value = "商品介绍")
    private String description;
    @ApiModelProperty(value = "数量")
    private Integer account;
}

2.2.3、效果演示

接下来启动nacos-server,商品服务,库存服务。

访问地址 http://127.0.0.1:8848/nacos/index.html ,登录之后,可以在服务列表里看到我们注册的两个服务:

访问商品服务Knife4j地址:http://localhost:8020/doc.html ,可以看到添加商品和根据商品ID查找商品的接口,分别调试调用:

  • 添加商品

image.png

  • 根据ID获取商品

image.png

可以看到各自对应的数据库也有数据生成:

image.png

整体的远程调用示意图大概如下:

image.png

2.3、Ribbon负载均衡

关于负载均衡,这里偷个懒,就不再演示了。

感兴趣的可以吧库存服务打包,以不同的端口启动,然后添加商品,通过日志查看商品服务调用的负载情况。

Feign负载均衡是通过Ribbon实现,Ribbon是一种客户端的负载均衡——也就是从注册中心获取服务列表,由客户端自己决定调用哪一个远程服务。

Ribbon的主要负载均衡策略有以下几种:

规则名称 特点
AvailabilityFilteringRule 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并 过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate 来包含过滤server的逻辑,其实就是检查status里记录的各个server 的运行状态
BestAvailableRule 选择一个最小的并发请求的server,逐个考察server, 如果Server被tripped了,则跳过
RandomRule 随机选择一个Server
ResponseTimeWeightedRule 已废弃,作用同WeightedResponseTimeRule
WeightedResponseTimeRule 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低
RetryRule 对选定的负载均衡策略加上重试机制,在一个配置时间段内当 选择Server不成功,则一直尝试使用subRule的方式选择一个 可用的Server
RoundRobinRule 轮询选择,轮询index,选择index对应位置的Server
ZoneAvoidanceRule 默认的负载均衡策略,即复合判断Server所在区域的性能和Server的可用性 选择Server,在没有区域的环境下,类似于轮询(RandomRule)

这里就不再展开讲了,感兴趣的自行了解。

3、意外状况

  • 发现远程调用的时候出现读取响应结果超时的情况:
java.net.SocketTimeoutException: Read timed out

修改Ribbon超时配置就行了:

# ribbon超时时间
ribbon:
  ReadTimeout: 30000
  ConnectTimeout: 30000
  • Feign接口中,使用@RequestParam报错

发现报错:

Caused by: java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0

Feign声明里需要加上value

Integer getAccountById(@RequestParam(value = "goodsId") Integer goodsId);

“简单的事情重复做,重复的事情认真做,认真的事情有创造性地做!”——

我是三分恶,可以叫我老三/三分/三哥/三子,一个能文能武的全栈开发,咱们下期见!


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
目录
相关文章
|
2月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
3月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
662 3
|
1月前
|
监控 Cloud Native Java
Spring Boot 3.x 微服务架构实战指南
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Spring Boot 3.x与微服务架构,探索云原生、性能优化与高可用系统设计。以代码为笔,在二进制星河中谱写极客诗篇。关注我,共赴技术星辰大海!(238字)
Spring Boot 3.x 微服务架构实战指南
|
1月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
1月前
|
负载均衡 Java API
《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
|
6月前
|
人工智能 Java API
Spring AI 实战|Spring AI入门之DeepSeek调用
本文介绍了Spring AI框架如何帮助Java开发者轻松集成和使用大模型API。文章从Spring AI的初探开始,探讨了其核心能力及应用场景,包括手动与自动发起请求、流式响应实现打字机效果,以及兼容不同AI服务(如DeepSeek、通义千问)的方法。同时,还详细讲解了如何在生产环境中添加监控以优化性能和成本管理。通过Spring AI,开发者可以简化大模型调用流程,降低复杂度,为企业智能应用开发提供强大支持。最后,文章展望了Spring AI在未来AI时代的重要作用,鼓励开发者积极拥抱这一技术变革。
2464 71
Spring AI 实战|Spring AI入门之DeepSeek调用
|
8月前
|
人工智能 搜索推荐 Java
Spring AI与DeepSeek实战三:打造企业知识库
本文基于Spring AI与RAG技术结合,通过构建实时知识库增强大语言模型能力,实现企业级智能搜索场景与个性化推荐,攻克LLM知识滞后与生成幻觉两大核心痛点。
909 7
|
3月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
7月前
|
安全 Java 数据库
Spring Security 实战指南:从入门到精通
本文详细介绍了Spring Security在Java Web项目中的应用,涵盖登录、权限控制与安全防护等功能。通过Filter Chain过滤器链实现请求拦截与认证授权,核心组件包括AuthenticationProvider和UserDetailsService,负责用户信息加载与密码验证。文章还解析了项目结构,如SecurityConfig配置类、User实体类及自定义登录逻辑,并探讨了Method-Level Security、CSRF防护、Remember-Me等进阶功能。最后总结了Spring Security的核心机制与常见配置,帮助开发者构建健壮的安全系统。
441 0