微服务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. 在同一个微服务内尽量保持单一性,比如统一使用属性配置,两种方式混用会增加问题定位复杂性。
相关推荐
fanly1114 小时前
Surging AI Agent 完整产品介绍
微服务·microservice
吃饱了得干活3 天前
Spring Cloud Gateway 微服务网关:路由、断言、过滤器
java·spring cloud
蝎子莱莱爱打怪7 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking8 天前
Java微服务练习方式
java·后端·微服务
米丘11 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质14 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
慧一居士14 天前
Feign的GET请求如何传递对象参数?
java·spring cloud
我登哥MVP14 天前
SpringCloud Alibaba 核心组件解析:服务链路追踪
java·spring boot·后端·spring·spring cloud·java-ee·maven
慧一居士14 天前
SpringCloud 微服务Feigin 用的完整调用端和被调用的示例
java·spring cloud
霸道流氓气质14 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化