zoukankan      html  css  js  c++  java
  • Spring Cloud OpenFeign深入浅出

    1. 简单玩法

    1.1 一个简单例子

    (1)服务端:

    @RestController
    @RequestMapping("hello")
    public class HelloController implements HelloApi {
        @Override
        public String hello(String name) {
            return "Hello, "+name+"!";
        }
    }

    API声明:

    public interface HelloApi {
    
        @GetMapping("/hello/{name}")
        String hello(@PathVariable("name") String name);
    
        @GetMapping("/bye/{name}")
        ResponseValue<String> bye(@PathVariable("name") String name);
    
        @GetMapping(value = "/download")
        byte[] download(HttpServletResponse response);
    }

    (2)客户端:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    开启配置 @EnableFeignClients,调用服务的代码:

    @FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello")
    public interface HelloApiExp extends HelloApi {
        @GetMapping("/download")
        Response download();
    }

    调用时的代码

    @RestController
    @RequestMapping("client")
    public class HelloClient {
    
        @Autowired
        private HelloApiExp helloApi;
    
        @GetMapping("/hello/{name}")
        public String hello(@PathVariable("name") String name){
            return helloApi.hello(name);
        }
    } 

    浏览器访问URL:http://127.0.0.1:8080/client/hello/Mark,页面返回: Hello, Mark!

    1.2 @FeignClient的简单用法

    2. 高级玩法

    2.1 configuration配置类

    通过自定义配置类统一配置Feign的各种功能属性,FeignClientsConfiguration为默认配置:

    @FeignClient(name="hello1", url = "127.0.0.1:8080", configuration = FeignClientsConfiguration.class)
    public interface HelloApi {
        @GetMapping("/{name}")
        String hello(@PathVariable("name") String name);
    }

    2.1.1 Decoder feignDecoder
    Decoder类,将http返回的Entity字符解码(反序列化)为我们需要的实例,如自定义的POJO对象。一般使用FeignClientsConfiguration默认的feignDecoder就能满足返回String、POJO等绝大多数场景。

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new OptionalDecoder(
            new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
    }

    2.1.2 Encoder feignEncoder
    Encode类对请求参数做编码(序列化)后,发送给http服务端。使用spring cloud默认的feignEncoder可以满足我们绝大多数情况。

    使用Feign实现文件上传下载时需要特殊处理,使用feign-form能够方便的实现。这里我们对feign-form在spring cloud中的使用举一个简单的例子。

    HelloApi接口声明:

    public interface HelloApi {
        @GetMapping(value = "/download")
        byte[] download(HttpServletResponse response);
    
        @PostMapping(value = "upload", 
                     consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
        ResponseValue<String> upload(@RequestBody MultipartFile file);
    }

    服务端代码:

    @RestController
    @RequestMapping("hello")
    public class HelloController implements HelloApi {
        
        @Override
        public byte[] download(HttpServletResponse response) {
            FileInputStream fis = null;
            try{
                File file = new File("E:\图片\6f7cc39284868762caaed525.jpg");
                fis = new FileInputStream(file);
                response.setContentType("application/octet-stream");
                response.setHeader("Content-Disposition",
                                   "attachment;filename=class.jpg");
                return IOUtils.toByteArray(fis, file.length());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
        
        @Override
        public ResponseValue<String> upload(@RequestBody MultipartFile file) {
            File destFile = new File("d:\1.jpg");
            ResponseValue<String> response = new ResponseValue<>();
            try {
                file.transferTo(destFile);
                return response.ok("上传成功!", null);
            } catch (IOException e) {
                e.printStackTrace();
                return response.fail("上传失败,错误原因:"+e.getMessage());
            }
        }    
    }

    客户端代码:

    pom.xml引入依赖:

    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form</artifactId>
        <version>3.8.0</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form-spring</artifactId>
        <version>3.8.0</version>
    </dependency>

    增加FeignClient配置类:

    @Configuration
    public class FeignMultipartSupportConfig extends FeignClientsConfiguration {
        @Bean
        public Encoder feignFormEncoder() {
            return new SpringFormEncoder();
        }
    }

    FeignClient接口声明:

    import feign.Response;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello",
            configuration = FeignMultipartSupportConfig.class)
    public interface HelloApiExp extends HelloApi {
        @GetMapping("/download")
        Response download();
    }

    调用端代码:

    @RestController
    @RequestMapping("client")
    public class HelloClient {
    
        @GetMapping(value = "/download")
        public byte[] download(HttpServletResponse response){
            response.setHeader("Content-Disposition",
                               "attachment;filename=class.jpg");
            //response.setHeader("Content-Type","application/octet-stream");
    
            Response resp = helloApi.download();
            Response.Body body = resp.body();
            try(InputStream is = body.asInputStream()) {
                return IOUtils.toByteArray(is, resp.body().length());
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
        
        @PostMapping(value = "upload", 
                     consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
        public ResponseValue<String> upload(@RequestBody MultipartFile file){
            return helloApi.upload(file);
        }    
    }

    2.1.3 Retryer feignRetryer

    请求重试策略类,默认不重试,可配置成feign.Retryer.Default,启用重试,默认间隔100毫秒重试一次,最大间隔时间限制为1秒,最大重试次数5次。

    @Configuration
    public class FeignRetryConfig extends FeignClientsConfiguration {
        @Bean
        @Override
        public Retryer feignRetryer() {
            return new Retryer.Default();
        }
    }

    2.1.4 Feign.Builder feignBuilder

    FeignClient的Builder,我们可以通过他使用代码的方式设置相关属性,代替@FeignClient的注解过的接口,如下面的代码:

    @GetMapping("/hello/{name}")
    public String hello(@PathVariable("name") String name){
        String response = feignBuilder
            .client(new OkHttpClient())
            .encoder(new SpringFormEncoder())
            .requestInterceptor(new ForwardedForInterceptor())
            .logger(new Slf4jLogger())
            .logLevel(Logger.Level.FULL)
            .target(String.class, "http://127.0.0.1:8080");
    
        return response;
        //return helloApi.hello(name);
    }

    其实@FeignClient生成的代理类也是通过它构建的。代码中的feignBuilder.client()可以使用RibbonClient,就集成了Ribben。

    2.1.5 FeignLoggerFactory feignLoggerFactory
    设置LoggerFactory类,默认为Slf4j。

    2.1.6 Feign.Builder feignHystrixBuilder
    配置Hystrix,从下面的配置类可以看出,@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class }) ,如果引用了Hystrix的相关依赖,并且属性feign.hystrix.enabled为true,则构建@FeignClient代理类时使用的FeignBuilder会使用feignHystrixBuilder。Feign通过这种方式集成了Hystrix。

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
    
       @Bean
       @Scope("prototype")
       @ConditionalOnMissingBean
       @ConditionalOnProperty(name = "feign.hystrix.enabled")
       public Feign.Builder feignHystrixBuilder() {
          return HystrixFeign.builder();
       }
    }

    2.2 故障转移

    故障转移机制,如果@FeignClient指定了fallback或fallbackFactory属性,http请求调用失败时会路由到fallback处理类的相同方法中。

    2.2.1 fallback

    @FeignClient声明:

    @FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello",
            configuration = FeignMultipartSupportConfig.class,
            fallback = HelloApiFallback.class)
    public interface HelloApiExp extends HelloApi {
        @GetMapping("/download")
        Response download();
    }

    HelloApiFallback代码需要实现HelloApiExp接口(包括父接口)的所有方法:

    @Slf4j
    public class HelloApiFallback implements HelloApiExp {
        @Override
        public Response download() {
            log.error("下载文件出错。");
            return null;
        }
    
        @Override
        public String hello(String name) {
            log.error("调用hello接口出错。");
            return "调用hello接口出错,请联系管理员。";
        }
    
        @Override
        public ResponseValue<String> bye(String name) {
            log.error("调用bye接口出错。");
            ResponseValue<String> response = new ResponseValue<>();
            return response.fail("调用hello接口出错,请联系管理员。");
        }
    
        @Override
        public byte[] download(HttpServletResponse response) {
            log.error("调用bye接口出错。");
            return new byte[0];
        }
    
        @Override
        public ResponseValue<String> upload(MultipartFile file) {
            log.error("调用上传文件接口出错。");
            ResponseValue<String> response = new ResponseValue<>();
            return response.fail("上传文件出错,请联系管理员。");
        }
    }

    2.2.2 fallbackFactory

    为@FeignClient接口所有方法指定统一的故障处理方法。

    @FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello",
            configuration = FeignMultipartSupportConfig.class,
            fallbackFactory = FallbackFactory.Default.class)
    public interface HelloApiExp extends HelloApi {
    
        @GetMapping("/download")
        Response download();
    }

    FallbackFactory.Default实现如下,请求失败后,统一路由到create(Throwable cause)方法。

    /** Returns a constant fallback after logging the cause to FINE level. */
    final class Default<T> implements FallbackFactory<T> {
      // jul to not add a dependency
      final Logger logger;
      final T constant;
    
      public Default(T constant) {
        this(constant, Logger.getLogger(Default.class.getName()));
      }
    
      Default(T constant, Logger logger) {
        this.constant = checkNotNull(constant, "fallback");
        this.logger = checkNotNull(logger, "logger");
      }
    
      @Override
      public T create(Throwable cause) {
        if (logger.isLoggable(Level.FINE)) {
          logger.log(Level.FINE, "fallback due to: " + cause.getMessage(), cause);
        }
        return constant;
      }
    
      @Override
      public String toString() {
        return constant.toString();
      }
    }

    3. OpenFeign思维导图

    在此奉上我整理的OpenFeign相关的知识点思维导图。

  • 相关阅读:
    由typedef和函数指针引起的危机
    从JVM角度看Java多态
    C语言中判断字符串str1是否以str2开始或结束
    Linux下利用json-c从一个json数组中提取每一个元素中的部分字段组成一个新json数组
    C语言中的条件编译
    学会 Python 到底能干嘛?我们整理出了 7 大工作方向……
    新手指南:我应该学哪种编程语言?
    盘点:2019年最赚钱的10种编程语言
    11个提升编程能力的小方法
    收好这份 Vue 升级图,假期偷偷上个钻
  • 原文地址:https://www.cnblogs.com/qingmuchuanqi48/p/13138400.html
Copyright © 2011-2022 走看看