导读:通过前面两篇文章我们准备好了微服务的基础环境并让accout-service 和 product-service对外提供了增删改查的能力,本篇我们的内容是让order-service作为消费者远程调用accout-service和product-service的服务接口。

统一接口返回结构

在开始今天的正餐之前我们先把上篇文章中那个丑陋的接口返回给优化掉,让所有的接口都有统一的返回结构。

  • 建立公共模块cloud-common

  • 其他模块都引入cloud-common,修改pom文件,加入依赖
<dependency>
    <groupId>com.jianzh5.cloud</groupId>
    <artifactId>cloud-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  • 建立接口返回的数据结构,这个数据结构大家可以根据自身项目情况统一即可
@Data
public class ResultData<T> {
    /** 结果状态 ,正常响应200,其他状态码都为失败*/
    private int status;
    private String message;
    private T data;
    private boolean success;
    private long timestamp ;

    ... 提供一些静态方法 ...
}
  • 改造accout-serviceproduct-service 模块中controller层的返回结构,改造完的代码如下:
@RestController
@Log4j2
public class AccountController {
    @Autowired
    private AccountService accountService;
    @PostMapping("/account/insert")
    public ResultData<String> insert(@RequestBody AccountDTO accountDTO){
        log.info("insert account:{}",accountDTO);
        accountService.insertAccount(accountDTO);
        return ResultData.success("insert account succeed");
    }
    @PostMapping("/account/delete")
    public ResultData<String> delete(@RequestParam String accountCode){
        log.info("delete account,accountCode is {}",accountCode);
        accountService.deleteAccount(accountCode);
        return ResultData.success("delete account succeed");
    }
    @PostMapping("/account/update")
    public  ResultData<String> update(@RequestBody AccountDTO accountDTO){
        log.info("update account:{}",accountDTO);
        accountService.updateAccount(accountDTO);
        return ResultData.success("update account succeed");
    }
    @GetMapping("/account/getByCode/{accountCode}")
    public ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode){
        log.info("get account detail,accountCode is :{}",accountCode);
        AccountDTO accountDTO = accountService.selectByCode(accountCode);
        return ResultData.success(accountDTO);
    }
}

服务调用

SpringCloud体系中,所有微服务间的通信都是通过Feign进行调用,Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像使用HttpClientOKHttp3等组件通过封装HTTP请求报文的方式调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。而且Feign默认集成了负载均衡器Ribbon,不需要自己实现负载均衡逻辑。

FeignSpringCloud的组件,在引入Feign之前我们先看看Spring BootSpring CloudSpring Cloud Alibaba 三者之间的关系,防止在业务中引入了错误的版本。

Spring Boot Spring Cloud Spring Cloud Alibaba
2.1.x Greenwich 0.9.x
2.0.x Finchley 0.2.x
1.5.x Edgware 0.1.x
1.5.x Dalston 0.1.x

很显然,我们引用的是SpringCloud Alibab 0.9.0,所以我们需要引入SpringCloud Greenwich

  • 引入SpringCloud版本依赖
    在项目主pom <dependencyManagement>中引入SpringCloud依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Greenwich.SR2</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
  • 在所有需要用到Feign的模块中引入openfeign依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 抽取公共的接口层,建立Feign接口,接口的定义和返回值需要跟Controller层保持一致
@FeignClient(name = "account-service")
public interface AccountFeign {
    @PostMapping("/account/insert")
    ResultData<String> insert(@RequestBody AccountDTO accountDTO);

    @PostMapping("/account/delete")
    ResultData<String> delete(@RequestParam("accountCode") String accountCode);

    @PostMapping("/account/update")
    ResultData<String> update(@RequestBody AccountDTO accountDTO);

    @GetMapping("/account/getByCode/{accountCode}")
    ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode);
}

在接口上添加注解@FeignClient(name = "account-service"),表明这是一个Feign客户端,name属性的配置表示我这个接口最终会转发到accout-service上。

正如回字有多种写法,这里Feign也有多种使用方式。
第一种就是我们这里介绍的,Feign和生产者的RequestMapping保持一致,大家可以看看上面改造后的AccountControllerAccountFeign一模一样有米有。

第二种方式就是让我们的Controller直接实现Feign接口,不再需要写RequestMapping,如:

@RestController
@Log4j2
public class ProductController implements ProductFeign {
    @Autowired
    private ProductService productService;

    @Override
    public ResultData<String> insert(@RequestBody ProductDTO productDTO){
        log.info("insert product:{}",productDTO);
        productService.insertProduct(productDTO);
        return ResultData.success("insert product succeed");
    }
}
  • 消费者模块引入Feign接口层的依赖
<dependency>
    <groupId>com.jianzh5.cloud</groupId>
    <artifactId>account-feign</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  • 在消费者product-service启动类上添加@EnableFeignClients注解
@SpringBootApplication
@RestController
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.javadaily.feign.*")
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
  • 消费者端跟使用本地service一样使用Feign
@RestController
public class OrderController {
    @Autowired
    private AccountFeign accountFeign;

    @Autowired
    private ProductFeign productFeign;

    @PostMapping("/order/getAccount/{accountCode}")
    public ResultData<AccountDTO> getAccount(@PathVariable String accountCode){
        return accountFeign.getByCode(accountCode);
    }

    @PostMapping("/order/insertAccount")
    public ResultData<String> insertAccount(AccountDTO accountDTO){
        return accountFeign.insert(accountDTO);
    }

    @PostMapping("/order/updateAccount")
    public ResultData<String> updateAccount(AccountDTO accountDTO){
        return accountFeign.update(accountDTO);
    }

    @PostMapping("/order/deleteAccount/{accountCode}")
    public ResultData<String> deleteAccount(@PathVariable String accountCode){
        return accountFeign.delete(accountCode);
    }
}
  • 项目模块截图

  • 联调测试
    我们请求OrderController中的接口,验证下接口请求结果:


    联调成功,搞定收工!

血与泪

使用feign过程中有以下几点需要注意,否则一不小心你就会掉进坑里。(我不会告诉你我当时在坑里踩了多久才爬上来)

  • Feign不支持直接使用对象作为参数请求
    接口中如果有多参数需要用实体接收,要么把参数一个一个摆开,要么在对象参数上加上@RequestBody注解,让其以json方式接收,如:
@PostMapping("/account/insert")ResultData<String> insert(@RequestBody AccountDTO accountDTO);
  • 消费者模块启动类上使用@EnableFeignClients注解后一定要指明Feign接口所在的包路径
    如:@EnableFeignClients(basePackages = "com.javadaily.feign.*")
    否则你的消费者启动时会报如下的错误:

    所以这里推荐你们在开发中所有feign模块最好能统一包名前缀com.javadaily.feign

  • @RequestParam的坑
    在Feign接口层使用@RequestParam注解要注意,一定要加上value属性,如:
    ResultData<String> delete(@RequestParam("accountCode") String accountCode);
    否则你会看到类似如下的错误:
    Caused by: java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0这个异常

  • @PathVariable的坑
    在Feign接口层使用@PathVariable注解要注意,一定要跟上面一样加上value属性,如:
    ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode);
    否则你也会看到类似如下的错误@PathVariable(value = "accountCode") String accountCode

  • 在消费者配置文件中添加Feign超时时间配置
    在我们的order-service配置文件中增加feign超时时间配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000

否则你会经常看到如下所示的错误:

java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method) ~[?:1.8.0_112]

至此我们已经完成了项目公共返回接口的统一并且成功使用Feign调用远程生产者的服务,那么本期的“SpringCloud Alibaba微服务实战三 - 服务调用”篇也就该结束啦,咱们下期有缘再见!

欢迎扫码关注微信公众号或 个人博客

SpringCloud Alibaba微服务实战三 - 服务调用的更多相关文章

  1. SpringCloud Alibaba微服务实战 - 基础环境准备

    Springcloud Aibaba现在这么火,我一直想写个基于Springcloud Alibaba一步一步构建微服务架构的系列博客,终于下定决心从今天开始本系列文章的第一篇 - 基础环境准备. 该 ...

  2. SpringCloud Alibaba微服务实战一 - 基础环境准备

    Springcloud Aibaba现在这么火,我一直想写个基于Springcloud Alibaba一步一步构建微服务架构的系列博客,终于下定决心从今天开始本系列文章的第一篇 - 基础环境准备. 该 ...

  3. Go微服务实战 - 用户服务开发(gRPC+Protocol Buffer)

    概要 用户服务基本是每个互联网产品里必备的一个服务了,因为没有用户基本是什么也干不了.所以他的重要性不言而喻.本文主要介绍下如何开发一个用户微服务,以及他的详细开发流程. 目录 Go微服务实战 - 从 ...

  4. 微服务实战(三):落地微服务架构到直销系统(构建基于RabbitMq的消息总线)

    从前面文章可以看出,消息总线是EDA(事件驱动架构)与微服务架构的核心部件,没有消息总线,就无法很好的实现微服务之间的解耦与通讯.通常我们可以利用现有成熟的消息代理产品或云平台提供的消息服务来构建自己 ...

  5. Spring Cloud 微服务实战——nacos 服务注册中心搭建(附源码)

    作为微服务的基础功能之一的注册中心担任重要的角色.微服务将单体的服务拆分成不同的模块下的服务,而不同的模块的服务如果进行通信调用呢?这就需要服务注册与发现.本文将使用阿里开源项目 nacos 搭建服务 ...

  6. 微服务实战——微服务架构选型SpringCloud / Dubbo / K8S比较(一)

    ## 说在前面 大概是三年前,因一些原因公司原项目最初为单体结构部署,所有业务模块都在一个项目里面,而后随着业务的不断膨胀以及模块之间的耦合,导致后面增加或修改一些简单业务时的成本都会变的极大.新入职 ...

  7. 微服务实战(三):深入微服务架构的进程间通信 - DockOne.io

    原文:微服务实战(三):深入微服务架构的进程间通信 - DockOne.io [编者的话]这是采用微服务架构创建自己应用系列第三篇文章.第一篇介绍了微服务架构模式,和单体式模式进行了比较,并且讨论了使 ...

  8. springcloud微服务实战--笔记

    目前对Springcloud对了解仅限于:“用[注册服务.配置服务]来统一管理其他微服务” 这个水平.有待提高 Springcloud微服务实战这本书是翟永超2017年5月写的,时间已经过去了两年,略 ...

  9. 微服务实战(一):微服务架构的优势与不足 - DockOne.io

    原文:微服务实战(一):微服务架构的优势与不足 - DockOne.io [编者的话]本文来自Nginx官方博客,是微服务系列文章的第一篇,主要探讨了传统的单体式应用的不足,以及微服务架构的优势与挑战 ...

随机推荐

  1. Head First设计模式——简单工厂、工厂、抽象工厂

    前言:按照惯例我以Head First设计模式的工厂模式例子开始编码学习.并由简单工厂,工厂模式,抽象工厂模式依次演变,归纳他们的相同与不同. 话说Head First认为简单工厂并不是设计模式,而是 ...

  2. 使用ESP8266 打造一款物联网产品---新版ESP8266-RTOS-SDK(V3.1以上)串口使用指南

    问题背景: 使用乐鑫的ESP8266做一个物联网的项目,要使用串口0通信,串口1作为打印log.本来是一个非常简单的事情.没想到居然里面有个大坑.本着前任踩坑,后任抱娃的原则. 这里就做个记录,给后面 ...

  3. [LINQ2Dapper]最完整Dapper To Linq框架(七)---仓储模式

    目录 [LINQ2Dapper]最完整Dapper To Linq框架(一)---基础查询 [LINQ2Dapper]最完整Dapper To Linq框架(二)---动态化查询 [LINQ2Dapp ...

  4. Linux下RIAD的实现及mdadm命令的基本用法

    一.RAID简述 磁盘阵列(Redundant Arrays of Independent Disks,RAID),是把多个物理磁盘组成一个阵列,当作一个逻辑磁盘使用,它将数据以分段或条带的方式储存在 ...

  5. vue使用一些外部插件及样式的配置

    一.配置全局css及js样式 1.首先将事先写好的css文件及js文件放在src文件目录下的assets文件下 2.在main.js文件输上图右边两个红色框的代码 二.配置全局jQuery及boots ...

  6. servlet三大组件

    servlet大致可以分为三个:简单servlet.过滤servlet.监听servlet servlet: servlet的创建 创建一个类并实现Servlet接口. 重写service方法. 在服 ...

  7. 平滑启动shell脚本

    # 平滑关闭和启动 Spring Boot 程序#设置端口SERVER_PORT="8090"#当前时间time=`date +%Y-%m-%d`#设置应用名称JAR_NAME=& ...

  8. 总结一下 IEnumerable 的例子

    本篇将围绕 <试试 IEnumerable 的 10 个小例子>和<试试 IEnumerable 的另外 6 个小例子>给出的例子,总结一下对于 IEnumerable 接口的 ...

  9. CentOS7安装PPTP

    CentOS7安装PPTP VPN(开启firewall防火墙) 1       准备一个CentOS7服务器 2       检查是否支持PPTP && echo ok #返回OK ...

  10. java编程思想第四版第十三章字符串 习题

    fas 第二题 package net.mindview.strings; import java.util.ArrayList; import java.util.List; /** * 无限循环 ...