简述:OpenFeign组件代替RestTemplate实现服务间的调用,并解决负载均衡和后期维护问题
一、什么是Feign组件——>OpenFeign
Feign组件(NetFlix)——>维护状态(NetFlix)——>SpringCloud提供OpenFeign
1、Feign 概述
在使用 Spring Cloud 开发微服务应用时,各个服务提供者都是以 HTTP 接口的形式对外提供服务,因此在服务消费者调用服务提供者时,底层通过 HTTP Client 的方式访问。此时可以使用 JDK 原生的 URLConnection、Apache 的 HTTP Client、Netty 的异步 HTTP Client 或者 Spring 的 RestTemplate 去实现服务间的调用。但是最方便、最优雅的方式是通过 Feign 进行服务间的调用。Feign 是由 Netflix 开发的一个声明式的 Web Service 客户端,它的出现使开发 Web Service 客户端变得很简单;Feign 同时也是一款声明式、模板化的 HTTP 客户端。更多介绍可参考:Feign 项目、Spring Cloud Feign 官方中文教程
2、Spring Cloud OpenFeign 概述
Spring Cloud OpenFeign 对 Feign 进行了二次封装,使得在 Spring Cloud 中使用 Feign 的时候,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程访问,更感知不到在访问 HTTP 请求。Spring Cloud OpenFeign 增强了 Feign 的功能,使 Feign 有限支持 Spring MVC 的注解,如 @RequestMapping 等。OpenFeign 的 @FeignClient 注解可以解析 Spring MVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,在实现类中做负载均衡并调用其他服务,默认集成了 Ribbon 与 Hystrix。更多介绍可参考:Spring Cloud OpenFeign 项目,OpenFeign入门教程
3、OpenFeign官网介绍
官网: Spring Cloud OpenFeign
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性(可以使用springmvc的注解),可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,默认实现了负载均衡的效果并且springcloud为feign添加了springmvc注解的支持。
4、OpenFeign和RestTemplate区别
RestTemplate:Spring框架封装HttpClient对象
OpenFeign:伪HttpClient客户端对象,让服务间的通信更简单,实现了Ribbon组件,实现负载均衡
简单: 使用时写一个接口加一个注解 ,自动完成数据传递过程中对象转换
5、为什么使用OpenFeign
RestTemplate 使用问题 1.路径写死 2.不能自动转化响应结果为对应对象 3.必须集成Ribbon实现负载均衡
OpenFeign组件 解决RestTemplate实现服务间通信所有问题
二、OpenFeign组件的使用
思考: 使用RestTemplate+ribbon已经可以完成对端的调用,为什么还要使用feign?
String restTemplateForObject = restTemplate.getForObject("http://服务名/url?参数" + name, String.class);
#存在问题: - 1.每次调用服务都需要写这些代码,存在大量的代码冗余 - 2.服务地址如果修改,维护成本增高 - 3.使用时不够灵活
1、OpenFeign服务调用
(1)服务调用方法引入依赖OpenFeign依赖
<!--Open Feign依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
(2)入口类加入注解开启OpenFeign支持
@SpringBootApplication @EnableFeignClients //入口类加入注解开启OpenFeign支持 @EnableDiscoveryClient //都是能够让注册中心能够发现,扫描到该服务。 public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }
(3)创建一个客户端调用接口
@FeignClient("products") public interface ProductClient { @GetMapping("/product/findAll") //书写服务调用路径 String findAll(); }
(4)使用feignClient客户端对象调用服务
@Autowired private ProductClient productClient; @GetMapping("/user/findAllFeignClient") public String findAllFeignClient(){ log.info("通过使用openFeign组件调用商品服务"); String msg = productClient.findAll(); return msg; }
(5)访问
网址:http://localhost:9999/user/findAllFeignClient
三、调用服务并传参
服务和服务之间通信,不仅仅是调用,往往在调用过程中还伴随着参数传递,接下来重点来看看OpenFeign在调用服务时如何传递参数
1、GET方式调用服务传递参数
在商品服务中加入需要传递参数的服务方法来进行测试
在用户服务中进行调用商品服务中需要传递参数的服务方法进行测试
(1)传参方式
queryString方式传值(地址栏) 例: ?name=王恒杰
路径传递参数(restFul) 例:/name/王恒杰
总结:当只有一个参数时以问号形式传参,当有多个参数不能自动用
?传参,需要在OpenFeign接口中必须给参数加入注解@RequestParam
如果是路径(RestFul)传参需要在OpenFeign接口中加入@PathVariable注解
(2)商品服务中添加如下方法
@RestController @Slf4j public class ProductAction { @Value("${server.port}") private int port; @GetMapping("/product/findOne") public Map<String,Object> findAll(String productId) { log.info("商品服务调度成功,当前服务端口[{}]", port); log.info("当前商品接收信息的ID:[{}]", productId); HashMap<String, Object> map = new HashMap<>(); map.put("msg","服务调度成功,服务端口为:" + port); map.put("status",true); map.put("productId",productId); return map; } }
(3)用户服务中在product客户端中声明方法(OpenFeignClient)
@FeignClient("products") public interface ProductClient { /** * 根据Id查询商品 * @RequestParam 路径传参 * @param productId * @return */ @GetMapping("/product/findOne") String findOne(@RequestParam("productId")String productId); }
(4)用户服务中调用并传递参数
@Autowired private ProductClient productClient; @GetMapping("/user/findOneFeignClient") public String findOneFeignClient(String productId){ log.info("通过openFeign组件调用商品服务。。。。。"); String msg=productClient.findOne(productId); return msg; }
(5)测试访问
2、post方式调用服务传递零散参数
在商品服务中加入需要传递参数的服务方法来进行测试
在用户服务中进行调用商品服务中需要传递参数的服务方法进行测试
(1)商品服务加入post方式请求并接受name
@PutMapping("/product/saveName") public Map<String, Object> save(String name) { log.info("商品服务现在调用的端口号[{}]"+port); log.info("商品接收到的姓名[{}]"+name); HashMap<String, Object> map = new HashMap<>(); map.put("msg","商品服务查询商品信息调用成功,当前服务端口: "+port); map.put("status",true); map.put("name",name); return map; }
(2)用户服务中在product客户端中声明方法
/** * 存储商品名 * @RequestParam ?传参 * @param name * @return */ @PutMapping("/product/saveName") String saveName(@RequestParam("name") String name);
(3)用户服务中调用并传递参数
@GetMapping("/user/saveName") public String saveName(String name) { log.info("调用商品组件的保存姓名功能"); String s = productClient.saveName(name); return s; }
(4)测试
3、post传递对象类型参数
商品服务定义对象
商品服务定义对象接收方法
用户服务调用商品服务定义对象参数方法进行参数传递
(1)商品服务定义对象
@Data //等价于@Setter、@Getter、@RequiredArgsConstructor、@ToString、@EqualsAndHashCode public class Product { private String productId; private String name; private Double Price; }
(2)商品服务加入post方式请求并接受对象
@PostMapping("/product/saveProduct") public Map<String, Object> saveProduct(@RequestBody Product product) { log.info("商品服务保存商品信息调用成功,当前服务端口:[{}]", port); log.info("当前接收商品名称:[{}]", product); Map<String, Object> map = new HashMap<String, Object>(); map.put("msg", "商品服务查询商品信息调用成功,当前服务端口: " + port); map.put("status", true); map.put("product", product); return map; }
(3)用户服务中在product客户端中声明方法
@PostMapping("/product/saveProduct") String saveProduct(@RequestBody Product product);
(4)用户服务中调用并传递对象参数
@GetMapping("/user/saveProduct") public String saveProduct(Product product){ log.info("接收到的商品信息:[{}]",product); String save = productClient.saveProduct(product); log.info("调用成功返回结果: "+save); return save; }
(5)测试
4、OpenFeign超时设置
默认情况下,openFiegn在进行服务调用时,要求服务提供方处理业务逻辑时间必须在1S内返回,如果超过1S没有返回则OpenFeign会直接报错,不会等待服务执行,但是往往在处理复杂业务逻辑是可能会超过1S,因此需要修改OpenFeign的默认服务调用超时时间。
(1)模拟超时
@PostMapping("/product/saveProduct") public Map<String, Object> saveProduct(@RequestBody Product product) throws InterruptedException { Thread.sleep(2000); log.info("商品服务保存商品信息调用成功,当前服务端口:[{}]", port); log.info("当前接收商品名称:[{}]", product); Map<String, Object> map = new HashMap<String, Object>(); map.put("msg", "商品服务查询商品信息调用成功,当前服务端口: " + port); map.put("status", true); map.put("product", product); return map; }
(2)进行客户端调用
调用超时会出现如下错误:
(3)在User中修改OpenFeign默认超时时间
#配置指定服务连接超时 feign.client.config.product.connectTimeout=5000 #配置指定服务等待超时 feign.client.config.product.readTimeout=5000
5、OpenFeign调用详细日志展示
往往在服务调用时我们需要详细展示feign的日志,默认feign在调用时并不是最详细日志输出,因此在调试程序时应该开启feign的详细日志展示。feign对日志的处理非常灵活可为每个feign客户端指定日志记录策略,每个客户端都会创建一个logger默认情况下logger的名称是feign的全限定名需要注意的是,feign日志的打印只会DEBUG级别做出响应。
我们可以为feign客户端配置各自的logger.lever对象,告诉feign记录那些日志logger.lever有以下的几种值
`NONE 不记录任何日志 `BASIC 仅仅记录请求方法,url,响应状态代码及执行时间 `HEADERS 记录Basic级别的基础上,记录请求和响应的header `FULL 记录请求和响应的header,body和元数据
(1)开启日志展示
feign.client.config.products.loggerLevel=full #开启指定服务日志展示 #feign.client.config.default.loggerLevel=full #全局开启服务日志展示 logging.level.com.tjcu.feignclients=debug #指定feign调用客户端对象所在包,必须是debug级别
(2)测试服务调用查看日志