目录
0 环境
系统环境:win10
编辑器:idea
springcloud版本:H版
1 前言
之前使用的eureka/hystrix 都是调用RestTemplate(繁琐 重复高) OpenFeign对请求进行简化。Feign停更了 OpenFeign是在Feign基础上开发出来的
- 常用的几种接口调用方法
- Httpclient 易用 灵活
- Okhttp 处理网络请求 轻量级 支持多协议。。
- HttpURLConnection 使用复杂
- RestTemplate Rest服务的客户端 提供多种便携访问HTTP服务的方法
2 尝鲜
2.1 创建springboot项目

2.2 yml配置
spring:
application:
name: openfeign
eureka:
client:
service-url:
defaultZone: http://localhost:1234/eureka
server:
port: 5000
2.3 启动类配置
@EnableFeignClients --> 开启Feign
2.4 接口配置
使用的是之前的eureka server和provider以及如今使用的openfeign
// openfeign service
// 对比之前xxx.getForObject("http://provider/hello1", String.class)
// 现在只需要抽取provider hello1 拼接不需要我们操心了
@FeignClient("provider")
public interface HelloService {
@GetMapping("/hello")
// 方法名无所谓 无参调用
String hello();
}
2.5 接口调用
@RestController
public class HelloController {
@Autowired
HelloService helloService;
// 无参测试
@GetMapping("/hello")
public String hello(){
return helloService.hello();
}
}
2.6 测试结果
开启eureka server provider openfeign

3 参数传递
3.1 导入依赖模块
因为要用到类 之前新建一个模块 现在该opfeign需要引入依赖 可以在opfeign中定义类(随意
<dependency>
<groupId>xxx</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3.2 接口配置
// eureka provider
@RestController
public class HelloController {
@GetMapping("/hello1")
public String hello1(String name){
return "hello provider: " + name;
}
@PostMapping("/user1")
public User addUser1(@RequestBody User user){
return user;
}
@DeleteMapping("/user1/{id}")
public void delUser1(@PathVariable Integer id){
System.out.println("json形式:" + id);
}
@GetMapping("/user2")
public void getUserByName(@RequestHeader String name) throws UnsupportedEncodingException {
// 解码
System.out.println(URLDecoder.decode(name, "utf-8"));
}
}
// openfeign service配置
@FeignClient("provider")
public interface HelloService {
// 参数传递一定要绑定参数名
@GetMapping("/hello1")
String hello1(@RequestParam("name") String name);
// json
// 注:key/value形式的参数 一定要标记参数的名称
@PostMapping("/user1")
User user1(@RequestBody User user);
// 删除id
// /user1/{id}
@DeleteMapping("/user1/{id}")
void delUser1(@PathVariable Integer id);
// 通过header来传参 中文要转码
@GetMapping("/user2")
void getUserByName(@RequestHeader String name);
}
3.3 接口调用
// openfeign controller 传参
@GetMapping("/hello1")
public void hello1() throws UnsupportedEncodingException {
String s = helloService.hello1("你好呀");
System.out.println("hello1:" + s);
System.out.println("---------------------------------------");
User user = new User();
user.setId(1);
user.setName("小个");
user.setNickName("萨达过");
User user1 = helloService.user1(user);
System.out.println("user:" + user1);
System.out.println("---------------------------------------");
helloService.delUser1(1);
System.out.println("---------------------------------------");
// 放在heard中的中文参数 一定要先编码在传递
helloService.getUserByName(URLEncoder.encode("方便热土", "utf-8"));
}
3.4 测试结果
开启eureka server provider openfeign

3.5 小结
- 参数传递
- 参数一定要绑定参数名
- 若通过header来传递参数 中文需转码 编码后在传递(URLEncoder.encode(xxx, "utf-8"))
.provider解码(URLDecoder.decode(xxx, "utf-8"))
4 继承特性
4.1 新建maven子模块
这个包被其他模块依赖 需要springmvc依赖
<dependencies>
<!-- 涉及到springmvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- 存储类模块 -->
<dependency>
<groupId>xxx</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
4.2 封装接口
public interface IUserService {
@GetMapping("/hello")
String hello();
// 参数传递一定要绑定参数名 若是参数最好用@RequestParam
// 最好别用map 其问题是可以随意传参
@GetMapping("/hello1")
String hello1(@RequestParam("name") String name);
// json
// 注:key/value形式的参数 一定要标记参数的名称
@PostMapping("/user1")
User addUser1(@RequestBody User user);
// 删除id
// /user1/{id}
// 添加@PathVariable("id") 注意了一定要把("id")添加进去 不然会报错
@DeleteMapping("/user1/{id}")
void delUser1(@PathVariable("id") Integer id);
// 通过header来传参 中文要转码
// 添加@RequestHeader("name") 注意了一定要把("name")添加进去 不然会报RequestHeader0参数错
@GetMapping("/user2")
void getUserByName(@RequestHeader("name") String name) throws UnsupportedEncodingException;
}
4.3 消费者和openfeign添加依赖
<dependency>
<groupId>com.sundown</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.sundown</groupId>
<artifactId>hi-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4.4 提供者实现接口
@RestController | |
//@RequestMapping("/test") | |
public class Hello1Controller implements IUserService { | |
@Value("${server.port}") | |
Integer port; | |
// 重写 | |
@Override | |
public String hello(){ | |
return "hello provider:" + port; | |
} | |
/** | |
* @Description: consumer访问该接口 调用RestTemplate的get请求 | |
* @Param: [name] | |
* @return: java.lang.String | |
* @Author: 水面行走 | |
* @Date: 2020/3/4 | |
*/ | |
@Override | |
public String hello1(String name){ | |
return "hello provider: " + name; | |
} | |
@GetMapping("/hello2") | |
public String hello2(String name){ | |
System.out.println(new Date() + "--->" + name); | |
return "hello " + name; | |
} | |
// 在provider 提供2个post接口 | |
/** | |
* @Description: key:value形式传参 | |
* @Param: [user] | |
* @return: model.User | |
* @Author: 水面行走 | |
* @Date: 2020/3/7 | |
*/ | |
@PostMapping("/user") | |
public User addUser(User user){ | |
return user; | |
} | |
/** | |
* @Description: json形式传参 | |
* @Param: [user] | |
* @return: model.User | |
* @Author: 水面行走 | |
* @Date: 2020/3/7 | |
*/ | |
@Override | |
public User addUser1(@RequestBody User user){ | |
return user; | |
} | |
/** | |
* @Description: k/v形式 因为是更新操作 put方法返回为void 所以返回值为void就行 有返回值不会报错 | |
* @Param: | |
* @return: | |
* @Author: 水面行走 | |
* @Date: 2020/xx/xx | |
*/ | |
@PutMapping("/update-user") | |
public void updateUser(User user){ | |
System.out.println("k/v形式:" + user); | |
} | |
/** | |
* @Description: json形式 别忘了传参添加注解 因为是更新操作 put方法返回为void 所以返回值为void就行 有返回值不会报错 | |
* @Param: | |
* @return: | |
* @Author: 水面行走 | |
* @Date: 2020/xx/xx | |
*/ | |
@PutMapping("/update-user1") | |
public void updateUser1(@RequestBody User user){ | |
System.out.println("json形式:" + user); | |
} | |
/** | |
* @Description: k/v形式的删除 xxx?id=1 | |
* @Param: | |
* @return: | |
* @Author: 水面行走 | |
* @Date: 2020/3/8 | |
*/ | |
@DeleteMapping("/deluser") | |
public void delUser(Integer id){ | |
System.out.println("k/v形式:" + id); | |
} | |
/** | |
* @Description: PathVariable(参数放在路径中 xxx/1)形式的删除 | |
* @Param: | |
* @return: | |
* @Author: 水面行走 | |
* @Date: 2020/3/8 | |
*/ | |
@Override | |
public void delUser1(@PathVariable Integer id){ | |
System.out.println("json形式:" + id); | |
} | |
@Override | |
public void getUserByName(@RequestHeader String name) throws UnsupportedEncodingException { | |
// 解码 | |
System.out.println(URLDecoder.decode(name, "utf-8")); | |
} | |
} |
4.5 openfeign配置
// 继承接口 | |
// 继承特性的好处:抽出公共模块 provider和consumer代码一致 只需更改公共接口即可 减少出错率 | |
// 坏处就是耦合度变高 | |
@FeignClient("provider") | |
public interface Hello1Service extends IUserService { | |
} |
// 调用Hello1Service | |
// 其他这个类没有变化 调用结果和上次是一致的 | |
@RestController | |
public class Hello1Controller { | |
@Autowired | |
Hello1Service hello1Service; | |
// 无参测试 | |
@GetMapping("/hello") | |
public String hello(){ | |
return hello1Service.hello(); | |
} | |
// 传参 | |
@GetMapping("/hello1") | |
public void hello1() throws UnsupportedEncodingException { | |
String s = hello1Service.hello1("你好呀"); | |
System.out.println("hello1:" + s); | |
System.out.println("---------------------------------------"); | |
User user = new User(); | |
user.setId(1); | |
user.setName("小个"); | |
user.setNickName("萨达过"); | |
User user1 = hello1Service.addUser1(user); | |
System.out.println("user:" + user1); | |
System.out.println("---------------------------------------"); | |
hello1Service.delUser1(1); | |
System.out.println("---------------------------------------"); | |
// 放在heard中的中文参数 一定要先编码在传递 | |
hello1Service.getUserByName(URLEncoder.encode("个百分点", "utf-8")); | |
} | |
} |
4.6 小结
- 继承特性
- 代码简洁 服务者和消费者指向同一目标 一改都改 出错烦恼大减(既是优点也是缺点(耦合度高) 类似赤壁之战 曹操的战船相连)
- 无论是否继承 参数(无参还是传参方式依然不变)
5 数据压缩
开启压缩 节省资源 提升性能
feign: | |
compression: | |
request: | |
# 开启数据压缩请求 | |
enabled: true | |
# 压缩数据类型 | |
mime-types: text/xml, application/xml, application/json | |
# 数据压缩下限 2048表示传输数据大于2048 才会进行数据压缩(最小压缩值标准) | |
min-request-size: 2048 | |
# 开启数据压缩响应 | |
response: | |
enabled: true |

6 日志配置
- 配置日志 分4种
- NONE: 不开启日志(默认)
- BASIC: 记录请求方法、URL、响应状态、执行时间
- HEADERS: 在BASIC基础上 加载请求/响应头(+2)
- FULL: 在HEADERS基础上 增加body和请求元数据(+3)
6.1 在yml中配置日志级别
# 可以在yml feign.client.config.xxx 配置超时时间 拦截器等配置 | |
logging: | |
level: | |
com.sundown.openfeign: debug |
6.2 两种配置日志bean方式
- 在applicaton类中配置bean
import feign.Logger; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.openfeign.EnableFeignClients; | |
import org.springframework.context.annotation.Bean; | |
@SpringBootApplication | |
@EnableFeignClients | |
public class OpenfeignApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(OpenfeignApplication.class, args); | |
} | |
// 配置日志 分4种 | |
// 1. NONE: 不开启日志(默认) | |
// 2. BASIC: 记录请求方法、URL、响应状态、执行时间 | |
// 3. HEADERS: 在BASIC基础上 加载请求/响应头(+2) | |
// 4. FULL: 在HEADERS基础上 增加body和请求元数据(+3) | |
// 通过bean配置 | |
@Bean | |
Logger.Level loggerLevel(){ | |
return Logger.Level.FULL; | |
} | |
} |
- Configuration中配置
正好将超时和自定义拦截器加入
@Configuration | |
public class FeignConfig { | |
// 超时时间配置(通过Options可配置连接超时时间和读取超时时间) | |
// Options第一个参数连接超时时间(ms 默认1000*10) | |
// Options第二个参数取超时时间(ms 默认1000*60) | |
@Bean | |
public Request.Options options(){ | |
return new Request.Options(3000, 8000); | |
} | |
/** | |
* 日志级别 | |
* 在这里配置日志级别 | |
* @return | |
*/ | |
@Bean | |
Logger.Level feignLoggerLevel() { | |
return Logger.Level.FULL; | |
} | |
// 配置basic认证 | |
// 通常: 调用有权限控制的接口 可能认证的值通过传参/请求头去传认证信息 | |
@Bean | |
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { | |
return new BasicAuthRequestInterceptor("user", "password"); | |
} | |
// 自定义拦截器配置 | |
@Bean | |
public FeignBasicAuthRequestInterceptor feignBasicAuthRequestInterceptor() { | |
return new FeignBasicAuthRequestInterceptor(); | |
} | |
} |
// OpenFeign自定义拦截器(自定义认证方式) 实现RequestInterceptor | |
// 自定义一个请求拦截器 在请求之前做认证操作 在往请求头中配置认证后的信息 | |
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor { | |
// 业务逻辑 | |
@Override | |
public void apply(RequestTemplate requestTemplate) { | |
System.err.println("欢迎进入拦截器: " + requestTemplate); | |
} | |
} |
// 在openfeign service层配置 | |
//@FeignClient(value = "provider",configuration= FeignConfig.class) 配置类-->拦截器啥的 | |
@FeignClient(value = "provider",configuration= FeignConfig.class) | |
public interface Hello1Service extends IUserService { | |
} |
启动eureka server和provider 还有openfeign
http://localhost:5000/hello1


7 openfeign+hystrix配合使用
降级@FeignClient+value+fallback/fallbackFactory属性(都是在openfeign模块中实现 且要开启hystrix)
hystrix: | |
# 开启hystrix | |
enabled: true |
- 降级fallback属性
// fallback属性实现类 | |
@Component | |
@RequestMapping("/milk") // implements Hello1Service相当于调用了2次 避免重复的请求地址 不然会报错 | |
public class HelloServiceFallback implements Hello1Service{ | |
@Override | |
public String hello() { | |
return "error-hello"; | |
} | |
@Override | |
public String hello1(String name) { | |
return "error-hello1"; | |
} | |
@Override | |
public User addUser1(User user) { | |
return null; | |
} | |
@Override | |
public void delUser1(Integer id) { | |
} | |
@Override | |
public void getUserByName(String name) throws UnsupportedEncodingException { | |
} | |
} |
@FeignClient(value = "provider",fallback= HelloServiceFallback.class) | |
public interface Hello1Service extends IUserService { | |
} |
启动/重启eureka server和openfeign 断开provider
http://localhost:5000/hello
和http://localhost:5000/hello1


- 降级fallbackFactory属性
@Component | |
public class HelloServiceFallFactory implements FallbackFactory<Hello1Service> { | |
@Override | |
public Hello1Service create(Throwable throwable) { | |
return new Hello1Service() { | |
@Override | |
public String hello() { | |
return "error1---------"; | |
} | |
@Override | |
public String hello1(String name) { | |
return "error2---------"; | |
} | |
@Override | |
public User addUser1(User user) { | |
return null; | |
} | |
@Override | |
public void delUser1(Integer id) { | |
} | |
@Override | |
public void getUserByName(String name) throws UnsupportedEncodingException { | |
} | |
}; | |
} | |
} | |
//@FeignClient(value = "provider",fallback= HelloServiceFallback.class) fallback和fallbackFactory不能同时使用 | |
@FeignClient(value = "provider",fallbackFactory= HelloServiceFallFactory.class) | |
public interface Hello1Service extends IUserService { | |
} |
启动/重启eureka server和openfeign 断开provider
http://localhost:5000/hello
和http://localhost:5000/hello1


8 小结
- openfeign只需要我们提供关键的value就行了 自行拼接
- openfeign环境: 依赖eureka连接依赖 web openfeign
- yml eureka连接配置
- 注解开启openfeign
- 无参 无需参数直接调用即可
- 有参 --> 绑定参数、header传递 中文要解码、多参数的话 建议@RequestParam("xxx")
- 特性继承 --> provider和openfeign公用一个接口 特别注意(一定要定义名字@RequestHeader("xxx") @PathVariable("xxx")。。。不然会报错) 它的好处既是坏处
- 数据压缩 yml配置 开启压缩请求和响应 最小压缩值标准还有压缩类型
- 日志配置 四种级别NONE BASIC HEADERS FULL
- yml -->
logging: level: com.sundown.openfeign: debug
- 在bean配置 要么在application中或是在config中配置好(还可以超时配置和认证配置或自定义拦截器) 在@FeignClient中配置configuration属性 例如
@FeignClient(value = "provider",configuration= FeignConfig.class)
- 另外 也可在yml feign.client.config.xxx 配置超时时间 拦截器等
- openfeign+hystrix降级操作 在yml中开启hystrix 在@FeignClient中实现
fallbackFactory属性(需要implements FallbackFactory<T>
)或fallback(实现接口implements Hello1Service添加@RequestMapping("/xx")作为区分)
2种实现方式都要添加@Component
注解