Spring Cloud Feign(宣告式服務呼叫)(2)
繼承特性
1.首先新建個工程,名為hello-service-api。因為要用到Spring MVC的註解所以要要加鎖spring-boot-web的依賴,具體如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>hello-service-api</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.因為在服務提供者和服務消費者中需要用到User這個類,所以將其提取出來放置到新建的工程中。
3.建立HelloService介面:
@RequestMapping("/refactor") public interface HelloService { @RequestMapping("/hello") public String hello(); @RequestMapping(value = "/hello1", method = RequestMethod.GET) String hello(@RequestParam("name") String name); @RequestMapping(value = "/hello2", method = RequestMethod.GET) Map<String,Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price); @RequestMapping(value = "/hello3", method = RequestMethod.POST) String hello(@RequestBody Map<String, Object> book); @RequestMapping(value = "/hello4", method = RequestMethod.POST) String hello(@RequestBody User user); }
最後的目錄結構:
然後需要對hello-service(服務提供者)和feign-consumer(服務呼叫)進行重構:
4.首先需要在各自的pom.xml檔案中引入對hello-service-api的依賴:
<dependency> <groupId>com.example</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
5.重寫服務提供者的控制器,改為:
@RestController
@RequestMapping("/refactor")
public class HelloController {
private final Logger logger=Logger.getLogger(getClass());
@RequestMapping("/hello")
public String index() throws InterruptedException {
// int sleepTime=new Random().nextInt(3000);
// logger.info("sleep:"+sleepTime);
// Thread.sleep(sleepTime);
logger.info(new Date());
return "Hello"+new Date()+"---"+new Random().nextInt();
}
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
public String hello1(@RequestParam String name) {
return "hello " + name + "!";
}
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
public Map<String,Object> hello2(@RequestHeader String name, @RequestHeader String author, @RequestHeader Integer price) throws UnsupportedEncodingException {
Map<String,Object> map = new HashMap<String,Object>();
map.put("name",URLDecoder.decode(name));
map.put("author",URLDecoder.decode(author));
return map;
}
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
public String hello3(@RequestBody Map<String,Object> book) {
return "書名為:" + book.get("name") + ";作者為:" + book.get("author");
}
@RequestMapping(value = "/hello4", method = RequestMethod.POST)
public String hello4(@RequestBody User user) {
return "使用者名稱:" + user.getName() + ";年齡為:" + user.getAge();
}
}
需要注意的是在原先的控制器上新增了@RequestMapping("/refactor"),要跟hello-service-api上的RequestMapping相同,這裡也可以用實現(implements)的方式實現。
6.建立HelloServiceImpl實現HelloService介面並新增@FeignClient註解繫結服務。
@FeignClient("hello-service")//用於繫結名為hello-service服務
public interface HelloServiceImpl extends HelloService {
}
7.修改其控制器如:
@RestController
public class ConsumerController {
// @Autowired
// HelloService helloService;
@Autowired
HelloServiceImpl helloService;
@GetMapping("/feign-consumer")
public String helloConsumer() {
return helloService.hello();
}
@RequestMapping("/hello1")
public String hello1() {
return helloService.hello("張三");
}
@RequestMapping(value = "/hello2")
public Map<String, Object> hello2() throws UnsupportedEncodingException {
Map<String, Object> book = helloService.hello(URLEncoder.encode("三國演義", "UTF-8"), URLEncoder.encode("羅貫中", "UTF-8"), 33);
System.out.println(book);
return book;
}
@RequestMapping("/hello3")
public String hello3() {
Map<String, Object> book = new HashMap<String, Object>();
book.put("name", "三國演義");
book.put("author", "羅貫中");
return helloService.hello(book);
}
@RequestMapping("/hello4")
public String hello4() {
User user = new User("劉德華", 99);
return helloService.hello(user);
}
}
OK這就是Feign的繼承特性
Ribbon配置
由於Spring Cloud Feign的客戶端負載均衡是通過Spring Cloud Ribbon實現,所以我們可以直接通過配置Ribbon客戶端方式來自定義各個服務客戶端呼叫的引數。
全域性配置
全域性配置的方式十分容易,如同Ribbon的配置一樣可以直接使用ribbon.<key>=<value>的方式來設定ribbon的各項預設引數。如修改客戶端呼叫超時時間:
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
指定服務設定
在使用Spring Cloud Feign的時候,針對各個服務客戶端進行個性化配置的方式與使用Spring Cloud Ribbon時的配置方式是一樣的,都採用<client>.ribbon.key=value的格式進行設定。
在定義Feign客戶端時,我們使用了@FeignClient註解。在初始化過程中,Spring Cloud Feign會根據該註解的name屬性或value屬性指定的服務名,自動建立一個同名的Ribbon客戶端。在上面的程式碼中使用的@FeignClient("hello-service")這時的application.properties的配置如:
# 設定針對hello-service服務的連線超時時間
hello-service.ribbon.ConnectTimeout=600
# 設定針對hello-service服務的讀取超時時間
hello-service.ribbon.ReadTimeout=6000
# 設定針對hello-service服務所有操作請求都進行重試
hello-service.ribbon.OkToRetryOnAllOperations=true
# 設定針對hello-service服務切換例項的重試次數
hello-service.ribbon.MaxAutoRetriesNextServer=2
# 設定針對hello-service服務的當前例項的重試次數
hello-service.ribbon.MaxAutoRetries=1
Hystrix配置
在Spring Cloud Feign中除了引入了客戶端負載均衡的Spring Cloud Ribbon之外,還引入了Hystrix。預設情況下Spring Cloud Feign會為所有Feign客戶端方法都封裝到Hystrix命令中進行服務保護。
全域性配置
對於Hystrix的全域性配置同Spring Cloud Ribbon的全域性配置一樣,直接使用它的預設配置字首hystrix.command.default進行設定,如設定全域性超時時間:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
另外對Hystrix進行配置之前,我們需要確認feign.hystrix.enabled引數沒有被設定為false,否則該引數設定會關閉Feign客戶端的Hystrix支援。feign.hystrix.enabled=false可以關閉Hystrix功能,hystrix.command.default.execution.timeout.enabled=false關閉熔斷功能。
禁用Hystrix
除了feign.hystrix.enabled=false可以關閉Hystrix功能,如果想針對某個服務客戶端關閉Hystrix支援可以使用@Scope("prototype")註解為指定的客戶端配置Feign.Builder例項。
@Configuration
public class DisableHystrixConfiguration{
@Bean
@Scope("prototype")
public Builder feignBuilder(){
return Feign.builder();
}
}
在HelloService中的@FeignClient註解中,通過configuration引數引入上面實現的配置。
@FeignClient(name="hello-service",configuration=DisableHystrixConfiguration.class)
指定的命令配置
指定命令配置方法也跟傳統的Hystrix 命令的引數配置相似,採用hystrix.command.<commandkey>作為字首,而預設情況下會採用Feign客戶端中的方法名作為標識,比如, 針對hello介面的熔斷超時時間的配置可以通過其方法名作為來進行配置, 具體如下:
hystrix.command.hello.execution.isolation.thread.timeoutinMilliseconds=5OOO
在使用指定命令配置的時候, 需要注意, 由於方法名很有可能重複, 這個時候相同方法名的Hystrix配置會共用,所以在進行方法定義與配置的時候需要做好一定的規劃。當然,也可以重寫Feign.Builder的實現,並在應用主類中建立它的例項來覆蓋自動化配置的HystrixFeign.Builder實現
服務降級配置
由於Spring Cloud Feign在定義服務客戶端的時候與Spring Cloud Ribbon有很大差別,HystrixCommand定義被封裝了起來, 我們無法像之前介紹Spring Cloud Hystrix時, 通過@HystrixCommand註解的fallback引數那樣來指定具體的服務降級處理方法。但是, Spring Cloud Feign提供了另外一種簡單的定義方式,如下:
1.繼承HelloService類,實現服務降級邏輯:
@Component
@RequestMapping("fallback/refactor")//必須要加,不然會報bean建立異常
public class HelloServiceFallbackImpl implements HelloService {
@Override
public String hello() {
return "error";
}
@Override
public String hello(@RequestParam("name") String name) {
return "error1";
}
@Override
public Map<String, Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("error", "未知");
return map;
}
@Override
public String hello(@RequestBody Map<String, Object> book) {
return "error3";
}
@Override
public String hello(@RequestBody User user) {
return "error4";
}
}
2.通過FeignClient註解的fallback屬性來指定對應的服務降級實現類
@RequestMapping("/refactor")
@FeignClient(name = "hello-service",fallback = HelloServiceFallbackImpl.class)//用於繫結名為hello-service服務 ,fallback = HelloServiceFallbackImpl.class
public interface HelloService {
@RequestMapping("/hello")
public String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello(@RequestParam("name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET,produces = {"application/json;charset-UTF-8"})
Map<String,Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello(@RequestBody Map<String,Object> book);
@RequestMapping(value = "/hello4", method = RequestMethod.POST)
String hello(@RequestBody User user);
}
3.修改application.properties開啟服務降級功能
spring.application.name=feign-consumer
server.port=9001
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
feign.hystrix.enabled=true
並且修改Hello-service使其超時
啟動服務後訪問:
其他配置
請求壓縮
Spring Cloud Feign支援對請求與響應的GZIP壓縮,以減少通訊過程中的效能損耗,只需要設定下面兩個引數即可:
# 配置請求GZIP壓縮
feign.compression.request.enabled=true
# 配置響應GZIP壓縮
feign.compression.response.enabled=true
並且能對請求壓縮做更細緻的設定,如:
# 配置壓縮支援的MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置壓縮資料大小的下限
feign.compression.request.min-request-size=2048
日誌配置
Spring Cloud Feign在構建被@FeignClient註解修飾的客戶端服務時,會為每一個客戶端都建立一個feign.Logger例項。開啟方式如下:
logging.level.com.example.feign.consumer.service.HelloService=DEBUG
但是由於Feign客戶端預設的Logger.Level物件定義為NONE級別,該級別不會記錄任何Feign呼叫過程中的資訊。所以要在應用主類中加入Logger.Level的Bean建立:
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class FeignConsumerApplication {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
Logger級別主要有4類:
NONE:不記錄任何資訊
BASIC:僅記錄請求方法,URL以及響應嗎和執行時間。
HEADERS:除了記錄BASIC級別的資訊外,還會記錄請求和響應的頭資訊。
FULL:記錄所有請求與響應的明細,包括頭資訊,請求體,元資料等。
參考:《Spring Cloud 微服務實戰》