说明

hystix-demoSpringCloud_Hystix.md 中有步骤)工程中,
我们使用了 Ribbon 的负载均衡功能,大大简化了远程调用时的代码

  1. String url = "http://user-service/user/" + id;
  2. // 查询
  3. User user = restTemplate.getForObject(url, User.class);
  4. return user;

如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?
这就是我们接下来要学的 Feign 的功能了。

demo

hystix-demo 的基础上进行 demo 练习,
consumer-service 引入依赖

  1. <!-- Feign启动器 -->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. </dependency>

开启 Feign 功能
在启动类上,添加注解,
你会发现 RestTemplate 的注册被我删除了。Feign 中已经自动集成了 Ribbon 负载均衡,因此我们不需要自己定义 RestTemplate

  1. package com.it.learn;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.netflix.hystrix.EnableHystrix;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. @SpringBootApplication
  7. @EnableHystrix
  8. @EnableFeignClients // 开启 Feign 功能支持
  9. public class UserConsumerApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(UserConsumerApplication .class);
  12. }
  13. }

Feign 的客户端

  1. 客户端接口要与服务提供者的入口方法保持一致(与服务提供者Controller中的方法名一致)
  2. @FeignClient("user-service") // 设置服务提供者的名称
  3. @Component // 创建接口实现类对象,并将对象存放到IOC容器中
  1. package com.it.learn.feign;
  2. import com.it.learn.pojo.User;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. @FeignClient("user-service")
  8. @Component
  9. public interface UserServiceFeignClient {
  10. @RequestMapping("/user/{id}")
  11. User findUserById(@PathVariable("id") Long id);
  12. }
  • 首先这是一个接口,Feign 会通过动态代理,帮我们生成实现类。这点跟 mybatismapper 很像
  • @FeignClient,声明这是一个 Feign 客户端,同时通过value属性指定服务名称
  • 接口中的定义方法,完全采用 SpringMVC 的注解,Feign 会根据注解帮我们生成 URL,并访问获取结果

改造原来的调用逻辑,使用 UserServiceFeignClient 访问:

  1. package com.it.learn.controller;
  2. import com.it.learn.feign.UserServiceFeignClient;
  3. import com.it.learn.pojo.User;
  4. import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. @RestController
  10. @RequestMapping("consumer")
  11. public class UserController {
  12. @Autowired
  13. private UserServiceFeignClient userServiceFeignClient;
  14. @RequestMapping("/{id}")
  15. @HystrixCommand(fallbackMethod = "findUserByIdForFail")
  16. public User findUserById(@PathVariable("id") Long id){
  17. User user = userServiceFeignClient.findUserById(id);
  18. return user;
  19. }
  20. /**
  21. * 降级处理的方法,与原方法返回值,参数列表保持一致
  22. * @param id
  23. * @return
  24. */
  25. public User findUserByIdForFail(@PathVariable("id") Long id){
  26. User user = new User();
  27. user.setId(id);
  28. user.setName("网页丢失了");
  29. return user;
  30. }
  31. }

启动,测试,访问接口 http://localhost:8080/consumer/6
SpringCloud_Feign - 图1

Hystix支持

Feign 默认也有对 Hystix 的集成:
SpringCloud_Feign - 图2
只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:

  1. feign:
  2. hystrix:
  3. enabled: true # 开启Feign的熔断功能

但是,Feign 中的 Fallback 配置不像 Ribbon 中那样简单了。
首先,我们要定义一个类,实现刚才编写的 UserServiceFeignClient,作为 fallback 的处理类

  1. package com.it.learn.feign.fallback;
  2. import com.it.learn.feign.UserServiceFeignClient;
  3. import com.it.learn.pojo.User;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * 创建降级类对象,将降级类对象存放到ioc容器中
  7. */
  8. @Component
  9. public class UserServiceFeignClientHystrix implements UserServiceFeignClient {
  10. @Override
  11. public User findUserById(Long id) {
  12. User user = new User();
  13. user.setId(id);
  14. user.setName("网页丢失了555");
  15. return user;
  16. }
  17. }

Feign 客户端类上指定降级类

  1. package com.it.learn.feign;
  2. import com.it.learn.feign.fallback.UserServiceFeignClientHystrix;
  3. import com.it.learn.pojo.User;
  4. import org.springframework.cloud.openfeign.FeignClient;
  5. import org.springframework.stereotype.Component;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. /**
  9. * value: 服务提供者名称
  10. * fallback: 降级类的字节码对象
  11. */
  12. @FeignClient(value = "user-service", fallback = UserServiceFeignClientHystrix.class)
  13. @Component
  14. public interface UserServiceFeignClient {
  15. @RequestMapping("/user/{id}")
  16. User findUserById(@PathVariable("id") Long id);
  17. }

重启 consumer-service 测试,故意在 user-service 中打断点,超时,远程调用导致降级
SpringCloud_Feign - 图3

日志级别

前面讲过,通过logging.level.xx=debug来设置日志级别。然而这个对 Fegin 客户端而言不会产生效果。因为@FeignClient注解修改的客户端在被代理时,都会创建一个新的 Fegin.Logger 实例。我们需要额外指定这个日志的级别才可以。
设置 com.it.learn 包下的日志级别都为 debug
编写配置类,定义日志级别

  1. package com.it.learn.config;
  2. import feign.Logger;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. public class FeignConfig {
  7. @Bean
  8. Logger.Level feignLoggerLevel(){
  9. return Logger.Level.FULL;
  10. }
  11. }

Feign 客户端开启日志记录

  1. package com.it.learn.feign;
  2. import com.it.learn.config.FeignConfig;
  3. import com.it.learn.feign.fallback.UserServiceFeignClientHystrix;
  4. import com.it.learn.pojo.User;
  5. import org.springframework.cloud.openfeign.FeignClient;
  6. import org.springframework.stereotype.Component;
  7. import org.springframework.web.bind.annotation.PathVariable;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. /**
  10. * value: 服务提供者名称
  11. * fallback: 降级类的字节码对象
  12. */
  13. @FeignClient(value = "user-service", fallback = UserServiceFeignClientHystrix.class, configuration = FeignConfig.class)
  14. @Component
  15. public interface UserServiceFeignClient {
  16. @RequestMapping("/user/{id}")
  17. User findUserById(@PathVariable("id") Long id);
  18. }

重启 consumer-service,访问,http://localhost:8080/consumer/6,查看效果
SpringCloud_Feign - 图4