openFeign的使用
1、openFeign是干什么的?
OpenFeign是一个显示声明式的WebService客户端。使用OpenFeign能让编写Web Service客户端更加简单。使用时只需定义服务接口,然后在上面添加注解。OpenFeign也支持可拔插式的编码和解码器。spring cloud对feign进行了封装,使其支持MVC注解和HttpMessageConverts。和eureka(服务注册中心)和ribbon组合可以实现负载均衡。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求,非常的方便
2、建立服务模块
建立order订单模块服务(之前文章已经建立了user服务),这样两个服务就可以测试feign接口了。
user服务的建立请看第一篇文章 https://blog.csdn.net/qq_38374397/article/details/125542389
新建maven子模块
application.yml配置文件
server: port: 9091 spring: application: name: mdx-shop-order cloud: nacos: discovery: server-addr: localhost:8848 namespace: mdx group: mdx
bootstrap.properties配置文件
spring.application.name=mdx-shop-order spring.cloud.nacos.config.server-addr=localhost:8848 spring.cloud.nacos.config.extension-configs[0].data-id=mdx-shop-order.yaml spring.cloud.nacos.config.extension-configs[0].group=shop spring.cloud.nacos.config.extension-configs[0].refresh=true spring.cloud.nacos.config.file-extension=yml spring.cloud.nacos.config.namespace=mdx spring.cloud.nacos.config.group=shop
3、引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
4、openFeign的使用
我们先创建一个common的公共模块,然后引入一些工具类
在common中添加工具类,一些字符串操作和非空判断
在其他模块中引用common公共模块
在order服务中先创建一个获取订单号的测试接口
建立一个controller层和service层,controller层代码分别如下:
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @GetMapping("getOrderNo") public String getOrderNo(String userId){ return orderService.getOrderNo(userId); } }
service层实现类,目前没有查DB,做了简单的判断
@Service public class OrderServiceImpl implements OrderService { @Override public String getOrderNo(String userId) { if (StringUtils.isNotEmpty(userId) && userId.equals("mdx123456")){ return "O111222333444"; } return "订单不存在"; } }
在启动类开启支持feign的远程调用的注解 @EnableFeignClients
@SpringBootApplication @EnableFeignClients public class MdxShopUserApplication { public static void main(String[] args) { SpringApplication.run(MdxShopUserApplication.class, args); } }
然后在user服务中创建一个OrderFeign接口用来调用order服务
@FeignClient(value = "mdx-shop-order") @Component public interface OrderFeign { @GetMapping("order/getOrderNo") String getOrderNo(String userId); }
@FeignClient标签的常用属性如下:
- name/value:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀,当我们项目中配置了server.context-path,server.servlet-path时使用
value 属性的值是order服务的服务名称,也就是注册到注册中心中的服务名称
我们在user服务中建立一个controller和service调用feign接口来测试一下
controller层
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("getOrderNo") public String getOrderNo(String userId){ return userService.getOrderNo(userId); } }
service层
@Service public class UserServiceImpl implements UserService { @Autowired private OrderFeign orderFeign; @Override public String getOrderNo(String userId) { return orderFeign.getOrderNo(userId); } }
分别启动user服务和order服务测试
启动user服务的时候发现报错了,是因为SpringCloud Feign在Hoxton.M2 RELEASED版本之后抛弃了Ribbon,使用了spring-cloud-loadbalancer,所以我们这里还需要引入spring-cloud-loadbalancer的依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> <version>3.1.1</version> </dependency>
重新启动项目,并查看nacos,发现服务已经注册成功
接下来访问user服务,看看是否能通过feign接口成功获取到订单号
user服务报错
feign.FeignException$MethodNotAllowed: [405] during [GET] to [http://mdx-shop-order/order/getOrderNo] [OrderFeign#getOrderNo(String)]: [{"timestamp":"2022-07-04T03:13:30.111+00:00","status":405,"error":"Method Not Allowed","path":"/order/getOrderNo"}]
order服务提示信息
2022-07-04 11:13:30.110 WARN 66016 --- [nio-9091-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
通过意思信息我们可以发现,我们的GET请求被当成了POST请求,如果不加默认的注解,Feign则会对参数默认加上@RequestBody注解,而RequestBody一定是包含在请求体中的,GET方式无法包含
我们改变一下feign接口,添加一个@RequestParam注解
@FeignClient(value = "mdx-shop-order") @Component public interface OrderFeign { @GetMapping("order/getOrderNo") String getOrderNo(@RequestParam String userId); }
重启user服务,访问接口测试
成功获取到order服务中的订单号
5、openfeign接口添加请求头信息
我们有时候会在接口请求中传递一些头信息,来看一下feign是怎么使用的
其中有5种方式可以实现传递请求头信息:
- 在@RequestMapping注解里添加headers属性
- 在方法参数前面添加@RequestHeader注解
- 在方法或者类上添加@Headers的注解
- 在方法参数前面添加@HeaderMap注解
- 实现RequestInterceptor接口
我们只演示一个最简单的,在方法参数前面添加@RequestHeader注解
修改一下OrderFeign接口
单个参数:
@GetMapping("order/getOrderNo") String getOrderNo(@RequestParam String userId,@RequestParam String tenantId,@RequestHeader("Authorization") String token);
多个参数使用MultiValueMap
@GetMapping("order/getOrderNo") String getOrderNo(@RequestParam String userId, @RequestParam String tenantId, @RequestHeader MultiValueMap<String, String> headers);
我们来测试下传递单个参数的情况
user服务代码修改:
controller(添加HttpServletRequest参数):
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("getOrderNo") public String getOrderNo(String userId,String tenantId,HttpServletRequest request){ return userService.getOrderNo(userId,tenantId,request); } }
service实现类(获取request中的头信息并传递给feign接口)
@Service public class UserServiceImpl implements UserService { @Autowired private OrderFeign orderFeign; @Override public String getOrderNo(String userId, String tenantId, HttpServletRequest request) { return orderFeign.getOrderNo(userId,tenantId, request.getHeader("token")); } }
order服务:
controller(获取头信息中的参数):
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @GetMapping("getOrderNo") public String getOrderNo(String userId, String tenantId, HttpServletRequest request){ System.out.println("Authorization:" + request.getHeader("Authorization")); return orderService.getOrderNo(userId,tenantId); } }
重启服务并调用接口
成功获取到头信息
6、fallback的使用
来看一下fallback属性的作用
fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
然后我们看下怎么用
先定义一个容错的处理类 OrderFeignHandler:
@Component public class OrderFeignHandler implements OrderFeign { @Override public String getOrderNo(String userId, String tenantId, String token) { String fallback = "当前人数过多,休息一会再试"; return fallback; } }
@FeignClient 注解添加fallback属性 fallback = OrderFeignHandler.class
@FeignClient(value = "mdx-shop-order",fallback = OrderFeignHandler.class) @Component public interface OrderFeign { @GetMapping("order/getOrderNo") String getOrderNo(@RequestParam String userId,@RequestParam String tenantId,@RequestHeader("Authorization") String token); }
重启user服务,先获取一下正常的数据
正常返回订单号
我们在模拟一下异常的情况 修改order服务中的获取单号接口,找不到单号则抛异常
@Service public class OrderServiceImpl implements OrderService { @Override public String getOrderNo(String userId,String tenantId) { System.out.println(tenantId); if (StringUtils.isNotEmpty(userId) && userId.equals("mdx123456")){ return "O111222333444"; }else { throw new RuntimeException("单号不存在"); } } }
重启order服务,参数中传递一个不存在的订单号
发现并没有返回我们想要的信息
检查代码,发现我们少了配置
添加sentinel,因为我们使用的是alibaba微服务体系,所以我们使用sentinel来做熔断
sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性(sentinel如何使用我们后面的章节会讲到)
添加sentinel 依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>${spring-cloud-alibaba.version}</version> </dependency>
yml配置文件添加feign开启sentinel的配置
feign: sentinel: enabled: true
重启user服务,再次访问接口
我们可以看到order服务抛了订单号不存在的异常
但是我们的接口成功返回了容错接口定义的信息
到这里feign的简单使用已经全部结束了
创作不易,点个赞吧👍
最后的最后送大家一句话
白驹过隙,沧海桑田
与君共勉
上一篇文章
springcloud alibaba微服务 – nacos使用以及注册中心和配置中心的应用(保姆级)
下一篇文章
springcloud alibaba微服务 – sentinel的使用(保姆级)
文末送福利啦~
1、Java(SE、JVM)、算法数据结构、数据库(Mysql、redis)、Maven、Netty、RocketMq、Zookeeper、多线程、IO、SSM、Git、Linux、Docker、Web前端相关学习笔记
2、2023最新BATJ大厂面试题集
3、本教程项目源码
领取方式:关注下方公主号,回复:【笔记】、【面试】、【mdx-shop】获取相关福利。
文章持续更新,可以关注下方公众号或者微信搜一搜「 最后一支迷迭香 」获取项目源码、干货笔记、面试题集,第一时间阅读,获取更完整的链路资料。