Feign
一: REST客戶端
在spring cloud集羣中,各個角色的通信是基於REST服務。因此在調用服務時,就不可避免地需要使用REST服務的請求客戶端。 本節介紹另一種REST服務:Feign
REST客戶端和瀏覽器作用有基本相同,但不能理解爲瀏覽器,REST客戶端沒有界面。而瀏覽器需要界面。
1.1 REST客戶端
首先介紹兩種Web Service框架 Apache CXF和Restlet
1.1.1 CXF
是目前一個較爲流行的Web Service框架,是Apache下的一個開源項目
public class MyCxfClient {
public static void main(String[] args) throws IOException {
//創建WebClient
WebClient webClient=WebClient.create("http://localhost:8080/person/1");
//獲取響應
Response response=webClient.get();
//獲取響應內容
InputStream inputStream=(InputStream) response.getEntity();
String contStr=IOUtils.readStringFromStream(inputStream);
//輸出字符串
System.out.println(contStr);
}
1.1.2 Restlet
public class MyRestletClient {
public static void main(String[] args) throws Exception {
ClientResource clientResource = new ClientResource(
"http://localhost:8080/person/1");
// 調用GET方法,服務端發佈的是GET
Representation representation = clientResource
.get(MediaType.APPLICATION_JSON);
// 創建JacksonRepresentation實例,將響應轉化爲Map
JacksonRepresentation jacksonRepresentation = new JacksonRepresentation(
representation, HashMap.class);
// 獲取轉化後的Map對象
Map map = (HashMap) jacksonRepresentation.getObject();
// 輸出結果
System.out.println(map.get("id") + "--" + map.get("name") + "--"
+ map.get("age"));
}
二: Feign框架介紹
- Feign是Github上的開源項目,目的是簡化Web Service客戶端的開發,在使用時,可以使用註解來修飾接口,被修飾的接口具有訪問Web Service的能力。
- *初次之外,Feign還支持插件式的編碼器和解碼器,使用者可以通過該特性對請求和響應進行不同的封裝與解析。
- Spring cloud 將Feign集成到Netflix項目中,當與Eureka、Ribbon集成時,Feign就具備了負載均衡的能力
- 利用的自身的註解@RequestLine(“GET /hello”)
2.1 Get
public interface HelloClient {
/**
* 使用了@RequestLine註解
* 表示用GET方法,向"/hello"發送請求
*/
@RequestLine("GET /hello")
String sayHello();
}
-------------------------------------------------------------------------------------------
public static void main(String[] args) {
// 調用Hello接口
HelloClient helloClient = Feign.builder().target(HelloClient.class,
"http://localhost:8080/");
System.out.println(helloClient.sayHello());
}
2.2 傳入參數
訪問另外一個地址”/person/{personId}”,需要傳入參數並且返回JSON字符串
public interface PersonClient {
@RequestLine("GET /person/{personId}")
Person findPerson(@Param("personId") Integer personId);
class Person {
Integer id;
String name;
Integer age;
//省略setter、getter
}
-------------------------------------
public static void main(String[] args) {
PersonClient personClient = Feign.builder().decoder(new GsonDecoder())
.target(PersonClient.class, "http://localhost:8080/");
//對比可以發現
//1.創建客戶端
//2.調用(甚至無需關心API地址)
Person person=personClient.findPerson(2);
System.out.println(person.id);
System.out.println(person.name);
System.out.println(person.age);
}
2.3 編碼器
向服務發送請求的過程中,有些情況需要對請求的內容進行處理
例如:服務端發佈的服務接收的是JSON格式參數,而客戶端使用的是對象,這種情況下,就可以使用編碼器,將對象轉換成JSON對象
2.3.1 提供服務端(demo01)
@Controller
public class MyController {
/*@GetMapping("/hello")
@ResponseBody
public String hello() {
return "Hello world";
}*/
@PostMapping("/person/create" method=RequestMethod.POST, consums=MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String createPerson(@RequestBody Person person) {
System.out.println(person.getName() + "---" + person.getAge());
return "創建成功:" + person.getId();
}
}
通過createPerson可以知道,需要請求的參數爲JSON類型
2.3.2 客戶端服務接口(demo02)
public interface PersonClient {
@RequestLine("POST /person/create")
@Headers("Content-Type: application/json")
String createPerson(Person person);
@Data
class Person {
Integer id;
String name;
Integer age;
}
}
客戶端服務接口中,使用了@Headers註解,聲明請求的內容類型爲JSON
2.3.3 客戶端運行類(demo02)
public class EncoderMain {
public static void main(String[] args) {
// 獲取服務接口
PersonClient personClient = Feign.builder()
.encoder(new GsonEncoder())
.target(PersonClient.class, "http://localhost:8080/");
// 創建參數的實例
Person person = new Person();
person.id = 1;
person.name = "aitemi";
person.age = 18;
String reponseStr = personClient.createPerson(person);
System.out.println(reponseStr);
}
}
- 在運行類中,在創建服務接口實例時,使用了encoder方法來指定編碼器
- 使用(Feign提供的GsonEncoder類),該類會在發送請求過程中,將請求的對象轉換爲JSON字符串
- Feign支持插件式的編碼器,如果Feign提供的編碼器無法滿足要求,可以使用自定義的編碼器
注:
實際請求的參數類型爲Person類型,而服務端需要請求的爲JSON類型,所以此時需要使用編碼器,以滿足服務端輸入的請求
2.4 解碼器
- 編碼器是對請求的內容進行處理,
- 解碼器則會對服務響應的內容進行處理
例如:解析響應的JSON或者XML字符串,轉化爲我們需要的對象
// 獲取服務接口
PersonClient personClient = Feign.builder()
.decoder(new GsonEncoder())
.target(PersonClient.class, "http://localhost:8080/");
採用decoder(new GsonEncoder()) 將響應的內容解析爲JSON對象
2.5 第三方註解
2.5.1 配置文件
如果想使用JAXRS規範來註解,可以使用Feign的“feign-jaxrs”模塊
<!-- Feign 對 JAXRS 的支持 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>9.5.0</version>
</dependency>
<!-- JAXRS -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
2.5.2 客戶端接口
public interface HelloClient {
/*@RequestLine("GET /hello")
public String sayHello();*/
@GET
@Path("/hello")
public String sayHello();
}
特殊之處:
- 使用 @GET @Path("/hello")註解,代替Feign中的@RequestLine(“POST /person/create”)
2.5.3 客戶端測試
public class HelloClient {
public static void main(String[] args) {
//設置了 JAXRSContract 類,Feign 就知道如何處理 JAXRS 的相關注解
HelloClient helloClient = Feign.builder().contract(new JAXRSContract())
.target(HelloClient.class, "http://localhost:8080/");
String result = helloClient.sayHello();
System.out.println("接口響應內容:" + result);
}
}
2.6 請求攔截器
- 在發送請求的時候需要設置公共信息(權限、請求類型等)
自定義攔截器
public class MyInterceptor implements RequestInterceptor {
public void apply(RequestTemplate template) {
//設置請求頭信息(這樣就可以不需要在每一個方法中都設置了)
//template.header("Content-Type", "application/json");
System.out.println(" --->>> 這是自定義攔截器 <<<--- ");
}
}
請求設置攔截器(Feign)
public class InterceptorMain {
public static void main(String[] args) {
HelloClient helloClient = Feign.builder()
.requestInterceptor(new MyInterceptor())
.target(HelloClient.class, "http://localhost:8080/");
String result=helloClient.sayHello();
System.out.println(result);
}
}
2.7 接口日誌
- 默認情況下,接口不會記錄接口日誌
- 如果想要了解接口的調用情況,可以配置接口日誌
測試代碼:
public class LogMain {
public static void main(String[] args) {
/**
* NONE: 默認值,不記錄日誌
* BASIC: 記錄請求方法、URL、響應狀態代碼和執行時間
* HEADERS: 除BASIC記錄日誌外,還會記錄請求頭和響應頭的信息
* FULL: 在HEADERS的基礎上,請求和響應的元數據,都會保存
*/
HelloClient helloClient = Feign.builder().logLevel(Logger.Level.FULL)
.logger(new Logger.JavaLogger().appendToFile("logs/main.log"))
.target(HelloClient.class, "http://localhost:8080/");
String result = helloClient.sayHello();
System.out.println(result);
}
}
以上日誌輸出到文件中,同時需要創建日誌文件