是一个http请求调用的轻量级框架,是以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
使用Feign替代RestTemplate发送Rest请求。使之更符合面向接口化的编程习惯。
使用Nacos 注册text、fegin两个服务;使用feign实现服务直接的调用;
Feign服务
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>版本号</version>
</dependency>
在启动类上加入 @EnableFeignClients,表示开启Feign
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
@FeignClient
- name/value:指定FeignClient的名称,如果指定了注册中心,指注册的服务名称。
- url:可以手动指定@FeignClient调用的地址。
- contextId:指定beanID。
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException。
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀
@FeignClient(
value = "text"
// url = "127.0.0.1:8090" 通过url指定服务地址访问也是可以的
)
public interface TextClient {
@GetMapping("/text/findOrderDetailById")
OrderDetail findOrderDetailById(@RequestParam String detailId);
}
服务调用
@RestController
@RequestMapping("/fegin")
public class FeginController {
@Autowired
private TextClient textClient;
@GetMapping("/findOrderDetailById")
public OrderDetail findOrderDetailById(@RequestParam("detailId") String detailId){
return textClient.findOrderDetailById(detailId);
}
}
Text服务
@RestController
@RequestMapping("/text")
public class TextController {
@GetMapping("/findOrderDetailById")
public OrderDetail findOrderDetailById(@RequestParam("detailId") String detailId){
return new OrderDetail(detailId);
}
}
测试
configuration:Feign配置类,可以自定义Feign配置
Retryer:
- Feign默认配置是不走重试策略的,当发生RetryableException异常时直接抛出异常。
- 并非所有的异常都会触发重试策略,只有 RetryableException 异常才会触发异常策略。
- 在默认Feign配置情况下,只有在网络调用时发生 IOException 异常时,才会抛出 RetryableException,也是就是说链接超时、读超时等不不会触发此异常。
因此常见的 SocketException、NoHttpResponseException、UnknownHostException、HttpRetryException、SocketConnectException、ConnectionClosedException 等异常都可触发Feign重试机制。
注:常见的还有 SocketTimeoutException、TimeoutException、ReadTimeoutException、WriteTimeoutException 等都不属于IO异常,因为不会触发Feign重试机制。
@Slf4j
public class FeginConfig {
/**
* 自定义重试
* maxAttempts:最多请求次数
* period:发起当前请求的时间间隔,单位毫秒
* maxPeriod:发起当前请求的最大时间间隔,单位毫秒
* @return
*/
@Bean
public Retryer feginRetryer(){
// 默认最大请求次数为5,初始间隔时间为100ms,下次间隔时间1.5倍递增,重试间最大间隔时间为1s,
// return new Retryer.Default();
return new Retryer.Default(1000, 0, 3);
}
/**
* 自定义错误码
* 当feign调用返回HTTP报文时,会触发这个方法,方法内可以获得HTTP状态码,可以用来定制一些处理逻辑等等
* @return
*/
@Bean
public ErrorDecoder feginErrorDecoder() {
return (key, response) -> {
switch (response.status()) {
case 404:
log.error("请求Text服务404异常,返回:{}", response.body());
break;
case 503:
log.error("请求Text服务503异常,返回:{}", response.body());
break;
default:
log.error("请求Text服务异常,返回:{}", response.body());
}
// 其他异常交给Default去解码处理
// 这里使用单例即可,Default不用每次都去new
return new ErrorDecoder.Default().decode(key, response);
};
}
}
自定义重试类
@Slf4j
@Component
@NoArgsConstructor
public class CustomRetryer implements Retryer{
private int retryMaxAttempt;
private long retryInterval;
private int attempt = 1;
public CustomRetryer(int retryMaxAttempt, Long retryInterval) {
this.retryMaxAttempt = retryMaxAttempt;
this.retryInterval = retryInterval;
}
@Override
public void continueOrPropagate(RetryableException e) {
log.info("Feign retry attempt {} due to {} ", attempt, e.getMessage());
if(attempt++ == retryMaxAttempt){
throw e;
}
try {
Thread.sleep(retryInterval);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
@Override
public Retryer clone() {
return new CustomRetryer(6, 2000L);
}
}
全局配置
feign:
client:
config:
# 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
default:
# 日志级别
loggerLevel: FULL
# 连接超时
connectTimeout: 1000
# 读取超时
readTimeout: 1000
# 自定义重试
retryer: com.Fegin.config.CustomRetryer
<!-- feign:
client:
config:
defalut: # feign请求默认配置
connectTimeout: 2000
readTimeout: 3000
fiegnName: # fiegnName服务请求的配置,优先defalut配置。
connectTimeout: 5000 # 链接超时时间
readTimeout: 5000 # 请求
loggerLevel: full # 日志级别
errorDecoder: com.example.SimpleErrorDecoder #异常处理
retryer: com.example.SimpleRetryer # 重试策略
defaultQueryParameters: # 默认参数条件
query: queryValue
defaultRequestHeaders: # 默认默认header
header: headerValue
requestInterceptors: # 默认拦截器
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false #404响应 true-直接返回,false-抛出异常
encoder: com.example.SimpleEncoder #传输编码
decoder: com.example.SimpleDecoder #传输解码
contract: com.example.SimpleContract #传输协议 -->
fallback和fallbackFactory 是Fegin两种降级的方式。不能同时使用。
OpenFegin 3.0 以前 集成了hystrix和ribbon,3.0以后去除了。这时使用fallbackFactory会失效。
需要单独引入hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
配置文件 feign.hystrix.enabled 不见了改成 fegin.circuitbreaker.enabled
feign:
circuitbreaker:
enabled: true
@Slf4j
@Component
public class TextClientFallbackFactory implements FallbackFactory<TextClient> {
@Override
public TextClient create(Throwable cause) {
return new TextClient() {
@Override
public OrderDetail findOrderDetailById(String detailId) {
log.info("调用{Text}失败,回调:detailId:{}", detailId );
return new OrderDetail(detailId);
}
};
}
}
测试
也可以集成阿里的sentinel。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置文件开启
feign:
sentinel:
enabled: true
测试
sentinel和Hystrix区别
使用Fegin实现文件上传
@RestController
@RequestMapping("/fegin")
public class FeginController {
@Autowired
private TextClient textClient;
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file){
return textClient.upload(file);
}
}
文件上传
文件属性需要使用 @RequestPart 属性,而且在请求中添加 consumes = MediaType.MULTIPART_FORM_DATA_VALUE ,
文件表单上传请求通常使用的ContentType为 multipart/form-data ,通过以上直接调用feign的方式即可实现feign 文件上传。
@RequestPart:用在multipart/form-data表单提交请求的方法上。支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过http协议传输的。
@RequestParam也同样支持multipart/form-data请求。
他们最大的不同是,当请求方法的请求参数类型不再是String类型的时候。@RequestParam适用于name-valueString类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)。
produces:它的作用是指定返回值类型,不但可以设置返回值类型还可以设定返回值的字符编码;
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
@FeignClient(
value = "text",
configuration = FeginConfig.class,
fallbackFactory = TextClientFallbackFactory.class
)
public interface TextClient {
@PostMapping(value = "/text/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
String upload(@RequestPart("file") MultipartFile file);
}
Text服务
@RestController
@RequestMapping("/text")
public class TextController {
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file){
return file.getName();
}
}
测试