一、Feign 簡介
在上一個用例中,我們使用 Ribbon + RestTemplate 實作服務之間的遠端調用,實際上每一個調用都是模闆化的内容,是以 Spring Cloud Feign 在此基礎上進行了進一步的封裝。我們隻需要定義一個接口并使用 Feign 注解的方式來進行配置,同時采用 springMvc 注解進行參數綁定就可以完成服務的調用。Feign 同時還内置實作了負載均衡、服務容錯等功能。
二、項目結構
- common:公共的接口和實體類;
- consumer:服務的消費者,采用 Feign 調用産品服務;
- producer:服務的提供者;
- eureka:注冊中心。

三、服務提供者的實作
3.1 定義服務
産品服務由
ProductService
提供,并通過
ProducerController
将服務暴露給外部調用:
ProductService.java:
/**
* @author : heibaiying
* @description : 産品提供接口實作類
*/
@Service
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
private static List<Product> productList = new ArrayList<>();
public Product queryProductById(int id) {
return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
}
public List<Product> queryAllProducts() {
return productList;
}
@Override
public void saveProduct(Product product) {
productList.add(product);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
int port = event.getWebServer().getPort();
for (long i = 0; i < 20; i++) {
productList.add(new Product(i, port + "産品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
}
ProducerController.java:
@RestController
public class ProducerController implements ProductFeign {
@Autowired
private IProductService productService;
@GetMapping("products")
public List<Product> productList() {
return productService.queryAllProducts();
}
@GetMapping("product/{id}")
public Product productDetail(@PathVariable int id) {
return productService.queryProductById(id);
}
@PostMapping("product")
public void save(@RequestBody Product product) {
productService.saveProduct(product);
}
}
3.2 服務注冊
指定注冊中心位址,并在啟動類上開啟自動注冊 @EnableDiscoveryClient:
server:
port: 8020
# 指定服務命名
spring:
application:
name: producer
# 指定注冊中心位址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
四、服務消費者的實作
4.1 基本依賴
<!-- feign 依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
4.2 @EnableFeignClients
指定注冊中心位址,并在啟動類上添加注解 @EnableDiscoveryClient 和 @EnableFeignClients,@EnableFeignClients 會去掃描工程中所有用 @FeignClient 聲明的 Feign 用戶端:
server:
port: 8080
# 指定服務命名
spring:
application:
name: consumer
# 指定注冊中心位址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
4.3 建立服務調用接口
/**
* @author : heibaiying
* @description : 聲明式服務調用
*/
public interface ProductFeign {
@GetMapping("products")
List<Product> productList();
/**
* 這是需要強調的是使用 feign 時候@PathVariable 一定要用 value 指明參數,
* 不然會抛出.IllegalStateException: PathVariable annotation was empty on param 異常
*/
@GetMapping("product/{id}")
Product productDetail(@PathVariable(value = "id") int id);
@PostMapping("product")
void save(@RequestBody Product product);
}
按照官方對于服務最佳化的推薦,這裡我們的服務調用接口放在公共子產品中,因為在實際的開發中,同一個服務調用接口可能被多個子產品所使用。
4.4 Feign 用戶端
繼承公共接口,建立 CProductFeign, 用 @FeignClient 聲明為 Feign 用戶端:
/**
* @author : heibaiying
* @description : 聲明式接口調用
*/
@FeignClient(value = "producer",configuration = FeignConfig.class)
public interface CProductFeign extends ProductFeign {
}
4.5 調用遠端服務
注入并使用 Feign 接口調用遠端服務:
@Controller
@RequestMapping("sell")
public class SellController {
@Autowired
private CProductFeign cproductFeign;
@GetMapping("products")
public String productList(Model model) {
List<Product> products = cproductFeign.productList();
model.addAttribute("products", products);
return "products";
}
@GetMapping("product/{id}")
public String productDetail(@PathVariable int id, Model model) {
Product product = cproductFeign.productDetail(id);
model.addAttribute("product", product);
return "product";
}
@PostMapping("product")
public String save(@RequestParam String productName) {
long id = Math.round(Math.random() * 100);
Product product = new Product(id, productName, false, new Date(), 88);
cproductFeign.save(product);
return "redirect:products";
}
}
五、啟動測試
5.1 啟動服務
啟動一個Eureka服務、三個生産者服務(注意區分端口)、和一個消費者服務。Feign 的依賴中導入了 spring-cloud-starter-netflix-ribbon 依賴,并且在内部實作了基于 Ribbon 的用戶端負載均衡,是以我們這裡啟動三個生産者服務來觀察負載均衡的情況:
服務注冊中心:
5.2 驗證負載均衡
通路 http://localhost:8080/sell/products 檢視負載均衡的調用結果:
六、Feign 的服務容錯
6.1 開啟容錯配置
Feign 的依賴中預設導入了 Hystrix (熔斷器)的相關依賴,我們不需要額外導入,隻需要開啟相關配置即可:
在 application.yml 中開啟 Hystrix :
feign:
hystrix:
# 如果為 true,則 OpenFign 用戶端将使用 Hystrix 斷路器進行封裝 預設為 false
enabled: true
6.2 定義降級處理
建立
CProductFeignImpl
,繼承 Feign接口(CProductFeign),定義熔斷時候的降級處理機制:
/**
* @author : heibaiying
* @description : 定義發生熔斷時候的降級處理。除了繼承自 CProductFeign,還需要用 @Component 聲明為 spring 的元件
*/
@Component
public class CProductFeignImpl implements CProductFeign {
// 發生熔斷時候,傳回空集合,前端頁面會做容錯顯示
@Override
public List<Product> productList() {
return new ArrayList<>();
}
@Override
public Product productDetail(int id) {
return null;
}
@Override
public void save(Product product) {
}
}
頁面的簡單容錯處理:
<!doctype html>
<html lang="en">
<head>
<title>産品清單</title>
</head>
<body>
<h3>産品清單:點選檢視詳情</h3>
<form action="/sell/product" method="post">
<input type="text" name="productName">
<input type="submit" value="新增産品">
</form>
<ul>
<#if (products?size>0) >
<#list products as product>
<li>
<a href="/sell/product/${product.id}">${product.name}</a>
</li>
</#list>
<#else>
<h4 style="color: red">目前排隊人數過多,請之後再購買!</h4>
</#if>
</ul>
</body>
</html>
6.3 配置降級處理
在 @FeignClient 注解中,用 fallback 參數指定熔斷時候的降級處理:
/**
* @author : heibaiying
* @description : 聲明式接口調用
*/
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class)
public interface CProductFeign extends ProductFeign {
}
6.4 測試熔斷
Hystrix 預設調用逾時時間為 2s ,這裡我們使用線程休眠的方式來模拟逾時熔斷。
public List<Product> queryAllProducts() {
/*用于測試 hystrix 逾時熔斷
try {
int i = new Random().nextInt(2500);
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return productList;
}
測試結果:
源碼GitHub位址:https://github.com/heibaiying/spring-samples-for-all