Feign 是声明式、模板化的 HTTP 客户端, 可以帮助我们更快捷、优雅地调用 HTTP API;Spring Cloud 为 Feign 添加了 Spring MVC 的注解支持,并整合了 Ribbon 和 Eureka 来为使用 Feign 时提供负载均衡;在 Spring Cloud 中使用 Feign 是非常容易的。
本篇主要介绍 SpringBoot 中要玩转 Feign 需要掌握的如添加 pom 依赖、客户端注解启用、切换底层 HttpClient、配置数据压缩、调整日志级别、定制配置、配置的优先级机制、增加拦截器以及拦截器的追加机制等知识。
org.springframework.cloud
spring-cloud-starter-openfeign
2.1.3.RELEASE
复制代码
在 SpringBoot 的启用类上添加注解@EnableFeignClients
,@EnableFeignClients
用于开启 Feign,会自动扫描@FeignClient
标注的 FeignClient 接口。
@SpringBootApplication
@EnableFeignClients
@EnableWeb
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class,args);
}
}
复制代码
@FeignClient(
name = "demo-service",
url = "http://localhost:8080/feign/server/",
configuration = FeignInterceptor.class,
fallback = TestService.DefaultFallback.class
)
public interface TestService {
@RequestMapping(value = "/getError/{id}", method = RequestMethod.GET)
public String getError(@RequestParam("id") Integer id);
@RequestMapping(value = "/get1", method = RequestMethod.GET)
public String get1();
@RequestMapping(value = "/get2/{param}", method = RequestMethod.GET)
public String get2(@RequestParam("param") String param);
@RequestMapping(value = "/post1", method = RequestMethod.POST)
public FeignDemo post1(@RequestBody FeignDemo demo);
复制代码
@RestController
@RequestMapping("/feign/server")
public class FeignServerController {
@GetMapping("/get1")
public String get1() {
return "get1";
}
@GetMapping("/get2/{para}")
public String get2(@PathVariable("para") String para){
return para;
}
@PostMapping("/post1")
public FeignDemo post1(@RequestBody FeignDemo demo) {
return demo;
}
}
复制代码
public class FeignDemo {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "FeignDemo{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {FeignApplication.class},webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ActiveProfiles("dev,feign")
public class FeignClientTest {
@Autowired
private TestService testService;
@Test
public void testFallback(){
testService.getError(1);
}
@Test
public void testGet1(){
System.out.println(testService.get1());
System.out.println(testService.get2("abc"));
System.out.printf("..");
FeignDemo feignDemo = new FeignDemo();
feignDemo.setName("name");
feignDemo.setAge(1);
System.out.println(testService.post1(feignDemo));
}
@Component
public class DefaultFallback implements TestService {
@Override
public String getError(@RequestParam("id") Integer id){
return "";
}
@Override
public String get1() {
return null;
}
@Override
public String get2(String param) {
return null;
}
@Override
public FeignDemo post1(FeignDemo demo) {
return null;
}
}
}
复制代码
Feign 中自带的是 HttpURLConnection,这个 client 健壮性差,可替换为成熟的 Apache HttpClient 或 OkHttp 来进行网络请求。
使用 Apache 的 httpclient
替换 Feign 中默认的 client。
2.1.1 添加依赖
org.apache.httpcomponents
httpclient
io.github.openfeign
feign-httpclient
10.4.0
复制代码
2.1.2 配置启用
配置中添加如下信息,表示启用httpclient
。
feign:
httpclient:
enabled: true
复制代码
2.2.1 添加依赖
在 Feign 中使用OkHttp
作为网络请求框架,则只需要在 pom 文件中加上feign-okhttp
的依赖,代码如下:
io.github.openfeign
feign-okhttp
10.2.0
复制代码
2.2.2 配置启用
feign:
okhttp:
enabled: true
复制代码
在发送和接收请求的时候,其内部将日志的打印输出定义成了四个等级,对应的详情如下:
级别 | 说明 |
---|---|
NONE | 不做任何记录 |
BASIC | 仅记录请求方法和 URL 以及响应状态代码和执行时间 |
HEADERS | 记录基本信息以及请求和响应标头 |
FULL | 记录请求和响应的标题,正文和元数据 |
注意需要指定接口的全限定名
logging:
level:
com.zto.titans.test.feign.service.TestService : DEBUG
复制代码
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
复制代码
这个一看即懂,不再废话。
可以分别对 HTTP 通信的request
和response
设置是否启用 GZIP 压缩,配置方法如下:
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
min-request-size: 2048 # 配置压缩数据大小的下限
response:
enabled: true # 配置响应GZIP压缩
复制代码
有 2 种途径设置 FeignClient 的配置,通过自定义配置类来设置配置和在配置文件中设置,其中配置文件方式有点特殊,它里边可以指定全局配置对所有 FeignClient 有效,也可以为特定名称的 FeignClient 设置专属的配置。
实现一个配置类
public class TestConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
复制代码
将配置类 TestConfiguration
指定给configuration
。
@FeignClient(
name = "test-service",
configuration = {FeignInterceptor2.class,TestConfiguration.class}
)
复制代码
feign.client.config.default.xxx ,这个default意为
全局的配置属性。
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
复制代码
feign.client.config.feignName.xxx , 给名字为feignName的FeignClient
指定专属的配置。
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
复制代码
从org.springframework.cloud.openfeign.FeignClientFactoryBean#configureFeign
中可以确认以上 3 种配置的优先级:
configureUsingConfiguration(context, builder); // 1
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder); //2
configureUsingProperties(properties.getConfig().get(this.contextId),builder);//3
复制代码
feign.client.config.default.xxx
设置全局配置feign.client.config.feignName.xxx
设置专属配置5.4.1 优先级的效果
配置文件里的专属配置 -覆盖-> 配置文件里的全局配置 -覆盖-> 配置类的配置
5.4.2 追加的原则
RequestInterceptor
是拦截器,可以在发送前做一些处理,比如统一添加header
信息。每一类中的requestInterceptors
可以存储多个拦截器,拦截器并非覆盖的效果,而是链式追加的效果;从执行顺序来看优先级是:1 > 2 > 3,即先执行 配置类中指定的拦截器,然后是 配置文件中指定的全局拦截器,最后是配置文件中指定的专属拦截器。
需特别注意:RequestInterceptor
的实现类(例如 RI-A,RI-B)上如果添加了@Component
注解,就都会被扫描识别到,并被追加到第一类的requestInterceptors
列表中;倘若不小心 RI-A 还在第 2 类中又被指定了,则还会将拦截器 RI-A 追加在第二类的requestInterceptors
列表中,结果是会 RI-A 总计会执行 2 次;若也在第三类中指定 RI-A,则 RI-A 也在其列表中追加,结果是 RI-A 总计会执行 3 次。
5.4.3 拦截器的效果验证
以一个实例来验证说明效果
class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("user", "myuser1");
requestTemplate.header("password", "mypassword");
}
}
复制代码
class FeignInterceptor1 implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("user1", "myuser1");
requestTemplate.header("password1", "mypassword1");
}
}
复制代码
class FeignInterceptor2 implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("user2", "myuser2");
requestTemplate.header("password2", "mypassword2");
}
}
复制代码
@FeignClient(
name = "test-service",
url = "http://localhost:8080/feign/server/",
configuration = {FeignInterceptor.class,TestConfiguration.class},
fallback = TestService.DefaultFallback.class
)
复制代码
default
指定了一个,test-service
里指定一个
feign:
httpclient:
enabled: true
okhttp:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
#loggerLevel: none
requestInterceptors:
- com.zto.titans.test.feign.service.FeignInterceptor1
test-service:
#loggerLevel: basic
requestInterceptors:
- com.zto.titans.test.feign.service.FeignInterceptor2
logging:
level:
com.zto.titans.test.feign.service.TestService : DEBUG
复制代码
根据追加逻辑,最终执行的顺序是:
本篇主要介绍 SpringBoot 中要玩转 Feign 需要掌握的如添加 pom 依赖、客户端注解启用、切换底层 HttpClient、配置数据压缩、调整日志级别、定制配置、配置的优先级机制、增加拦截器以及拦截器的追加机制等知识,以实例 + 效果的方式帮读者高效全面并深入的理解它们。
如果这篇文章对您有帮助,或者有所启发的话,欢迎关注公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。