feign說明
服務之間都是以 HTTP 接口的形式對外提供服務的。消費者在進行調用的時候,底層是通過 HTTP Client 進行訪問。也可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 異步 Http Client,Spring 的 RestTemplate 去實現服務間的調用。但是最方便、最優雅的方式是通過 Spring Cloud Open Feign 進行服務間的調用 Spring Cloud 對 Feign 進行了增強,使 Feign 支持 Spring Mvc 的註解,並整合了 Ribbon 等,從而讓 Feign 的使用更加方便。
Feign 特性:
- 可插拔的註解支持,包括 Feign 註解和AX-RS註解。
- 支持可插拔的 HTTP 編碼器和解碼器。
- 支持 Hystrix 和它的 Fallback。
- 支持 Ribbon 的負載均衡。
- 支持 HTTP 請求和響應的壓縮。
Feign 是一個聲明式的 WebService 客戶端,它的目的就是讓 Web Service 調用更加簡單。它整合了 Ribbon 和 Hystrix,從而不需要開發者針對 Feign 對其進行整合。Feign 還提供了 HTTP 請求的模板,通過編寫簡單的接口和註解,就可以定義好 HTTP 請求的參數、格式、地址等信息。Feign 會完全代理 HTTP 的請求,在使用過程中我們只需要依賴注入 Bean,然後調用對應的方法傳遞參數即可。
Feign 工作原理
-
在開發微服務應用時,我們會在主程序入口添加
@EnableFeignClients
註解開啓對 Feign Client 掃描加載處理。根據 Feign Client 的開發規範,定義接口並加@FeignClient
註解。 -
當程序啓動時,會進行包掃描,掃描所有
@FeignClient
的註解的類,並將這些信息注入 Spring IOC 容器中。當定義的 Feign 接口中的方法被調用時,通過JDK的代理的方式,來生成具體的 RequestTemplate。當生成代理時,Feign 會爲每個接口方法創建一個 RequetTemplate 對象,該對象封裝了 HTTP 請求需要的全部信息,如請求參數名、請求方法等信息都是在這個過程中確定的。 -
然後由 RequestTemplate 生成 Request,然後把 Request 交給 Client 去處理,這裏指的 Client 可以是 JDK 原生的 URLConnection、Apache 的 Http Client 也可以是 Okhttp。最後 Client 被封裝到 LoadBalanceclient 類,這個類結合 Ribbon 負載均衡發起服務之間的調用。
@FeignClient 註解屬性
-
name:指定 Feign Client 的名稱,如果項目使用了 Ribbon,name 屬性會作爲微服務的名稱,用於服務發現。
-
url:url 一般用於調試,可以手動指定
@FeignClient
調用的地址。 -
decode404:當發生404錯誤時,如果該字段爲 true,會調用 decoder 進行解碼,否則拋出 FeignException。
-
configuration:Feign 配置類,可以自定義 Feign 的 Encoder、Decoder、LogLevel、Contract。
-
fallback:定義容錯的處理類,當調用遠程接口失敗或超時時,會調用對應接口的容錯邏輯,fallback 指定的類必須實現
@FeignClient
標記的接口。 -
fallbackFactory:工廠類,用於生成 fallback 類示例,通過這個屬性我們可以實現每個接口通用的容錯邏輯,減少重複的代碼。
-
path:定義當前 FeignClient 的統一前綴。
以上內容來自:Feign 基本使用
示例
示例中eureka註冊中心代碼地址
示例中(客戶端)代碼地址
1.pomxml
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.2.3</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.在啓動類上加@EnableFeignClients
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class CloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(CloudFeignApplication.class, args);
}
}
3.配置文件
feign.hystrix.enabled
爲true纔可以使用容錯
spring.application.name=cloud-feign
server.servlet.context-path=/feign
server.port=8035
# logging 配置
logging.config=classpath:log4j2.xml
#ribbon 飢餓加載
ribbon.eager-load.enabled=true
#指定需要飢餓加載的客戶端名稱、服務名
ribbon.eager-load.clients=cloud-client,cloud-feign
ribbon.ConnectTimeout=90000
ribbon.ReadTimeout=90000
#根據ip註冊實例
eureka.instance.prefer-ip-address=true
#指定註冊實例ID(默認是主機名:應用名:應用端口)
#eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#指定註冊實例主機名
#eureka.instance.hostname=127.0.0.1
#eureka.instance.hostname= ${spring.cloud.client.ip-address}
#註冊地址 eureka服務端的地址 多節點用,分隔
eureka.client.service-url.defaultZone=http://127.0.0.1:8025/eureka/
#打開hystrix
feign.hystrix.enabled=true
4.controller
private FeignService feignService;
@GetMapping("/{id}")
public Object findByPathId(@PathVariable Long id) {
log.info("Path-->id:" + id);
return feignService.findByPathId(id);
}
@GetMapping("findById")
public Object findById(@RequestParam(name = "id") Long id) {
log.info("findById-->id:" + id);
return feignService.findById(id);
}
5.service
@FeignClient(name = "cloud-client/client/api")
註解不捕獲異常,也沒有默認實現
@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
註解不捕獲異常,但是有默認實現,默認實現類是FeignServiceImplDefault
@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
註解即捕獲異常,也有默認實現,還可以捕獲異常信息。對應的類是FeignServiceCauseDefault
//不捕獲異常,沒有默認實現
@FeignClient(name = "cloud-client/client/api")
//不捕獲異常,只有默認實現
//@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
//捕獲異常和默認實現
//@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
public interface FeignService {
@GetMapping("/{id}")
Object findByPathId(@PathVariable(name = "id") Long id);
@GetMapping("findById")
Object findById(@RequestParam(name = "id") Long id);
}
6.FeignServiceCauseDefault
@Service
@Log4j2
public class FeignServiceCauseDefault implements FallbackFactory<FeignService> {
@Override
public FeignService create(Throwable throwable) {
return new FeignService() {
@Override
public Object findById(Long id) {
log.info("sorry, fallback. reason was: " + throwable);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceCauseDefault-->findById-->id::" + id);
return jsonObject;
}
@Override
public Object findByPathId(Long id) {
log.info("sorry, fallback. reason was: " + throwable);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceCauseDefault-->findByPathId-->id:" + id);
return jsonObject;
}
};
}
}
7.FeignServiceImplDefault
@Service
@Log4j2
public class FeignServiceImplDefault implements FeignService {
@Override
public Object findById(Long id) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceImplDefault-->findById-->id:" + id);
return jsonObject;
}
@Override
public Object findByPathId(Long id) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceImplDefault-->findByPathId-->id:" + id);
return jsonObject;
}
}
8.被調用者的Controller(服務端代碼地址)
@RestController
@RequestMapping("api")
@Log4j2
public class ClientController {
@GetMapping("findById")
public Object findById(@RequestParam(name = "id") Long id) {
log.info("findById:"+id);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
return jsonObject;
}
@GetMapping("findById2")
public Object findById2(@RequestParam(name = "id") Long id) {
log.info("findById2:"+id);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id+2);
return jsonObject;
}
@GetMapping("/{id}")
public Object findByPathId(@PathVariable Long id) {
log.info("findByPathId:"+id);
JSONObject jsonObject = new JSONObject();
jsonObject.put("findByPathId", id);
return jsonObject;
}
}
9.先啓動eureka註冊中心再啓動服務端、客戶端
10.在瀏覽器中輸入地址查看效果
輸入http://localhost:8035/feign/findById?id=3
<LinkedHashMap>
<id>3</id>
</LinkedHashMap>
輸入http://localhost:8035/feign/3
<LinkedHashMap>
<findByPathId>3</findByPathId>
</LinkedHashMap>
其中客戶端日誌
INFO findById-->id:3
INFO Path-->id:3
其中服務端日誌
INFO findById:3
INFO findByPathId:3
這說明客戶端成功調用了服務端
11.將服務端關閉,在瀏覽器中輸入地址
出現報錯
12.只打開@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
註解,並在瀏覽器中輸入地址
輸入http://localhost:8035/feign/findById?id=3或者http://localhost:8035/feign/3
<JSONObject>
<id>3</id>
</JSONObject>
其中客戶端日誌
INFO findById-->id:3
INFO FeignServiceImplDefault-->findById-->id:3
這說明客戶端執行了默認實現類FeignServiceImplDefault
的容錯邏輯
13.只打開@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
註解,並在瀏覽器中輸入地址
輸入http://localhost:8035/feign/findById?id=3或者http://localhost:8035/feign/3
<JSONObject>
<id>3</id>
</JSONObject>
其中客戶端日誌
INFO findById-->id:3
INFO sorry, fallback. reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-client
INFO FeignServiceCauseDefault-->findById-->id::3
或者是
INFO Path-->id:3
INFO sorry, fallback. reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-client
INFO FeignServiceCauseDefault-->findByPathId-->id:3
這說明客戶端執行了默認實現類FeignServiceCauseDefault
的容錯邏輯