Spring Could OpenFeign
一、OpenFeign的概述
OpenFeign是Spring Could基于Feign发明的。OpenFeign是一个声明式的Rest风格的网络访问客户端,它具有可插入式注释支持,包括feign注释和 JAX-RS 注释。 OpenFeign还支持可插入式编码器和解码器。春云增加了对Spring MVC 注释的支持,以及默认在Spring Web 中使用相同内容的支持。春云集成了eureka、Spring Could断路器以及Spring Could负载平衡器以及Ribbon,毫无疑问,在使用时提供负载平衡 。它和我们的RestTemplate的机制有点像,但是肯定跟强大,因此在分布式微服务中,它的作用主要在于网络请求这方面。
二、OpenFeign的使用
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
利用idea的maven工具查看该依赖包含了哪些依赖:
可以看出其中除了有Spring和自己的依赖外,还有hystrix(熔断)和ribbon的依赖,所以也就对应了概述中描述,他有负载均衡和熔断等功能。
2.主启动类
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在上面加@EnableFeignClients注解
3.创建服务访问接口
@Component
@FeignClient(name = "cloud-provider-service")
public interface FeignService {
@GetMapping("/payment/timeout")
public String timeout() throws InterruptedException ;
@GetMapping("/payment/ok")
public String ok();
}
这个是openFeign的和RestTemplate最大的不同点之一,可以看到我们通过@FeignClient注解表明这个接口是访问接口,这个接口我们也不用写实现类,他有默认的实现类,类似于Mybatis的Mapper,而且还有一个特点是它支持Spring Mvc中的注解的使用,在里面调用服务端的接口,其中@FeignClient的name属性只要写服务端在微服务中的名字就可以直接调用服务端了,当然这要得益于服务注册中心如Eureka。
4.在controller注入服务访问接口
@RestController
@Slf4j
public class OrderController {
@Resource
FeignService feignService;
@GetMapping("/consumer/payment/timeout")
public String timeout() throws InterruptedException {
log.info("timeout..");
return feignService.timeout();
}
@GetMapping("/consumer/payment/ok")
public String ok() {
log.info("ok..");
return feignService.ok();
}
}
这样我们就可以通过我们自己的接口访问到提供服务的节点了,这样更符合我们的编码规范,controller层调service,也是代码的耦合度降低,当然肯定远远不止这些好处。
三、OpenFeign的配置
1.使用@FeignClient()中的configuration属性
@Component
@FeignClient(name = "cloud-provider-service",configuration = TimeoutConfig.class)
public interface FeignService {
@GetMapping("/payment/timeout")
public String timeout() throws InterruptedException ;
@GetMapping("/payment/ok")
public String ok();
}
TimeoutConfig.java:
public class TimeoutConfig {
@Bean
Logger.Level level() {
return Logger.Level.FULL;
}
}
这样FeignService 的请求日志等级都是FULL,当然也可以配置feign.Decoder,feign.Encoder,feign.Contract,同理。
2.Spring配置类配置
上面配置当然直接在配置类中配置也可以,但是此时对这个接口中请求不生效,而且日志其实还要配置,后面会展示,可以看作是全局的。
@Configuration
public class FeignConfig {
@Bean
Logger.Level level() {
return Logger.Level.NONE;
}
}
3.配置文件配置
- 指定@FeignClient中name的配置
application.yml
feign:
client:
config:
#指定了名字
feignName: cloud-provider-service
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
metrics.enabled: false
- 针对所有的feignClient的默认配置
application.yml
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
3.配置文件和配置类的优先级
默认配置文件的优先级更高,也就配置文件中的配置优先生效,但是要改变这个特点可以配置:
feign.client.default-to-properties=false
4.让同一个名字相同不同的feignClient具有不同配置,利用@feignClient的contextId区别
FooClient.java
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
//..
}
BarClient.java
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}
两个接口的@FeignClient中name一样,他们访问同一个微服务,但是他们的配置类不一样,为了区分,用contextId区分。
5.超时处理
openFiegn处理超时,主要配置以下:
- connectTimeout:设置建立连接所用的时间的超时,超时会报出异常
- readTimeout:设置建立连接后从服务器读取到可用资源所用的超时时间
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
四、手动创建feign客户端
前面的演示,都是基于声明式的建立OpenFeign的客服端,很简洁,但是Spring Could这么强大,肯定也提供了手动创建。以下摘自官网例子:
@Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}
相当于创建了一个访问PROD-SVC微服务的客服端。
五、feign与断路器
我们知道,feign结合了hystrix的依赖,所以它肯定用hystrix的支持的
1.OpenFeign开启hystrix的支持
feign:
hystrix:
enabled: true
2.全局禁用OpenFeign使用hystrix
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
配置该类即可
3.OpenFeign使用短路器的实现回退
回退:也就是出现故障,异常或者超时等情况的时候,不让客户端一直等待,及时返回对用户友好的信息,而不是直接把异常打印给用户,也就一个兜底的返回。
要实现OpenFeign使用断路器实现回退,有如下步骤:
- 创建一个实现Feign客服端接口的类
HytrixService.java
@Component
public class HytrixService implements FeignService {
@Override
public CommonResult<Payment> get(Long id) {
return null;
}
@Override
public String timeout() {
return "解耦,对该服务进行处理...";
}
}
这里面方法的实现,就是对应这个请求出现异常等回退兜底的方法
- 在feign客户端的@FeignClinet中添加fallback属性,注明上面的类
Component
@FeignClient(name = "cloud-provider-service",fallback = HytrixService.class)
public interface FeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> get(@PathVariable("id") Long id);
@GetMapping("/payment/timeout")
String timeout();
}
- 实现效果
我们在payment/timeout这个接口调用的微服务中写一个运行时异常int i = 1 / 0;按照断路器的作用,遇到异常应该不会返回正常的值,而是返回兜底方法的结果
服务的提供者,出现运行时异常
@GetMapping("/payment/timeout")
public String timeout() {
int i = 1/0;
//故意使程序暂停3s
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
return port;
}
消费者利用feign客户端访问服务提供者的接口:
@GetMapping("/consumer/payment/timeout")
public String timeout() {
return feignService.timeout();
}
正常应该时返回端口号,出现异常应该有兜底方法,返回"解耦,对该服务进行处理…",这里int i=i/0,发生异常,应该走兜底方法:
可以看见确实走了回退方法。
六、OpenFeign日志
前面配置的时候也配置了日志,但不是主要讲解,其实除了上述的配置,还有一步,配置那个feign客户端以及作为什么日志级别打印出:
logging:
level:
com.kyg.could.service.FeignService: debug
指定是FeignService这个客户端,OpenFeign中这些请求的日志信息在spring 中以debug输出; OpenFeign中的日志级别:
- NONE,无记录(默认)。
- BASIC,仅记录请求方法和 URL 以及响应状态代码和执行时间。
- HEADERS,记录基本信息以及请求和响应标题。
- FULL,记录请求和响应的标题、正体和元数据。
配置好了之后,我们可以看效果:请求过后,控制台上有日志(这里设置feigin的级别为FULL,以debug级别输出):
可以看到请求的信息都以日志打印在控制台,并且都是debug级别。
七、OpenFeign中与@primary注解,以及@FeignClient的primay属性
在短路器中,我们创建了一个FeignSevice的实现类:
@Component
public class HytrixService implements FeignService {
@Override
public CommonResult<Payment> get(Long id) {
return null;
}
@Override
public String timeout() {
return "解耦,对该服务进行处理...";
}
}
并且这个类是注入到Spring 容器中的,以前我们没有实现自己创建实现类的时候,我们利用@AutoWire也能将FeignService 注入使用,意味着Spring中有默认的实现类,这时候,有两个实现类,按理来说,Spring会不知道找谁,原本在spring中,我们是用@primary来标注应该优先找谁的,我们这里没有配,但是为什么没有报错呢,是因为@FeignClient的primay属性
public @interface FeignClient {
@AliasFor("name")
String value() default "";
/** @deprecated */
@Deprecated
String serviceId() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default "";
String url() default "";
boolean decode404() default false;
Class<?>[] configuration() default {};
Class<?> fallback() default void.class;
Class<?> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
}
默认primary=true,就相当于优先找默认实现的,而不是我们自定义的,假设我们设置primary=true
@Component
@FeignClient(name = "cloud-provider-service",fallback = HytrixService.class,primary = false)
public interface FeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> get(@PathVariable("id") Long id);
@GetMapping("/payment/timeout")
String timeout();
}
启动则报错
Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.kyg.could.service.FeignService' available:
expected single matching bean but found 2: hytrixService,com.kyg.could.service.FeignService
所有我们默认不用更改primary属性,除非自己真的能写出优于默认实现类的实现方法。
八、总结
以上这些知识都是阅读官网,结合自己看视频总结,可能不是很全,但是常用,写这些文章,主要还是逼着自己看官网学习,锻炼自我学习能力,同时也是对知识的总结,方便复习,也希望可以帮助到阅读的小伙伴,可能自己的理解可能也有偏差,望读者指教纠正,共勉!