Contents
  1. 1. 1 什么是Feign
    1. 1.1. 1.1 Feign开源地址
  2. 2. 2 简单整合Feign
    1. 2.1. 2.1 引入feign的maven依赖
    2. 2.2. 2.2 创建主程序并添加@EnableFeignClients注解
    3. 2.3. 2.3 编写Feign接口
    4. 2.4. 2.4 编写Controller
  3. 3. 3 Feign的工作原理
  4. 4. 4 Feign的基础功能
    1. 4.1. 4.2.1 @FeignClient注解介绍
    2. 4.2. 4.2.2 feign开启GZIP压缩
    3. 4.3. 4.2.3 feign client开启日志
      1. 4.3.1. 4.2.3.1 在application.yml中配置日志输出
      2. 4.3.2. 4.2.3.2 通过Java代码进行配置
    4. 4.4. 4.2.4 Feign超时配置
  5. 5. 5 Feign文件上传
    1. 5.1. 5.1 编写文件上传客户端
    2. 5.2. 5.2 编写Feign文件上传服务端

1 什么是Feign

Feign是一个声明式的web service客户端,她的出现使开发web service客户端变得非常简单。她具备可插拨的注解支持,包括Feign注解和JAX-RS注解。使用Feign的时候只需要创建一个interface再加上@FeignClient就可以。Feign支持编码器和解码器,Spring Cloud Open Feign对Feign进行了增强,使其支持了Spring MVC注解,可以像Spring Web一样使用 HttpMessageConverters

Feign是一种声明式、模板化的HTTP客户端,在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程控服务,就像调用本地方法一样,开发者完全感觉不到这是在调用远程方法,更加感觉不到这是在访问HTTP请求。以下列出了一些Feign的特性。

  • 可插播的注解支持,包括Feign注解和JAX-RS注解。
  • 支持可插拨的HTTP编码器和解码器
  • 支持Hystrix以及她的fallback机制
  • 支持ribbon的负载均衡
  • 支持Http请求和响应的压缩

Feign是一个声明式的web service客户端,她出现的目的就是让web service调用变得更加简单。她整合了Ribbon和Hystrix,从而不需开发者再将两者进行整合。Feign还提供了HTTP请求的模板,通过编写简单的注解和接口就可以定义好HTTP请求的参数、格式、地址等信息。Feign会完全代理HTTP请求在使用中我们只需要注入相应的接口,传递好参数就行。

1.1 Feign开源地址

Spring Cloud Open Feign: https://github.com/spring-cloud/spring-cloud-openfeign

Open Feign: https://github.com/OpenFeign/feign

2 简单整合Feign

2.1 引入feign的maven依赖

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>

2.2 创建主程序并添加@EnableFeignClients注解

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
package cn.com.bmsmart;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* <pre>
* <p>Description: 应用程序入口 </p>
* @author wusong
* @date 2018-06-27 11:31
*/
@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@Slf4j
public class FeignApplication {

public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}

}

其中的@EnableFeignClients注解表示当前程序启动时,会进行包扫描,扫描所有标识了@FeignClient注解的类并进行处理。

2.3 编写Feign接口

1
2
3
4
5
6
7
8
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();

@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}

@FeignClient注解中stores对应的值是eureka中任何一个客户端的名称。用于创建Ribbon的负载均衡器。你还可以使用url属性(只能是绝对值或仅主机名)指定URL 。应用程序上下文中bean的名称是接口的名称。要指定自己的别名值,可以使用注释@FeignClientqualifier值。

在上面代码中,Ribbon客户端负责去发现stores服务的物理地址,如果你在项目中使用了Eureka客户端,那么她将解析的是Eureka服务注册表中的服务,如果没有使用Eureka,则需要在配置文件中配置服务器列表 e.g.

1
2
3
stores:
ribbon:
listOfServers: example.com,google.com

2.4 编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequiredArgsConstructor
public class HelloFeignController {

private final StoreClient storeClient;

// 服务消费者对位提供的服务
@PostMapping(value = "/stores/{storeId}")
public Store update(@PathVariable(value ="storeId")String storeId) {
return storeClient.update(storeId);
}

}

以上就完成了Feign整个调用的基本流程。

3 Feign的工作原理

通过上面的整合,我们了解了Feign的基本使用。接着来介绍一下Feign的工作原理:

  • 在开发微服务时,我们会在主类中加入@EnableFeignClients注解开启对FeignClient的扫描加载处理,根据Feign Client的开发规范,定义接口并加@FeignClient注解
  • 当程序启动时,会进行包的扫描。扫描所有用@FeignClient注解进行修饰的类并将这些信息注入Spring IOC容器中,当定义Feign接口中的方法被调用时,通过JDK的代理方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个Request Template对象,该对象奉准过了HTTP请求的全部信息,例如请求参数,请求方法等。
  • 由RequestTemplate生成Request,然后把Request交给Client去处理,这里的client制的是JDK的URLConnection、apache的http client、或者OKhttp。最后client被封装到LoadBalanceClient类中,这个类结合Ribbon负载均衡发起服务之间的调用。

4 Feign的基础功能

4.2.1 @FeignClient注解介绍

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
* Annotation for interfaces declaring that a REST client with that interface should be
* created (e.g. for autowiring into another component). If ribbon is available it will be
* used to load balance the backend requests, and the load balancer can be configured
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
*
* @author Spencer Gibb
* @author Venil Noronha
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";

/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";

/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";

/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";

/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";

/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;

/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] configuration() default {};

/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
*/
Class<?> fallback() default void.class;

/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring
* bean.
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;

/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";

/**
* Whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;

}

value: 指定FeignClient的名称,如果使用了Ribbon,value属性会作为微服务的名称,用于服务发现

qualifier: 使用客户端的别名

url: url用于调试,可以手动指定@FeignClient的调用地址

decode404: 当发生404错误的时候,如果该字段为true, 会调用decoder进行解码,否则抛出FeignException异常

configuration:feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract等

fallback: 定义容错的处理类,当调用远程接口失败,或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口

fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性,我们可以实现每个接口通用的容错逻辑,减少重复的代码

path: 定义当前FeignClient的统一前缀

4.2.2 feign开启GZIP压缩

spring cloud feign支持对请求和响应进行GZIP压缩,以提高通信效率。

1
2
3
4
5
6
7
8
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
min-request-size: 2048 # 配置压缩数据大小的下限
response:
enabled: true # 配置响应GZIP压缩

4.2.3 feign client开启日志

Feign为每个FeignClient都提供了一个feign.Logger实例,可以在配置文件中开启日志,开启方式较为简单,分为两部

4.2.3.1 在application.yml中配置日志输出

1
2
3
logging:
level:
cn.com.bmsmart.StoreClient: debug

4.2.3.2 通过Java代码进行配置

通过在主类中配置

1
2
3
4
 @Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}

通过用@Configuration修饰的类去配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class FeignServiceConfig {

/**
*
* Logger.Level 的具体级别如下:
* NONE:不记录任何信息
* BASIC:仅记录请求方法、URL以及响应状态码和执行时间
* HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息
* FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
* @return
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

4.2.4 Feign超时配置

  • Feign的调用分两层,即Ribbon的调用和Hystrix的调用,高版本的Hystrix默认是关闭的。
1
feign.RetryableException: Read time out execution POST http://.....

出现上面报错信息,说明Ribbon处理超时这时设置ribbon的配置信息如下:

1
2
ribbon.ReadTimeOut: 120000 # 请求处理超时时间
ribbon.ConnectionTimeout: 3000 # 请求连接超时时间

如果开启Hystrix, hystrix超时报错信息如下:

1
com.netflix.hystrix.exception.HystrixRuntimeException: Demo#demo() timedout and no fallback available.

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
feign:
hystrix:
enabled: true
hystrix:
shareSecurityContext: true
command:
default:
circuitBreaker:
sleepWindowInMilliseconds: 10000
forceClosed: true
execution:
isolation:
thread:
timeoutInMilliseconds: 600000

5 Feign文件上传

在我们开发中,文件上传是一个比较常见的功能,Feign早先是不支持文件上传的,后来虽然支持,但是仍然有缺陷。

5.1 编写文件上传客户端

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
/**
* <p>Description: 导入词汇</p>
*
* @param userId 用户id
* @param tradeId 行业id
* @param dicId 词典id
* @param fileName 文件名
* @param file 文件
* @return
*/
@PostMapping(value = "/importdic", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<JsonResult> importDic(@RequestParam(value = "userId") String userId,
@RequestParam(value = "tradeId") String tradeId,
@RequestParam(value = "dicId") String dicId,
@RequestParam(value = "fileName") String fileName,
@RequestPart("file") MultipartFile file
) {
byte[] bytes = new byte[0];
try {
bytes = file.getBytes();
} catch (IOException e) {
e.printStackTrace();
}
HashMap<String, Object> importDic = dicDetailService.importDic(userId, tradeId, dicId, fileName, bytes);
return ResponseMessageUtil.success(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getStatus(), importDic);
}

注意点:

5.2 编写Feign文件上传服务端

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
37
38
39
40
41
42
43
44
45
46
47
48
49
@FeignClient(name = "smartke-txgl", configuration = DicDetailFeignClient.MultipartSupportConfig.class)
public interface DicDetailFeignClient {
/**
* <p>Description: 导入词汇</p>
*
* @param userId 用户id
* @param tradeId 行业id
* @param dicId 词典id
* @param fileName 文件名
* @param file 文件
* @return
*/
@PostMapping(value = DIC_DETAIL_FEIGN_CLIENT_PREFIX + "/importdic", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<JsonResult> importDic(@RequestParam(value = "userId") String userId,
@RequestParam(value = "tradeId") String tradeId,
@RequestParam(value = "dicId") String dicId,
@RequestParam(value = "fileName") String fileName,
@RequestPart("file") MultipartFile file
);

/**
* <p>Description: 通过少量的配置, 让feign阔以支持MultipartFile </p>
* Created by wusong on 2018-08-23 14:15.
*/
@Configuration
public class MultipartSupportConfig {
@Inject
private ObjectFactory<HttpMessageConverters> messageConverters;

@Inject
private HttpServletRequest request;


@Bean
public RequestInterceptor overrideRequestHeader() {
return (template) -> {
template.header("User-Agent", request.getHeader("User-Agent"));
};
}


@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}

}
}

注意上面SpringFormEncoder需要feign-form-spring以及feign-form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.3.0</version>
</dependency>
Contents
  1. 1. 1 什么是Feign
    1. 1.1. 1.1 Feign开源地址
  2. 2. 2 简单整合Feign
    1. 2.1. 2.1 引入feign的maven依赖
    2. 2.2. 2.2 创建主程序并添加@EnableFeignClients注解
    3. 2.3. 2.3 编写Feign接口
    4. 2.4. 2.4 编写Controller
  3. 3. 3 Feign的工作原理
  4. 4. 4 Feign的基础功能
    1. 4.1. 4.2.1 @FeignClient注解介绍
    2. 4.2. 4.2.2 feign开启GZIP压缩
    3. 4.3. 4.2.3 feign client开启日志
      1. 4.3.1. 4.2.3.1 在application.yml中配置日志输出
      2. 4.3.2. 4.2.3.2 通过Java代码进行配置
    4. 4.4. 4.2.4 Feign超时配置
  5. 5. 5 Feign文件上传
    1. 5.1. 5.1 编写文件上传客户端
    2. 5.2. 5.2 编写Feign文件上传服务端