Feign是Netflix开源的声明式HTTP客户端,提供本地方法使用HTTP协议访问远程服务,屏蔽开发者远程调用和HTTP请求过程。Spring Cloud OpenFeign增强Feign功能支持Spring MVC注解,另外整合Ribbon和Eureka,从而使得Feign使用更加方便。
01 常见HTTP客户端
客户端 | 描述 |
---|---|
HttpClient | HttpClient是Apache Jakarta Common提供高效、丰富的Http协议客户端编程工具包,相比传统JDK自带URLConnection,使客户端发送HTTP请求变得容易,提升易用性、灵活性和开发效率。 |
Okhttp | OkHttp是Square公司贡献的安卓最轻量级框架,拥有简洁API、高效性能,支持多种协议(HTTP/2和SPDY),目的是取代HttpUrlConnection和Apache HttpClient。 |
HttpURLConnection | HttpURLConnection属于Java标准类,提供支持GET、 POST请求,使用比较复杂,不像HttpClient那样简单易用。 |
RestTemplate | RestTemplate是Spring用于访问Rest服务客户端,提供多种访问远程HTTP服务方法,能够大大提高客户端编写效率。 |
02 OpenFeign远程调用
2.1 依赖
xml
<properties>
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2 开启OpenFeign
java
@EnableFeignClients
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
}
2.3 远程调用
java
@FeignClient(name = "stock")
public interface StockFeignClient {
@GetMapping("/stock/reduce/{productId}")
String reduce(@PathVariable Integer productId);
}
@Slf4j
@RestController
public class OrderController {
@Autowired
private StockFeignClient stockFeignClient;
@GetMapping("/order/create")
public String createOrder(Integer productId,Integer userId) {
String reduce = stockFeignClient.reduce(productId);
log.info("减库存成功, result = {}", reduce);
return "下单成功";
}
}
03 Feign组成
接口 | 作用 | 默认值 |
---|---|---|
Feign.Builder | Feign入口 | Feign.Builder |
Client | Feign底层用什么请求 | 与Ribbon配合:LoadBalancerFeignClient 不与Ribbon配合:Feign.Client.Default |
Contract | 契约,注解支持 | SpringMVC Contract |
Encoder | 解码器,对象转换请求体 | SpringEncoder |
Decoder | 编码器,消息体转成对象 | ResponseEntityDecoder |
Logger | 日志管理器 | Slf4jLogger |
RequestInterceptor | 请求拦截器 | 无 |
Feign.Client.Default利用默认的HttpURLConnection,没有连接池和资源管理概念,性能比较差。
java
class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
private final boolean disableRequestBuffering;
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
public HttpURLConnection getConnection(final URL url) throws IOException {
// 打开链接
return (HttpURLConnection) url.openConnection();
}
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final URL url = new URL(request.url());
final HttpURLConnection connection = this.getConnection(url);
//...
OutputStream out = connection.getOutputStream();
//...
}
}
04 OpenFeign配置范围
4.1 代码方式-指定日志级别
Feign底层支持请求日志记录打印,主要包括NONE、BASIC、HEADERS和FULL四种日志级别,可以通过配置项指定。
级别 | 打印日志内容 |
---|---|
NONE(默认值) | 不记录任何日志 |
BASIC | 仅记录请求方法、URL、响应状态代码以及执行时间 |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header |
FULL | 记录请求和响应的header、body和元数据 |
4.2 开启日志打印
Feign是基于Spring Boot日志实现,所以需要开启Spring Boot日志打印功能。
yml
logging:
level:
com.feiyu: debug
4.3 类配置
4.3.1 全局配置类
方式一:通过@Configuration
注解,默认就是全局配置。
java
@Configuration
public class StockFeignConfiguration {
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
方式二:通过启动类@EnableFeignClients
注解,指定默认全局配置类。
java
public class StockFeignConfiguration {
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
@EnableFeignClients(defaultConfiguration = StockFeignConfiguration.class)
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
}
4.3.2 指定客户端配置类
java
public class StockFeignConfiguration {
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
// 指定客户端配置类
@FeignClient(name = "user", configuration = UserFeignConfiguration.class)
public interface UserFeignClient {
// http://user:10001/user/${userId}
@GetMapping("/user/{userId}")
String getUserName(@PathVariable Integer userId);
}
4.4 属性配置
4.4.1 属性全局配置
yml
yaml
logging:
level:
com.msb: debug
feign:
client:
config:
default: // default: 全局默认配置
loggerLevel: full
stock: // 指定应用配置
loggerLevel: debug
4.4.2 指定应用属性配置
yml
logging:
level:
com.feiyu: debug
feign:
client:
config:
stock: // 指定应用配置
loggerLevel: debug
05 配置项
5.1 契约配置
原生Feign不支持Spring MVC注解,Spring Cloud扩展演变为OpenFeign组件,如果需要使用原生注解定义客户端,可以配置契约实现,Spring Cloud默认锲约是SpringMvc Contract。
5.1.1 类配置
java
public class UserFeignConfiguration {
// 修改契约配置, 仅仅支持Feign原生注解, 如果想要支持其他注解,可以更改Contract实现类
@Bean
public Contract feignContract(){
return new Contract.Default();
}
}
java
@FeignClient(name = "user",configuration = UserFeignConfiguration.class)
public interface UserFeignClient {
// Feign原生注解
@RequestLine("GET /userInfo/{userId}")
UserDto getUserInfo(@Param("userId") Integer userId);
}
5.1.2 属性配置
yml
logging:
level:
com.feiyu: debug
feign:
client:
config:
stock: // 指定应用配置
loggerLevel: debug
contract: feign.Contract.Default # 指定Feign原生注解契约配置
5.2 编解码
Feign支持传输编码、解码数据,默认提供多种编码器实现,比如Gson、Jaxb、Jackson。开发者也可以实现Encoder和Decoder接口自定义扩展,默认配置实现类为SpringEncoder和SpringDecoder。
java
public interface Encoder {
void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
}
public interface Decoder {
Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
}
5.2.1 类配置
java
public class UserFeignConfiguration {
@Bean
public Decoder decoder(){
return new CustomDecoder();
}
@Bean
public Encoder encoder(){
return new CustomEncoder();
}
}
5.2.2 属性配置
java
feign:
client:
config:
# 微服名称
user:
encoder: com.xxx.CustomDecoder
decoder: com.xxx..CustomEncoder
5.3 请求拦截器
拦截器用于拦截请求发送和数据响应,提供权限认证、请求头传递等业务逻辑处理,便于封装SDK实现更灵活业务逻辑扩展。
java
public interface RequestInterceptor {
void apply(RequestTemplate template);
}
5.3.1 自定义扩展点
消费端定义OpenFeign拦截器,用于发送请求进行请求头设置,实现自定义拦截扩展逻辑。
java
public class FeignAuthRequestInterceptor implements RequestInterceptor {
private String tokenId;
public FeignAuthRequestInterceptor(String tokenId) {
this.tokenId = tokenId;
}
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", tokenId);
}
}
java
@Configuration
public class FeignConfig {
@Value("${feign.tokenId}")
private String tokenId;
@Bean
public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
return new FeignAuthRequestInterceptor(tokenId);
}
}
yml
feign:
tokenId: d874528b-a9d9-46df-ad90-b92f87ccc557
5.3.2 Servlet拦截器
提供端定义Servlet拦截器,用于拦截请求校验是否满足要求,实现业务定制化功能。
java
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 请求头
String authorization = request.getHeader("Authorization");
log.info("Request Header Authorization = {}",authorization);
return StringUtils.isNotBlank(authorization);
}
}
java
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor());
}
5.4 客户端设置
Feign底层默认使用JDK原生URLConnection发送HTTP请求,也可以配置其他客户端组件替换URLConnection,比如Apache HttpClient,OkHttp。
java
public interface Client {
// 客户端执行入口
Response execute(Request request, Options options) throws IOException;
}
5.4.1 配置Apache HttpClient
Feign通过自动装配实现客户端切换,导入依赖feign-httpclient
就会通过FeignAutoConfiguration配置Apache HttpClient客户端。
xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
yml
feign:
httpclient:
# 开启Apache HttpClient客户端
enabled: true
# 最大链接数, 默认200
max-connections: 200
# 单个路径最大连接数, 默认50
max-connections-per-route: 50
java
@EnableConfigurationProperties({
FeignClientProperties.class, FeignHttpClientProperties.class
})
public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
@Autowired(required = false)
private RegistryBuilder registryBuilder;
private CloseableHttpClient httpClient;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory cmf,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = cmf
.newConnectionManager(httpClientProperties.isDisableSslValidation(),
httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(),
httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(),
this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder()
.setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
}
}
5.4.2 设置OkHttp
xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
yml
feign:
okhttp:
enabled: true
# 线程池默认使用httpclient参数配置
httpclient:
max-connections: 200
max-connections-per-route: 50
java
@EnableConfigurationProperties({
FeignClientProperties.class, FeignHttpClientProperties.class
})
public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(
FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.followRedirects(followRedirects).connectionPool(connectionPool)
.build();
return this.okHttpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(okhttp3.OkHttpClient client) {
return new OkHttpClient(client);
}
}
}
5.5 超时配置
OpenFeign支持通过Options配置连接和读取超时时间,可以单独设置连接超时时间(ms,默认10s),或者请求处理超时时间(ms,默认60s)。
java
public static class Options {
private final long connectTimeout;
private final TimeUnit connectTimeoutUnit;
private final long readTimeout;
private final TimeUnit readTimeoutUnit;
private final boolean followRedirects;
public Options(long connectTimeout, TimeUnit connectTimeoutUnit,
long readTimeout, TimeUnit readTimeoutUnit, boolean followRedirects) {
super();
this.connectTimeout = connectTimeout;
this.connectTimeoutUnit = connectTimeoutUnit;
this.readTimeout = readTimeout;
this.readTimeoutUnit = readTimeoutUnit;
this.followRedirects = followRedirects;
}
@Deprecated
public Options(int connectTimeoutMillis, int readTimeoutMillis) {
this(connectTimeoutMillis, readTimeoutMillis, true);
}
}
5.5.1 消费端
消费端调用,可以需要通过进行设置就可以限制请求超时时间,也就是配置项设置配置参数。
java
public class FeignConfig {
@Bean
public Request.Options options(){
return new Request.Options(2000,50000);
}
}
@FeignClient(name = "user",configuration = FeignConfig.class)
public interface UserFeignClient {
// http://user:10001/user/${userId}
@GetMapping("/user/{userId}")
String getUserName(@PathVariable Integer userId);
}
5.5.2 提供端
java
@GetMapping("user/${userId}")
public String userName(@PathVariable Integer userId){
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "feiyu";
}

06 推荐配置方式
- 尽量使用属性配置,属性配置实现不了的情况再考虑用代码配置。
- 在同一个微服务内尽量保持单一性,比如统一使用属性配置,两种方式混用会增加问题定位复杂性。