目录
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
注解