Spring Cloud Alibaba微服务实战三 - 服务调用

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

导读:通过前面两篇文章我们准备好了微服务的基础环境并让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-service 和 product-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 Boot, Spring CloudSpring Cloud Alibaba 三者之间的关系,防止在业务中引入了错误的版本。

SpringBootSpringCloudSpringCloudAlibaba
2.1.xGreenwich0.9.x
2.0.xFinchley0.2.x
1.5.xEdgware0.1.x
1.5.xDalston0.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保持一致 ,大家可以看看上面改造后的AccountController 和 AccountFeign一模一样有米有。

第二种方式就是让我们的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微服务实战三 - 服务调用”篇也就该结束啦,咱们下期有缘再见!

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值