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相关的知识点思维导图。
本文来自博客园,作者:King-DA,转载请注明原文链接:https://www.cnblogs.com/qingmuchuanqi48/p/13138400.html