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