OpenFeign 是一种声明式、模板化的 HTTP 客户端,它使得编写和调用 HTTP API 变得更加容易和优雅。它是 Netflix 开源的一款基于 Java 的 HTTP 客户端,它内置了 Ribbon 负载均衡和 Hystrix 断路器等功能,可以与 Spring Cloud 很好地集成。
OpenFeign 可以使用注解方式定义 HTTP 接口,开发者无需编写接口实现,即可直接调用其他服务提供的 HTTP 接口。在调用时,OpenFeign 可以自动实现负载均衡、请求重试、断路器等功能,大大简化了服务间的调用过程。
OpenFeign 的主要优点包括:
- 简化的接口定义:使用注解方式定义 HTTP 接口,无需编写接口实现;
- 自动集成 Ribbon:内置 Ribbon 负载均衡功能,可实现客户端负载均衡;
- 自动集成 Hystrix:内置 Hystrix 断路器功能,可实现服务容错和熔断保护;
- 可扩展的编码解码器:支持自定义的编解码器,可扩展处理其他协议格式的请求和响应;
- 可扩展的请求拦截器:支持自定义的请求拦截器,可在请求前或请求后做一些额外的处理。
OpenFeign 通过自动配置,能够与 Spring Cloud 中的 Eureka、Ribbon、Hystrix、Zuul 等组件进行无缝集成,让服务之间的调用更加简单、可靠。同时,它还提供了一些高级特性,例如自定义编码解码器、请求拦截器、日志配置等,可以满足更多复杂的应用场景。
应用
依赖
<!--Open Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启feign调用
@SpringBootApplication
@EnableDiscoveryClient //spring提供的统一开启服务注册中心
@RestController
@EnableFeignClients
public class ClinetApplication {
public static void main(String[] args) {
SpringApplication.run(ClinetApplication.class);
}
@GetMapping("/index")
String test(){
return "{'msg':'hello world!'}";
}
}
一般来说feign调用都统一写在一个包,一般命名为feignclient,调用A生产者的类命名:AClient
使用@FeignClient(value=”producerOnline“)标注生产者类
使用@GetMapping("/index")标注方法
两个注解连用表明,调用http://producerOnline/index
使用时注入Aclient,直接调用方法即可。
Feign能够识别Spring MVC的注解,例如@RequestMapping、@RequestParam等。这是因为Feign本身就是基于Spring MVC构建的,它重用了Spring MVC的注解。
此外,Feign还提供了自己的注解,用于对远程API进行声明。这些注解包括:
@RequestLine:声明请求方法、路径和HTTP版本。@Headers:声明请求头。@Param:声明请求参数,也可以用@RequestParam。@Body:声明请求体,可以用于参数,方法上,完成请全体内数据的序列化于反序列化。@Body、@RequestBody和@ResponseBody在feign中可以替换一致,feign中更多的适合用于参数的序列化, Feign客户端默认会自动将响应转换成Java对象返回。@QueryMap:声明查询参数的Map,@Param也有同样的功能。@HeaderMap:声明请求头的Map。@Path:声明路径参数,和@PathVariable类似。
参数传递
restful风格参数
@FeignClient(name = "user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
@ResponseBody
User getUserById(@PathVariable("id") Long id);
}
Post参数
Get也是同理,可以是复杂对象,会自动转化,也可以不是复杂参数。可以使用RequestParam注解去指定某个参数的名称,实用这个注解在Feign中必须去指定名称
@FeignClient(name = "user-service")
public interface UserFeignClient {
@PostMapping("/user")
User createUser(@RequestBody User user);
}
Json格式参数
我使用OpenFeign发送Get请求并且携带json数据的时候报错了,但是apifox发送是没有问题的。
@FeignClient(name = "example-service")
public interface ExampleFeignClient {
@PostMapping(value = "/example", consumes = MediaType.APPLICATION_JSON_VALUE)
void createExample(@RequestBody ExampleDTO example);
}
在 OpenFeign 中,使用 @ResponseBody 注解并不是必须的,因为在默认情况下,Feign 将会使用 Jackson JSON 处理库来完成请求和响应中的 JSON 与 Java 对象的序列化和反序列化。
在使用 @FeignClient 注解声明的接口中,只需要使用 @RequestMapping、@GetMapping、@PostMapping 等请求注解来定义请求的 URL 和 HTTP 方法,以及使用 Java 对象作为请求或响应参数即可完成 JSON 转对象的过程,不需要额外添加 @RequestBody 或 @ResponseBody 等注解。
但是,如果使用了自定义的编解码器,或者在处理一些非标准的响应格式时,可能需要手动使用 @ResponseBody 注解来声明需要将响应体转换为 Java 对象。
在使用 @RequestBody 注解时,请求头的 Content-Type 属性可以不用手动设置,Feign 会自动将其设置为 application/json。 如果在请求头中已经指定了 Content-Type 属性为 application/json,那么在请求方法的参数中添加 @RequestBody 注解是可有可无的。
springmvc中不能默认将数据序列化到list,set,arraylist参数中,需要配合@RequestParam 使用。
设置请求头
稍微整理了一下在Feign中设置请求头
@Headers("Authorization: Bearer {token}")
@GET("/api/users/{id}")
User getUser(@Path("id") Long id, @Param("token") String token);
@RequestLine("GET /example")
public ResponseEntity<String> example(@RequestHeader("Authorization") String authorization);
//可以使用一个map集合,设置多个
@PostMapping(value = "/example", headers ={"Accept: application/json", "Authorization: Basic dXNlcjpwYXNzd29yZA=="}
public ResponseEntity<String> example();
@RequestLine("GET /api/user/{userId}")
User getUser(@Param("userId") Long userId, @Header("My-Header2") String header2Value);
使用RequestInterceptor,如果注入spring中,等于配置了全局的请求头,下面的单独配置。 @FeignClient注解会自动扫描所有带有@Configuration注解的类,并将它们应用到相应的Feign客户端中。
// @Configuration 加了这个主流就是全局配置
public class MyInterceptor implements FeignClientConfigurer {
@Override
public void configure(Feign.Builder builder) {
builder.requestInterceptor(new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
template.header("My-Header", "header-value");
}
});
}
}
@FeignClient(configuration =MyInterceptor.class)
public interface UserFeignClient {
@PostMapping(value = "/example"}
public ResponseEntity<String> example();
}
FeignClientConfigurer接口提供了一个configure()方法,可以用来配置Feign客户端的各种选项。除了在RequestInterceptor中添加请求拦截器外,FeignClientConfigurer还可以用于以下配置:
配置Feign客户端的超时时间、重试次数等选项。
javaCopy codepublic class MyConfigurer implements FeignClientConfigurer {
@Override
public void configure(Feign.Builder builder) {
builder.options(new Request.Options(5000, 10000));
}
}
配置Feign客户端的连接池选项。
javaCopy codepublic class MyConfigurer implements FeignClientConfigurer {
@Override
public void configure(Feign.Builder builder) {
builder.client(new OkHttpClient(new OkHttpClient.Builder().connectionPool(new ConnectionPool(10, 30, TimeUnit.SECONDS)).build()));
}
}
配置Feign客户端的Encoder和Decoder。
javaCopy codepublic class MyConfigurer implements FeignClientConfigurer {
@Override
public void configure(Feign.Builder builder) {
builder.encoder(new GsonEncoder())
.decoder(new GsonDecoder());
}
}
配置Feign客户端的日志输出。
javaCopy codepublic class MyConfigurer implements FeignClientConfigurer {
@Override
public void configure(Feign.Builder builder) {
builder.logLevel(Logger.Level.FULL);
}
}
配置Feign客户端的SSL选项。
javaCopy codepublic class MyConfigurer implements FeignClientConfigurer {
@Override
public void configure(Feign.Builder builder) {
builder.client(new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory()).build());
}
}
等等。
设置响应时间
默认想要时间是
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
其中 connectTimeout 表示连接超时时间,readTimeout 表示读取超时时间,单位均为毫秒。
上述配置表示,整个应用的 OpenFeign 客户端的默认配置为连接超时时间为 5 秒,读取超时时间为 10 秒。
Feign客户端日志
常见的日志级别包括:
- TRACE: 最详细的日志信息,通常用于问题排查;
- DEBUG: 比TRACE略少的日志信息,通常用于开发过程中的调试;
- INFO: 比DEBUG略少的日志信息,通常用于生产环境中的运行状态展示;
- WARN: 警告日志信息,表明程序在运行过程中可能会遇到一些潜在的问题,但是不会影响程序的运行;
- ERROR: 错误日志信息,表明程序在运行过程中遇到了某些不可恢复的错误,需要进行问题排查和处理;
- FATAL: 致命错误日志信息,表明程序遇到了致命的错误,可能会导致程序崩溃或无法正常运行。
我们可能需要详细展示feign的日志,默认feign在调用是并不是最详细日志输出,因此在开启输出日志时需要开启feign的详细日志展示。feign对日志的处理非常灵活可为每个feign客户端指定日志记录策略。
OpenFeign提供了4个日志级别供在配置时选择,分别是:
- NONE:不记录任何日志(默认值)
- BASIC:仅记录请求方法、URL、响应状态码以及执行时间
- HEADERS:除了记录BASIC级别的信息外,还会记录请求和响应的头信息
- FULL:记录所有请求与响应的明细,包括头信息、请求体、响应体等
# 给这个微服务开启最细日志输出
feign:
client:
config:
producerOnline-2:
loggerLevel: FULL
# 这条配置必须开启:否则针对某个服务的调用就不启用
logging:
level:
demo:
feignclient: DEBUG
服务列表刷新
从服务中心更新缓存的服务列表。配置OpenFeign的缓存刷新间隔时间。
如果使用Consul作为服务注册中心,可以通过在application.properties或application.yml中设置以下属性来配置OpenFeign的缓存刷新间隔时间:
spring.cloud.consul.discovery.catalogServicesWatch.delay=30000
其中,spring.cloud.consul.discovery.catalogServicesWatch.delay属性表示刷新缓存的间隔时间,单位为毫秒。在上面的示例中,OpenFeign会每隔30秒刷新一次缓存。
需要注意的是,Consul的缓存刷新是通过一个后台线程来实现的,因此需要在应用程序启动时创建一个线程池,并在关闭应用程序时销毁线程池,以确保线程安全性和资源释放。可以使用Spring的ThreadPoolTaskScheduler来实现此功能,示例代码如下:
@Configuration
public class ConsulConfiguration {
@Bean(destroyMethod = "destroy")
public ThreadPoolTaskScheduler consulScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("consulScheduler-");
scheduler.setPoolSize(1);
scheduler.initialize();
return scheduler;
}
@Bean
public ConsulAutoRegistration consulAutoRegistration(ConsulProperties properties, ServiceInstanceProvider provider,
ApplicationContext applicationContext, @Qualifier("consulScheduler") TaskScheduler scheduler) {
ConsulAutoRegistration registration = new ConsulAutoRegistration(properties, provider);
registration.setApplicationContext(applicationContext);
registration.setTaskScheduler(scheduler);
return registration;
}
}
在上面的示例代码中,创建了一个名为consulScheduler的线程池,并将其注入到了ConsulAutoRegistration中,以实现缓存刷新功能。
consulScheduler 是默认的定时器名称。
spring:
cloud:
consul:
discovery:
catalogServicesWatch:
delay: 5000 # 设置延迟时间为 5 秒
在这个例子中,定时器名称为默认的 consulScheduler,但是定时器延迟时间被设置为了 5 秒。如果希望使用自定义的定时器名称,可以在属性名中包含定时器名称,例如:
spring:
cloud:
consul:
discovery:
myCustomScheduler:
delay: 5000 # 设置延迟时间为 5 秒
在这个例子中,定时器名称被命名为 myCustomScheduler,并且定时器延迟时间也被设置为了 5 秒。