1. 使用Feign调用服务接口
在Java中,进行项目开发基本都是模块划分,划分模块之后,就需要进行接口的调用。那么该如何进行接口的调用呢??
HttpClient
HttpClient提供了高效的、最新的、功能丰富的支持Http协议的客户端编程工具包。
HttpClient相比传统JDK自带的URLConnection,提升了易用性和灵活性,使客户端发送HTTP请求变得容易,提高了开发的效率。
OkHttp
一个处理网络请求的开源项目,是安卓端最火的轻量级框架,OkHttp拥有简洁的API、高效的性能,并支持多种协议(Http和SPDY)。
HttpURLConnection
HttpURLConnection 是Java的标准类,它继承自URLConnection,可用于向网站发送GET请求、POST请求。HttpURLConnection使用比较复杂,不像HttpClient那样容易使用。
RestTemplate
RestTemplate 是Spring 提供的用于访问Rest 服务的客户端,RestTemplate提供了多种便捷访问远程Http 服务的方法,能够大大提高客户端的编写效率。
上面介绍了最常见的几种接口调用方法,下面要介绍的Feign更加简单、方便。
Feign 是一个声明式的REST客户端,它能够让REST调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
Feign会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。
并且,Feign可以与Eureka 和Ribbon组合使用以支持负载均衡。
1.1 在 Spring Cloud 中集成Feign
下面通过一个简单的案例去描述Feign接口调用的一个简单过程:
需求:
将模块feign-interface和feign-invoke注册到注册中心,在feign-invoke模块中调用feign-interface中的接口。
首先需要创建一个模块eureka-server作为注册中心,并配置上security的安全框架。因为在我博客上已经写有Eureka服务注册中心的相关搭建,这里就简单将位置列出来即可,若感兴趣可以进入博客详细学习:小曾博客。
eureka-server:

pom.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.1.3.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.2.1.RELEASE</version> </dependency> </dependencies>
|
application.properties配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| # 服务器端口 server.port=8010
# 服务应用名称 spring.application.name=eureka-server
# 此应用为注册中心,false:不向注册中心注册自己。 eureka.client.register-with-eureka = false
# 注册中心职责是维护服务实例,false:不检索服务。 eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http:
# spring-security的配置信息: spring.security.user.name=xiaozeng spring.security.user.password=123456
#eureka.instance.prefer-ip-address=true
# eureka.instance.status-page-url=www.baidu.com
eureka.server.enableSelfPreservation=false
|
因为会集成Spring Security,所以在配置文件中必须添加下面两行代码,不添加的话会报401的权限错误:
1 2 3
| # spring-security的配置信息: spring.security.user.name=xiaozeng spring.security.user.password=123456
|
主启动类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.self.eurekaserver;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer @SpringBootApplication public class EurekaServerApplication {
public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class,args); } }
|
下面是Security的相关配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.self.eurekaserver.config;
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); } }
|
至此,Eureka-server注册中心就搭建成功了,启动项目,访问http://localhost:8010即可登录Eureka的Web页面。
下面就是进入feign的正式学习:
在Spring Cloud 中集成Feign 的步骤相当简单,首先还是创建项目feign-interface,并加入相关的依赖,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.1.3.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.0.RELEASE</version> </dependency> </dependencies>
|
编写application.propeties配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| # 服务器端口 server.port=8088
# 服务应用名称 spring.application.name=feign-interface
# Eureka注册中心地址 eureka.client.service-url.defaultZone=http:
# 使用IP注册 eureka.instance.prefer-ip-address=true
# 定义实例ID格式 eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
eureka.instance.status-page-url=http:
spring.security.user.name=xiaozeng spring.security.user.password=123456
|
启动类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.self.feign;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}) @EnableFeignClients public class FeignInterfaceApplication {
public static void main(String[] args) { SpringApplication.run(FeignInterfaceApplication.class,args); } }
|
在启动类上添加@EnableFeignClients注解,如果你的Feign接口定义跟你启动类不在同一包名下,还需要制定扫描的包名@EnableFeignClients(basePackages=”com.self.xxxx”)。在上面的@SpringBootApplication注解中排除了org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class ,是为了解决Security的一个权限问题,如果不排除这个自动配置,将会报401的权限问题。
因为是将这个模块注册到Eureka注册中心,然后客户端调用带模块的接口返回数据,所以必须在此模块中创建相应的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.self.feign.controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class DemoController {
@GetMapping("/user/hello") public String hello(){
return "Hello Welcome to Study Feign~~~"; } }
|
到了这里,启动Eureka注册中心,和feign-interface就可以将模块注册进注册中心。

上面的步骤就是创建一个注册中心,并且将服务接口提供者已经注册到了注册中心,那么,既然已经有了服务的提供者,那么必须要有人去使用把??所以下面咱就来创建feign-invoke模块调用服务提供者提供的接口:
pom.xml :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.1.3.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.0.RELEASE</version> </dependency> </dependencies>
|
application.properties配置文件 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| # 服务器端口 server.port=8099
# 服务应用名称 spring.application.name=feign-invoke
# Eureka注册中心地址 eureka.client.service-url.defaultZone=http:
# 使用IP注册 eureka.instance.prefer-ip-address=true
# 定义实例ID格式 eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
eureka.instance.status-page-url=http:
spring.security.user.name=xiaozeng spring.security.user.password=123456
|
启动类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.self.feign;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class FeignApplication {
public static void main(String[] args) { SpringApplication.run(FeignApplication.class,args); } }
|
当我们写到这,服务调用者的基本配置已经完成。启动项目就可以将模块注册进注册中心:

如果需要调用服务提供者提供的接口,那么在调用端这里必须要获取提供者暴露出来的接口信息,因此需要在调用端创建一个DemoRemoteClient接口,标识一下服务提供者暴露的接口路径等信息:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.self.feign.client;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "feign-interface") @Component public interface DemoRemoteClient {
@GetMapping("/user/hello") public String hello(); }
|
最后我们可以测试一下,使用调用端的DemoRemoteClient对象调用服务提供者提供的hello接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.self.feign.controller;
import com.self.feign.client.DemoRemoteClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class TestController {
@Autowired private DemoRemoteClient demoRemoteClient;
@GetMapping("/callHello") public String callHello() {
String result = demoRemoteClient.hello(); System.out.println("远程调用结果:" + result); return result; } }
|
当模块都启动后,访问http://localhost:8099/callHello时,由于配置了Spring Security的原因,所以需要将配置文件的用户名密码填写登录才可以访问:

登录后可以发现页面返回数据:

控制台也会有对应的输出:

【注意:在客户端(服务调用者)接口上加@FeignClient注解,就相当于标识这是一个Feign客户端,value的属性就是对应的服务名称,也就是你需要调用那个服务的接口。】
定义方法时直接复制接口的定义即可,这样做的好处就是,每写一个接口就要对应写一个调用的Client,后面打成公共的jar,这样无论是哪个项目需要调用接口,只要引入公共的接口SDK jar 即可,不用重新定义一遍了。
2. Feign自定义配置
Feign提供了很多的的扩展机制,让用户可以更加灵活的使用,下面就一起来学习Feign的一些自定义配置。
2.1 日志配置
在进行接口调用时,比如接口调用失败,参数没收到等问题,或者想看看调用的性能,就需要配置Feign的日志,让Feign把请求信息输出出来。
首先定义一个配置类,获取日志级别,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.self.feign.config;
import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class FeignConfig {
@Bean public Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } }
|
Feign 的Level日志级别有4种,分别是:
- NONE:不输出日志。
- BASIC:只输出请求方法的URL和响应的状态码以及接口执行的时间。
- HEADERS:将BASIC信息和请求头信息输出。
- FULL:输出完整的请求信息。
配置类建好后,我们需要在Feign Client(服务调用方)中的@FeignClient注解中指定使用的配置类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.self.feign.client;
import com.self.feign.config.FeignConfig; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "feign-interface",configuration = FeignConfig.class) @Component public interface DemoRemoteClient {
@GetMapping("/user/hello") public String hello(); }
|
在配置文件中执行Client的日志级别才能正确的输出日志,格式是”logging.level.client类地址=级别”。
1
| logging.level.com.self.feign.client.DemoRemoteClient=DEBUG
|
最后,通过Feign的客户端8099端口去调用/callHello,就可以看到控制台输出的调用信息,如下图:

2.2 契约配置
其实,原生的Feign是不支持Spring MVC注解的,只是因为Spring Cloud 在Feign的基础上做了扩展,才使Feign支持Spring MVC的注解来调用。
如果你想在Spring Cloud中使用原生的注解方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud中默认的是SpringMvcContract,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.self.feign.config;
import feign.Contract; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class FeignConfig {
@Bean public Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; }
@Bean public Contract feignContract(){ return new Contract.Default(); } }
|
当配置了默认契约之后,之前定义的Client就用不了了,因为之前上面的注解都是Spring MVC的注解。
效果如下图:

2.3 Basic 认证配置
通常我们调用的接口都是有权限控制的,很多时候可能认证的值是通过参数去传递的,还有就是通过请求头去传递认证信息,比如Basic认证方式。在Feign中我们可以直接配置Basic认证,代码如下:
1 2 3 4 5 6 7
| @Configuration public class FeignConfig { @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); } }
|
或者你可以自定义属于自己的认证方式,其实就是自定义一个请求拦截器。在请求之前做认证操作,然后往请求头中设置认证之后的信息。通过实现RequestIntercepter接口来自定义认证方式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.self.feign.interceptor;
import feign.RequestInterceptor; import feign.RequestTemplate;
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
public FeignBasicAuthRequestInterceptor(){
}
@Override public void apply(RequestTemplate requestTemplate) { System.out.println("进入请求拦截,用户名密码认证~~~"); } }
|
然后将配置改成我们自定义的就可以了,这样当Feign去请求接口的时候,每次请求之前都会进入FeignBasicAuthRequestInterceptor的apply方法中,在在里面就可以做属于你的逻辑了,代码如下:
1 2 3 4 5 6 7
| @Configuration public class FeignConfig { @Bean public FeignBasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new FeignBasicAuthRequestInterceptor(); } }
|
尝试去访问8099下的callHello接口后,控制台会输出以下结果:

2.4 超时时间配置
通过Options可以配置连接超时时间和读取超时时间(代码如下),Options的第一个参数是连接超时时间(ms),默认值是10 × 1000;第二个是读取超时时间(ms),默认值是60 × 1000。
1 2 3 4 5 6 7
| @Configuration public class FeignConfig { @Bean public Request.Options options() { return new Request.Options(5000, 10000); } }
|
2.5 客户端组件配置
Feign中默认使用JDK原生的URLConnection发送HTTP请求,我们可以集成别的组件来替换掉URLConnection,比如Apache HttpClient,OkHttp。
配置 OkHttp 只需要加入OkHttp 的依赖,代码如下:
1 2 3 4
| <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
|
然后修改配置,将Feign的 HttpClient 禁用,启用OkHttp,配置如下:
1 2 3
| #feign 使用 okhttp feign.httpclient.enabled=false feign.okhttp.enabled=true
|
关于配置可参考源码org.springframework.cloud.openfeign.FeignAutoConfiguration。
HttpClient自动配置源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration @ConditionalOnClass(ApacheHttpClient.class) @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) protected static class HttpClientFeignConfiguration { @Autowired(required = false) private HttpClient httpClient; @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient() { if (this.httpClient != null) { return new ApacheHttpClient(this.httpClient); } return new ApacheHttpClient(); } }
|
OkHttp 自动配置源码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration @ConditionalOnClass(OkHttpClient.class) @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true) protected static class OkHttpFeignConfiguration { @Autowired(required = false) private okhttp3.OkHttpClient okHttpClient; @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient() { if (this.okHttpClient != null) { return new OkHttpClient(this.okHttpClient); } return new OkHttpClient(); } }
|
上面所示两段代码分别是配置 HttpClient 和 OkClient 的方法。其通过@ConditionalOnProperty中的值来决定启用哪种客户端(HTTPClient 和 OkHttp),@ConditionalOnClass 表示对应的类在 classpath 目录下存在时,才会去解析对应的配置文件。
2.6 使用配置自定义Feign 的配置
除了使用代码的方式来对Feign进行配置,我们还可以通过配置文件的方式来指定 Feign 的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # 连接超时时间 feign.client.config.feignName.connectTimeout=5000 # 读取超时时间 feign.client.config.feignName.readTimeout=5000 # 日志等级 feign.client.config.feignName.loggerLevel=full # 重试 feign.client.config.feignName.retryer=com.xxx.xxxRetryer # 拦截器 feign.client.config.feignName.requestInterceptors[0]=com.xxx.xxxRequestInterceptor feign.client.config.feignName.requestInterceptors[1]=com.xxx.xxxRequestInterceptor # 编码器 feign.client.config.feignName.encoder=com.xxx.xxxEncoder # 解码器 feign.client.config.feignName.decoder=com.xxx.xxxDecoder # 契约 feign.client.config.feignName.contract=com.xxx.xxxContract
|
2.7 继承特性
Feign 的继承特性可以让服务的接口定义单独抽出来,作为公关的依赖,以方便使用。
创建一个 Maven 项目,feign-inherit-api,用于存放API接口的定义,增加Feign 的依赖,代码如下:
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
定义接口,指定服务名称,代码如下:
1 2 3 4 5
| @FeignClient("feign-inherit-provider") public interface UserRemoteClient { @GetMapping("/user/name") String getName(); }
|
创建一个服务提供者feign-inherit-provider,引入feign-inherit-api,代码如下:
1 2 3 4 5
| <dependency> <groupId>net.biancheng</groupId> <artifactId>feign-inherit-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
|
实现UserRemoteClient 接口,代码如下:
1 2 3 4 5 6 7
| @RestController public class DemoController implements UserRemoteClient { @Override public String getName() { return "xiaozeng"; } }
|
创建一个服务消费者feign-inherit-consumer,同样需要引入feign-inherit-api,用于调用feign-inherit-provider提供的/user/name 接口,代码如下:
1 2 3 4 5 6 7 8 9 10
| @RestController public class UserController { @Autowired private UserRemoteClient userRemoteClient; @GetMapping("/call") public String callHello() { String result = userRemoteClient.getName(); System.out.println("getName调用结果:" + result); } }
|
通过将接口的定义单独抽取出来,服务提供者去实现接口,服务消费者直接就可以引入定义好的接口进行调用,非常方便。
2.8 多参数请求构造
多参数请求构造分为GET 请求 和POST 请求两种方式,首先来看GET请求的多参数请求构造方式,代码如下:
1 2
| @GetMapping("/user/info") public String getUserInfo(@RequestParam("name")String name,@RequestParam("age")int age);
|
另一种是通过Map来传递参数,参数数量可以动态的改变,个人是不建议使用Map进行传参的(Map传递参数最大的问题就是可以随意传参)。代码如下:
1 2
| @GetMapping("/user/detail") public String getUserDetail(@RequestParam Map<String, Object> param);
|
POST 请求多参数就定义一个参数类,通过@RequestBody 注解的方式来实现,代码如下:
1 2
| @PostMapping("/user/add") public String addUser(@RequestBody User user);
|
实现类中也需要加上@RequestBody 注解,代码如下:
1 2 3 4 5 6 7
| @RestController public class UserController implements UserRemoteClient { @Override public String addUser(@RequestBody User user) { return user.getName(); } }
|
【注意:使用继承特性的时候,实现类也需要加上@RequestBody 注解】。
至此,Feign的基本使用已经结束……