覆蓋Feign的默認(rèn)配置
A central concept in Spring Cloud’s Feign support is that of the named client. Each feign client is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer using the @FeignClient annotation. Spring Cloud creates a new ensemble as an ApplicationContext on demand for each named client using FeignClientsConfiguration. This contains (amongst other things) an feign.Decoder, a feign.Encoder, and a feign.Contract.
Spring Cloud的Feign
支持的一個中心概念就是命名客戶端墨技。 每個Feign
客戶端都是組合的組件的一部分放椰,它們一起工作以按需調(diào)用遠(yuǎn)程服務(wù)器缔刹,并且該集合具有您將其作為使用@FeignClient
注釋的參數(shù)名稱。 Spring Cloud
使用FeignClientsConfiguration
創(chuàng)建一個新的集合作為每個命名客戶端的ApplicationContext
(應(yīng)用上下文)杀迹。 這包含(除其他外)feign.Decoder
亡脸,feign.Encoder
和feign.Contract
。
你可以自定義FeignClientsConfiguration
以完全控制這一系列的配置树酪。比如我們下面的demo:
定義一個order服務(wù)梗掰,并加入依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
定義主體啟動類:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
定義Controller:
@RestController
@RequestMapping("/order")
public class OrderController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
UserService userService;
@RequestMapping("/index")
public String index(){
logger.info("index方法");
return userService.index();
}
}
定義Feign客戶端接口:
@FeignClient(value = "user-service",configuration = FooConfiguration.class)
public interface UserService {
@RequestLine("GET /user/index")
String index();
}
使用了配置@Configuration
參數(shù),自己定義了FooConfiguration
類來自定義FeignClientsConfiguration
嗅回,并且FeignClientsConfiguration
類的類路徑不在啟動類OrderApplication的掃描路徑下及穗,是因為如果在掃描目錄下會覆蓋該項目所有的Feign接口的默認(rèn)配置。
FooConfiguration定義:
package com.zhihao.miao.config;
import feign.Contract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FooConfiguration {
//使用Feign自己的注解绵载,使用springmvc的注解就會報錯
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
}
因為配置FooConfiguration
定義的是new feign.Contract.Default()
埂陆,所有在UserService
接口中只能使用Feign
自己的注解url方式。
配置文件:
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://zhihao.miao:123456@localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
server:
port: 9090
訪問http://192.168.5.3:9090/order/index
娃豹,正常訪問到結(jié)果焚虱。
再定義一個FeignClient接口,使用SpringMvc注解的方式來訪問
@FeignClient(value = "eureka-service",url = "http://localhost:8761/",configuration = EurekaConfiguration.class)
public interface EurekaService {
@RequestMapping(value = "/eureka/apps/{serviceName}")
String findServiceInfoFromEurekaByServiceName(@PathVariable("serviceName") String serviceName);
}
因為Eureka
配置了用戶名和密碼懂版,所有這個FeignClient
也自己定義了FeignClientsConfiguration
鹃栽,也可以用來訪問Eureka
服務(wù)接口。
package com.zhihao.miao.config;
import feign.auth.BasicAuthRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EurekaConfiguration {
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
}
}
通過訪問http://192.168.5.3:9090/order/findServiceInfoFromEurekaByServiceName/user-service
也能訪問成功躯畴,通過這個列子我們知道可以為每個Feign客戶端都配置了自己的默認(rèn)配置民鼓。
注意
-
@FeignClient
注解的serviceId
參數(shù)不建議被使用薇芝。 - 以前使用
@FeignClient
注解的時候使用url
參數(shù)的使用就不需要使用name
屬性了,現(xiàn)在不然丰嘉,需要在url
屬性的基礎(chǔ)上也要使用name
屬性夯到,此時的name屬性只是一個標(biāo)識。
參考資料
springcloud 官網(wǎng)
feign的github地址
參數(shù)綁定
在快速入門中饮亏,我們使用了spring cloud feign
實現(xiàn)的是一個不帶參數(shù)的REST服務(wù)綁定∷<郑現(xiàn)實中的各種業(yè)務(wù)接口要比它復(fù)雜的多,我們會在http的各個位置傳入各種不同類型的參數(shù)路幸,并且在返回請求響應(yīng)的時候也可能是一個復(fù)雜的對象結(jié)構(gòu)荐开。
擴展一下user-servcice
服務(wù),增加一些接口定義简肴,其中包含Request參數(shù)的請求晃听,帶有Header信息的請求,帶有RequestBody
的請求以及請求響應(yīng)體中是一個對象的請求,擴展了三個接口分別是hello着帽,hello2,hello3
@RestController
@RequestMapping("/user")
public class UserController {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value="/index",method = RequestMethod.GET)
public String index(){
ServiceInstance instance = client.getLocalServiceInstance();
logger.info("/user,host:"+instance.getHost()+",service id:"+instance.getServiceId()+",port:"+instance.getPort());
return "user index, local time="+ LocalDateTime.now();
}
@GetMapping("/hello")
public String userHello() throws Exception{
ServiceInstance serviceInstance = client.getLocalServiceInstance();
//線程阻塞
int sleeptime = new Random().nextInt(3000);
logger.info("sleeptime:"+sleeptime);
Thread.sleep(sleeptime);
logger.info("/user,host:"+serviceInstance.getHost()+",service id:"+serviceInstance.getServiceId()+",port:"+serviceInstance.getPort());
return "user hello";
}
@RequestMapping(value = "/hello1",method = RequestMethod.GET)
public String hello(@RequestParam String username){
return "hello "+username;
}
@RequestMapping(value = "hello2",method = RequestMethod.GET)
public User hello2(@RequestHeader String username,@RequestHeader Integer age){
return new User(username,age);
}
@RequestMapping(value = "hello3",method = RequestMethod.POST)
public String hello3(@RequestBody User user){
return "hello "+user.getUsername() +", "+user.getAge()+", "+user.getId();
}
}
訪問:
localhost:8080/user/hello1?username=zhihao.miao
User對象的定義如下移层,需要注意的是要有User的默認(rèn)的構(gòu)造函數(shù)仍翰,不然,spring cloud feign根據(jù)json字符串轉(zhuǎn)換User對象的時候會拋出異常观话。
public class User {
private String username;
private int age;
private int id;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public User(String username, int age) {
this.username = username;
this.age = age;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
完成對user-service
的改造之后予借,我們對pay-service
進行改造:
- 在
user-service
中創(chuàng)建與上面一樣的User類 - 在
pay-service
中的UserService接口中加入之前的接口定義:
@FeignClient("user-service")
public interface UserService {
@RequestMapping("/user/index")
String index();
@RequestMapping("/user/hello")
String hello();
@RequestMapping(value = "/user/hello1",method = RequestMethod.GET)
String hello1(@RequestParam("username") String username);
@RequestMapping(value = "/user/hello2",method = RequestMethod.GET)
User hello2(@RequestHeader("username") String username, @RequestHeader("age") Integer age);
@RequestMapping(value = "/user/hello3",method = RequestMethod.POST)
String hello3(@RequestBody User user);
}
注意
在定義各參數(shù)綁定的時候,@RequestParam
和@RequestHeader
等可以指定參數(shù)名稱的注解频蛔,它們的value
值千萬不能少灵迫。在spring mvc
程序中,這些注解會根據(jù)指定參數(shù)名來作為默認(rèn)值晦溪,但是在fegin中綁定參數(shù)必須通過value屬性來指明具體的參數(shù)名瀑粥,不然會拋出IllegalStateException
異常,value屬性不能為空三圆。
- 在payservice中增加對UserService中新增接口的調(diào)用狞换,來驗證feign客戶端的調(diào)用是否可行:
@RestController
@RequestMapping("/pay")
public class PayController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
UserService userService;
@RequestMapping("/index")
public String index(){
return userService.index();
}
@RequestMapping("/hello")
public String hello(){
return userService.hello();
}
@RequestMapping(value = "/hello1",method = RequestMethod.GET)
public String hello1(@RequestParam String username){
return userService.hello1(username);
}
@RequestMapping(value = "/hello2",method = RequestMethod.GET)
public User hello2(@RequestHeader String username,@RequestHeader Integer age){
logger.info(age.getClass().getName());
return userService.hello2(username,age);
}
@RequestMapping(value = "/hello3",method = RequestMethod.POST)
public String hello3(@RequestBody User user){
return userService.hello3(user);
}
}
測試,
localhost:7070/pay/hello1?username=zhihao.miao
localhost:7070/pay/hello2
localhost:7070/pay/hello3
繼承特性
通過上面的快速入門和參數(shù)綁定二個demo舟肉,當(dāng)使用springmvc的注解來綁定服務(wù)接口時候修噪,我們幾乎可以完全從服務(wù)提供方(user-service)的Controller中依靠復(fù)制操作,構(gòu)建出相應(yīng)的服務(wù)客戶端綁定接口路媚。既然存在這么多復(fù)制操作黄琼,我們自然需要考慮這部分內(nèi)容是否可以得到進一步的抽象。spring cloud feign中整慎,針對該問題提供了繼承特性來幫助我們解決這些復(fù)制操作脏款,以進一步減少編碼量围苫。
- 定義一個maven工程,
user-service-api
- 由于在
user-service-api
中需要定義可同時復(fù)用于服務(wù)端與客戶端的接口弛矛,需要用到spring mvc的注解够吩,所以在pom.xml中引入spring-boot-starter-web
依賴,具體的內(nèi)容如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 將之前的User對象復(fù)制到自己的項目中丈氓,創(chuàng)建
UserService
接口周循,內(nèi)容如下:
@RequestMapping("/refactor")
public interface UserService {
@RequestMapping(value = "/hello4",method = RequestMethod.GET)
String hello1(@RequestParam("username") String username);
@RequestMapping(value = "/hello5",method = RequestMethod.GET)
User hello2(@RequestHeader("username") String username, @RequestHeader("age") Integer age);
@RequestMapping(value = "/hello6",method = RequestMethod.POST)
String hello3(@RequestBody User user);
}
- 對user-service進行重構(gòu),在pom依賴中加入user-service-api的依賴:
<dependency>
<groupId>com.zhihao.miao</groupId>
<artifactId>user-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 創(chuàng)建
com.zhihao.miao.user.controller.RefactorUserController
繼承user-service
中的UserService
接口万俗,實現(xiàn)如下:
@RestController
public class RefactorUserController implements UserService{
@Override
public String hello1(@RequestParam String username) {
return "user "+username;
}
@Override
public User hello2(@RequestHeader("username") String username, @RequestHeader("age") Integer age) {
return new User(username,age);
}
@Override
public String hello3(@RequestBody User user) {
return "user "+user.getUsername()+", "+user.getAge();
}
}
我們看到可以通過集成的方式湾笛,在Controller
中不再包含以往會定義的映射注解@RequestMapping
,而參數(shù)的注解定義在重寫的時候自動帶過來了闰歪,這個類中嚎研,除了要實現(xiàn)接口邏輯之外,只需要增加了@RestController
注解使該類成為一個REST接口類库倘。
此時這些restful接口的接口url就是user-service-api
中定義的临扮,具體的uri地址是/refactor/hello4
,/refactor/hello5
教翩,/refactor/hello6
- 完成了對服務(wù)提供者的重構(gòu)杆勇,在消費端的pay-service中也要進行改造,在pay-service中加入如下依賴:
<dependency>
<groupId>com.zhihao.miao</groupId>
<artifactId>user-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 創(chuàng)建
RefactorUserService
,繼承user-service-api
的UserService
接口饱亿,然后添加@FeignClient
來綁定服務(wù)蚜退。
@FeignClient(value = "user-service")
public interface RefactorUserService extends com.zhihao.miao.service.UserService{
}
- 在
PayController2
中注入RefactorUserService
實例,新增restful接口進行訪問:
@RestController
@RequestMapping("/pay2")
public class PayController2 {
@Autowired
RefactorUserService refactorUserService;
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping(value = "/hello1",method = RequestMethod.GET)
public String hello4(@RequestParam String username){
return refactorUserService.hello1(username);
}
@RequestMapping(value = "/hello2",method = RequestMethod.GET)
public User hello5(@RequestHeader String username, @RequestHeader Integer age){
logger.info(age.getClass().getName());
return refactorUserService.hello2(username,age);
}
@RequestMapping(value = "/hello3",method = RequestMethod.POST)
public String hello6(@RequestBody User user){
return refactorUserService.hello3(user);
}
}
- 測試:
訪問user-service
服務(wù):localhost:8080/refactor/hello4?username=zhihao.miao
測試pay-service:
localhost:7070/pay/hello1?username=zhihao.miao
優(yōu)點與缺點
使用spring cloud feign
的繼承特性的優(yōu)點很明顯彪笼,可以將接口的定義從Controller
中剝離钻注,同時配合maven倉庫就可以輕易實現(xiàn)接口定義的共享,實現(xiàn)在構(gòu)建期的接口綁定配猫,從而有效的減少服務(wù)客戶端的綁定配置幅恋。這么做雖然可以很方便的實現(xiàn)接口定義和依賴的共享,不用在復(fù)制粘貼接口進行綁定泵肄,但是這樣的做法使用不當(dāng)?shù)脑挄砀弊饔眉亚病S捎诮涌谠跇?gòu)建期間就建立起了依賴,那么接口變化就會對項目構(gòu)建造成了影響凡伊,可能服務(wù)提供方修改一個接口定義零渐,那么會直接導(dǎo)致客戶端工程的構(gòu)建失敗。所以系忙,如果開發(fā)團隊通過此方法來實現(xiàn)接口共享的話诵盼,建議在開發(fā)評審期間嚴(yán)格遵守面向?qū)ο蟮拈_閉原則,盡可能低做好前后版本兼容,防止因為版本原因造成接口定義的不一致风宁。
代碼地址
代碼地址