1 一个简单的例子
openfeign
提供了一种声明式的http调用方式,用户只需要提供http的请求参数和接收响应的数据类型即可。同时,openfeign
的http接口定义方式和使用的注解与spring mvc定义服务端接口的时候一模一样,只是不需要提供接口具体的实现,大大方便了用户的使用,对spring开发者来说没有任何门槛。
要使用openfeign
调用http接口,只需要在pom.xml
文件中添加依赖:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在spring配置类上添加@EnableFeignClients
注解:
java
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class AppClient1Application {
public static void main(String[] args) {
SpringApplication.run(AppClient1Application.class, args);
}
}
然后定义一个添加了@FeignClient
注解的接口,如下所示,通过@FeignClient
的name
参数(即Feign client的名字)指定服务名,通过@PostMapping
、@PathVariable
、@RequestParam
、@RequestBody
等注解指定请求参数:
java
@FeignClient(name = "app-server")
public interface AppServerFeign {
@PostMapping(value = "/hello/{who}", headers = {"Content-Type: application/json"})
HelloResponse hello(@PathVariable String who, @RequestParam String type, @RequestBody HelloRequest request);
}
public class HelloRequest {
private String content;
}
public class HelloResponse {
private String who;
private String type;
private String content;
}
然后就可以调用接口了:
java
@RestController
@RequestMapping(value = "/")
public class Controller {
private final AppServerFeign appServerFeign;
public Controller(AppServerFeign appServerFeign) {
this.appServerFeign = appServerFeign;
}
@GetMapping(value = "/hello")
public HelloResponse hello() {
return appServerFeign.hello("movee", "email", new HelloRequest("hello world"));
}
}
在调用feign client接口的方法时,feign client会从注册中心获取服务的实例,然后构造http请求,并将响应解析为返回类型对象。
2 打开日志调试开关
调试中,我们经常想查看http的调用情况,可以通过以下方式打印openfeign的请求日志(注意:spring boot3的openfeign配置放在spring.cloud.openfeign
配置空间下了,不再放在feign
配置空间下,如果从spring boot2迁移过来,原先配置在feign空间下的配置会不生效):
yaml
logging:
level:
root: INFO
# 包的日志级别设置为DEBUG
movee.app.client1.io.AppServerFeign: DEBUG
spring:
cloud:
openfeign:
client:
config:
default:
# openfeign的默认日志等级
loggerLevel: FULL
也可以单独控制特定的feign接口的日志等级
yaml
spring:
cloud:
openfeign:
client:
config:
# openfeign client的默认配置
default:
# 默认不打印
loggerLevel: NONE
# 特定openfeign client的配置
app-server:
loggerLevel: FULL
openfeign共定义了四个loggerLevel
less
NONE: 不打印
BASIC: 打印请求方法、url、响应状态等基本信息
HEADERS:除了打印BASIC等级的基本信息外,还打印请求和响应头
FULL:打印比较详细的请求和响应信息,包括请求和响应body的具体内容
3 @FeignClient
注解的常用配置
3.1 直接指定服务的地址
上面的例子,是指定服务的名字,然后openfeign通过从注册中心获取实例后选择调用的地址。但是,有些外部服务并没有注册到注册中心中,需要通过直接地址调用,服务的直接地址可以通过@FeignClient
注解的url参数指定:
java
@FeignClient(name = "app-server", url = "http://127.0.0.1:8101")
public interface AppServerFeign {
}
3.2 同一个服务的读写请求使用不同的配置
我们对同一个服务提供的不同接口的处理方式往往不同,比如只读接口或幂等性写接口调用失败时,我们往往会尝试重新请求,而对非幂等性接口调用则不进行重试,这时可以将该服务需要不同配置的接口分别放在不同的openfeign接口中,并通过contextId
进行区分,如下所示(configuration
参数将在下面介绍):
java
@FeignClient(name = "app-server", contextId = "app-server-read", configuration = AppServerFeignReadConfig.class)
public interface AppServerFeign {
}
@FeignClient(name = "app-server", contextId = "app-server-write", configuration = AppServerFeignWriteConfig.class)
public interface AppServerFeign {
}
3.3 解析404响应body
默认情况下,openfeign只有在请求响应码在[200, 300)
范围内的响应body进行解析(实现该功能的类默认为ResponseEntityDecoder
, 它会委托给SpringDecoder
实现具体的解析功能),提取信息转换成接口定义的返回类对象,然后正常返回给调用者。如果请求的响应码为其他的值,则会调用ErrorDecoder
进行处理,默认最后会抛出异常给调用者。如果希望响应码为404时依然使用ResponseEntityDecoder
解析响应body,则可以设置dismiss404
值为true
java
@FeignClient(name = "app-server", dismiss404 = true)
public interface AppServerFeign {
}
3.5 异常回调函数fallback
和fallbackFactory
当openfeign接口调用抛出异常时,openfeign可以指定回调fallback方法,fallback方法中可以定义异常处理逻辑。
引入依赖:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
添加配置,使能circuitbreaker
yaml
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
编写fallback逻辑,fallback类实现了openfeign client接口:
java
@Component
public class AppServerFallback implements AppServerFeign {
@Override
public HelloResponse hello(String who, String type, HelloRequest request) {
return new HelloResponse(who, type, "fallback");
}
}
然后在@FeignClient
直接中指定fallback
实现类
java
@FeignClient(name = "app-server", fallback = AppServerFallback.class)
public interface AppServerFeign {
}
如果你想对不同的异常类型采取不同的善后处理逻辑,可以指定fallbackFactory
,由fallbackFactory
根据不同的异常生成不同的fallback类,对异常进行不同的处理。
java
@Component
static class AppServerFallbackFactory implements FallbackFactory<AppServerFallback> {
@Override
public AppServerFallback create(Throwable cause) {
// 在这里可以根据不同异常进行不同的处理,返回不同的异常处理实例
return new AppServerFallback();
}
}
@FeignClient(name = "app-server", fallbackFactory = AppServerFallbackFactory.class)
public interface AppServerFeign {
}
4 openfeign的configuration
第 3.2
节引入了@FeignClient
注解的configuration配置,但是没有介绍它的功能和用法,这里对它进行详细的介绍。每一个openfeign client接口可以指定一个configuration类,这个配置类可以创建一些自定义的bean,用来替换掉默认的bean。且这些bean只对配置的openfeign client有效,不影响其他的openfeign client。因为每一个openfeign client有一个子容器(context),子容器中的bean只有当前openfeign client可见。
4.1 定义LoggerLevel
java
public class AppServerFeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}
@FeignClient(name = "app-server", configuration = AppServerFeignReadConfig.class)
public interface AppServerFeign {
}
注意,这个AppServerFeignConfig
配置类不能添加@Component
、@Configuration
等注解,从而让他添加到spring 父容器中,如果这样的话就是全局生效了,而不是只针对当前的openfeign client有效。
4.2 拦截器
用户可添加http拦截,查看或修改请求的host、header、请求参数、request body或响应body等信息。如下所示:
java
@Configuration
public class AppProperties {
public static String appName;
public static String token;
@Value("${app.app-name}")
public void setAppName(String appName) {
DunetConfig.appName = appName;
}
@Value("${app.token}")
public void setToken(String token) {
DunetConfig.token = token;
}
}
public class AppServerFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
String appName = AppProperties.appName;
String token = AppProperties.token;
requestTemplate.header("app-name", appName);
long currentTime = System.currentTimeMillis();
requestTemplate.header("time-stamp", Long.toString(currentTime));
String authText = appName + currentTime + token;
String auth = DigestUtils.md5DigestAsHex(authText.getBytes()).toLowerCase();
requestTemplate.header("Authorization", auth);
requestTemplate.header("Content-Type", "application/json");
requestTemplate.header("Accept", "application/json");
};
}
}
4.3 重试
当http调用遇到临时性失败,如tcp连接或读写数据超时,或者服务有多个实例,为提高应用的可靠性,往往会对只读接口或幂等性写接口进行重试。openfeign也提供了重试机制,只要简单的配置即可。
4.3.1 tcp超时:ConnectionTimeout、ReadTimeout、WriteTimeout
tcp超时时,只需要配置重试策略即可
java
public class AppServerFeignConfig {
@Bean
public Retryer feignRetryer() {
// 最大请求次数为3,重试次数为2,首次重试时间间隔100毫秒,重试时间间隔以1.5的指数递升,重试最大间隔时间为1s
return new Retryer.Default(100, SECONDS.toMillis(1), 3);
}
}
4.3.2 http异常响应码
如果http响应码不是[200, 300),而你又想对某些情况下进行重试时,可以自定义ErrorDecoder
,在ErrorDecoder
中根据条件抛出RetryableException
,openfeign就会根据配置Retry
策略进行重试了。下面的ErrorDecoder
实现了对服务端错误进行重试的逻辑
java
public class AppServerFeignConfig {
private final ErrorDecoder defaultErrorDecoder = new ErrorDecoder.Default();
@Bean
public Retryer feignRetryer() {
// 最大请求次数为3,重试次数为2,首次重试时间间隔100毫秒,重试时间间隔以1.5的指数递升,重试最大间隔时间为1s
return new Retryer.Default(100, SECONDS.toMillis(1), 3);
}
@Bean
public ErrorDecoder feignError() {
return (key, response) -> {
if (response.status() >= 500) {
FeignException exception = FeignException.errorStatus(key, response);
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
new Date(),
response.request());
}
// 其他异常交给Default去解码处理
return defaultErrorDecoder.decode(key, response);
};
}
}
5 通过配置文件自定义openfeign的功能
上面通过@FeignClient
注解的configuration类对openfeign的配置均能通过配置文件进行配置。除了上面介绍的部分配置,更全面的配置请参考FeignClientProperties
以及FeignClientProperties.FeignClientConfiguration
类。
配置文件配置请参考如下配置:
yaml
spring:
cloud:
openfeign:
client:
config:
# app-server为feign client名字。如果值为default,表示默认配置或全局配置
app-server:
url: http://remote-service.com
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
responseInterceptor: com.example.BazResponseInterceptor
dismiss404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
queryMapEncoder: com.example.SimpleQueryMapEncoder
micrometer.enabled: false
6 指定底层http客户端组件
openfeign默认采用URLConnection
作为http客户端,也可以配置使用比较常用的apache httpclient
和okhttp
6.1 使用apache httpclient
xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
yaml
spring:
cloud:
openfeign:
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
time-to-live: 900
time-to-live-unit: seconds
6.2 使用okhttp
xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
yaml
spring:
cloud:
openfeign:
okhttp:
enabled: true