微服务OpenFeign使用手册

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 推荐配置方式

  1. 尽量使用属性配置,属性配置实现不了的情况再考虑用代码配置。
  2. 在同一个微服务内尽量保持单一性,比如统一使用属性配置,两种方式混用会增加问题定位复杂性。
相关推荐
guojl2 小时前
微服务OpenFeign源码分析
spring cloud·微服务
chanalbert3 小时前
从单体到微服务:Spring Cloud 开篇与微服务设计
spring boot·spring·spring cloud
Code季风1 天前
Gin Web 层集成 Viper 配置文件和 Zap 日志文件指南(下)
前端·微服务·架构·go·gin
Code季风1 天前
Gin Web 服务集成 Consul:从服务注册到服务发现实践指南(下)
java·前端·微服务·架构·go·gin·consul
万物皆字节2 天前
spring cloud负载均衡之FeignBlockingLoadBalancerClient、BlockingLoadBalancerClient
spring cloud
掘金-我是哪吒2 天前
分布式微服务系统架构第156集:JavaPlus技术文档平台日更-Java线程池使用指南
java·分布式·微服务·云原生·架构
Ken_11152 天前
SpringCloud系列(49)--SpringCloud Stream消息驱动之实现生产者
spring cloud
DavidSoCool2 天前
RabbitMQ使用topic Exchange实现微服务分组订阅
分布式·微服务·rabbitmq
掘金-我是哪吒2 天前
分布式微服务系统架构第158集:JavaPlus技术文档平台日更-JVM基础知识
jvm·分布式·微服务·架构·系统架构