目录
1、Feign和OpenFeign
让通过HTTP请求访问远程服务,就像调用本地方法一样简单
官网地址:https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_openfeign
1.1 Feign概述
Feign 是由 Netflix 开发的一个声明式的 Web Service 客户端。Feign旨在使编写Java Http客户端变得更容易,使用Feign能让编写Web Service客户端更加简单,降低了访问http Api的复杂性。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(在一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量
1.2 OpenFeign概述
Spring Cloud OpenFeign 对 Feign 进行了二次封装,使得在 Spring Cloud 中使用 Feign 的时候,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程访问,更感知不到在访问 HTTP 请求。Spring Cloud OpenFeign 增强了 Feign 的功能,使 Feign 有限支持 Spring MVC 的注解,如 @RequestMapping 等。OpenFeign 的 @FeignClient 注解可以解析 Spring MVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,在实现类中做负载均衡并调用其他服务,默认集成了 Ribbon 与 Hystrix。
2、Feign和OpenFeign的区别和联系
Feign | OpenFeign |
---|---|
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端。Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务 | OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
依赖:spring-cloud-starter-feign | 依赖:spring-cloud-starter-openfeign |
3、Feign 与 Spring Cloud OpenFeign 的选择
Spring Cloud F 及 F 版本以上与 Spring Boot 2.0 以上一般使用 OpenFeign。
spring cloud 和 spring boot的版本对应关系:
4、如何使用OpenFeign
4.1 引入依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
4.2 启动类添加@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
// OpenFeign使用的是Ribbon做负载均衡,所以可以用Ribbon的方法修改负载均衡的规则
@RibbonClients(value={
@RibbonClient(name = "PROVIDER-PAYMENT-8001-3",configuration= changeRibbonRule.class)})
public class OpenFeignOrder80Start {
public static void main(String[] args) {
SpringApplication.run(OpenFeignOrder80Start.class,args);
}
}
4.3 创建一个接口,指定服务提供者地址和请求方式,并用@FeignClient
注释
@FeignClient(value = "PROVIDER-PAYMENT-8001-3",contextId="contextId1",qualifier = "paymentFeignService",decode404 = true,path = "${server.servlet.context-path}")
public interface PaymentFeignService
{
@GetMapping(value = "/provider/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
// 测试调用超时的方法,使用seconds传递方法等待时间
@GetMapping(value = "/payment/feign/timeout/{seconds}")
public String paymentFeignTimeOut(@PathVariable("seconds") String seconds);
}
说明:
PROVIDER-PAYMENT-8001-3
是服务提供者的服务名称。
@FeignClient
是支持配置文件占位符的:@FeignClient(name = "${feign.name}", url = "${feign.url}")
。
可以通过url属性来指定主机地址。
contextId
:除非你将对PROVIDER-PAYMENT-8001-3
服务的所有调用都写在同一个service中,不然就需要指定contextId,不指定会报bean冲突(具体分析见后面的@FeignClient注解参数说明)。
decode404:解析404状态码错误(具体分析见后面的@FeignClient注解参数说明)。
path:就是用来配置server.servlet.context-path
这样,就不用在每个接口地址前都自己拼接。
通过该接口生成的代理bean的名称是接口的全限定名称,本例子中为com.study.springcloud.openFeign.service.PaymentFeignService
。可以使用qualifier属性指定bean的一个别名paymentFeignService
,通过这两个bean名称都能获取到这个bean。
4.4 配置Feign客户端
在例子中只修改Feign的日志打印内容,更多配置,参考feign客户端相关配置.
@Configuration
public class FeignLogConfig {
@Bean
Logger.Level feignLoggerLevel()
{
// NONE:默认的,不显示任何日志;
// BASIC:仅记录请求方法、URL、响应状态码及执行时间;
// HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
// FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
return Logger.Level.FULL;
}
}
5、@FeignClient 注解参数说明
-
String value() default ""; 和 String name() default "";
value()和name()一样,是被调用的服务的服务名称,无论是否提供 url,都要配置该属性,可以使用${propertyKey}的方式配置 -
String serviceId() default "";
弃用,改为使用name -
String contextId() default "";
比如我们对同一个PROVIDER-PAYMENT-8001-3服务分多个接口调用想下面的写法,启动会报Bean名称冲突:
解决方案有以下两种:
1、将spring.main.allow-bean-definition-overriding=true
设置为true,该配置的作用是:是否允许通过注册与现有定义同名的bean
2、使用contextId隔离 -
String qualifier() default "";
生成的bean的别名,可以将这个别名作为bean的名称,通过getBean方式获取到bean。 -
String url() default "";
配置指定服务的地址,可用于在开发调试阶段,调用指定地址的服务。
例如:@FeignClient(name = "PROVIDER-PAYMENT-8001-3", url = "http://localhost:8085")
-
boolean decode404() default false;
当调用请求发生404错误时,decode404的值为true,那么会执行decoder解码,否则抛出异常。
当decode404=false
时改变正确的调用地址,访问返回404。浏览器页面打印的是异常信息
再将decode404=true
,此时,将不会在抛异常到客户端,而是将404解码。
解码也就是会返回固定的数据格式给你:
{"timestamp":"2021-12-29T13:23:08.962+0000","status":404,"error":"Not Found","message":"No message available","path":"/study/provider/payment/getaa/1"}
注:浏览器看到的返回值之所以是只有status
和message
这两个字段有值,是因为我自己定义的接口返回对象CommonResult
没有对应的字段存储解析处理后404错误的信息中的数据。
-
Class<?>[] configuration() default {};
指定Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等。 -
Class<?> fallback() default void.class;
定义容错的处理类,也就是熔断后的兜底方法,fallback的类必须实现@FeignClient
注解修饰的接口,并且是一个有效的 spring bean。
-
Class<?> fallbackFactory() default void.class;
容错处理的工厂,工厂必须生成实现由FeignClient注释的接口的回退类的实例。并且工厂类也必须是有效的 spring bean -
String path() default "";
方法级映射使用的路径前缀
写法可以是path = "${server.servlet.context-path}"
、path = "/study"
、path = "study"
-
boolean primary() default true;
是否将 feign 代理标记为主要 bean
6、feign客户端相关配置
Feign Client 默认的配置类为FeignClientsConfiguration
, 这个类在 spring-cloud-netflix-core 的 jar 包下.
当我们自己也定义了配置类后,例如上文中所创建的FeignLogConfig.class ,那么此时的客户端配置是由FeignClientsConfiguration
中已有的配置bean以及FeignLogConfig
中的配置bean组成(其中后者将覆盖前者)。
6.1 配置的默认值
Spring Cloud Netflix通过FeignClientsConfiguration
默认为feign提供以下beans(Beanleix beanName:ClassName):
Bean类型 | beanName | 实现类 |
---|---|---|
Decoder | feignDecoder | ResponseEntityDecoder(包装SpringDecoder) |
Encoder | feignEncoder | SpringEncoder |
Logger | feignLogger | Slf4jLogger |
Contract | feignContract | SpringMvcContract |
Feign.Builder | feignBuilder | HystrixFeign.Builder |
Client | feignClient | 如果启用了Ribbon,则它是LoadBalancerFeignClient,否则使用默认的伪装客户端。 |
Spring Cloud Netflix 默认情况下不会为feign提供以下beans,但仍会从应用程序上下文中查找以下类型的beans用来配置客户端。
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
6.2 使用注入Bean的方式配置
来自官网的例子:
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
FooConfiguration不需要用@Configuration进行注释。但是,如果是使用@Configuration注释的话,请注意将其从任何可能包含此配置的@ComponentScan中排除,因为它将成为feign.Decoder,feign.Encoder,feign.Contract等的默认来源。(因为FeignClientsConfiguration中配置的bean都使用@ConditionalOnMissingBean
注释了,即如果存在,则不会再注入该类型的bean)
可以通过将其与任何@ComponentScan或@SpringBootApplication放在单独的,不重叠的包中来避免这种情况,也可以在@ComponentScan中将其明确排除在外
将一个配置类应用到所有的feign客户端的方法
除了上面所说的将配置类放置在@ComponentScan的扫包范围内,还可以通过@EnableFeignClients 注解上的 defaultConfiguration 属性,将默认配置统一写在一个配置类中,然后在主程序入口用 defaultConfiguration 来应用配置类,这样也可以作用于所有 Feign。
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(defaultConfiguration = FeignLogConfig.class)
@RibbonClients(value={
@RibbonClient(name = "PROVIDER-PAYMENT-8001-3",configuration= changeRibbonRule.class)})
@Log
public class OpenFeignOrder80Start {
public static void main(String[] args) {
SpringApplication.run(OpenFeignOrder80Start.class, args);
}
}
6.3 使用配置文件的方式
若希望对单个指定特定名称的 Feign 进行配置,此时可以将 @FeignClient 注解的属性配置写在 application.yml 或者 application.properties,配置示例如下:
feign:
client:
config:
feignName: # 注意这个地方并不一定是服务的名称。如果@FeignClient没有配置contextId,则取value或者name
connectTimeout: 5000 # 连接超时时间
readTimeout: 5000 # 读超时时间设置
loggerLevel: full # 配置Feign的日志级别
errorDecoder: com.example.SimpleErrorDecoder # Feign的错误解码器
retryer: com.example.SimpleRetryer # 配置重试
requestInterceptors: # 配置拦截器
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder # Feign的编码器
decoder: com.example.SimpleDecoder # Feign的解码器
contract: com.example.SimpleContract # Feign的Contract配置
将feignName设置为default
(之所以是这个名字,是因为FeignClientProperties.class 的 private String defaultConfig = "default";
)则是对所有feign客户端生效。
另外这个地方配置一定要注意<feignName>
到底要配置啥:
- 如果你的feign客户端配置是:
@FeignClient(value = "PROVIDER-PAYMENT-8001-3",contextId="contextId1")
,那么<feignName>
为contextId1
- 如果你的feign客户端配置是:
@FeignClient(value = "PROVIDER-PAYMENT-8001-3"
即没有使用contextId属性显示设置,此时contextId默认读取name或value属性的值,<feignName>
就为PROVIDER-PAYMENT-8001-3
配置文件中可配置的内容参见FeignClientProperties的内部类FeignClientConfiguration.class
:
public static class FeignClientConfiguration {
private Logger.Level loggerLevel;
private Class<Retryer