目錄
1.Feign簡介
Feign是一種聲明式的web service客戶端,使用Feign只需定義一個接口並加上相應註解即可使用。通過Feign只需要簡單的幾行配置,即可實現調用遠程服務如調用本地服務般簡單
2.入門案例
服務名 | 服務端口 | 作用 |
---|---|---|
192.168.1.100 | 8500 | 註冊中心 |
eureka-client | 8762,8763 | 服務提供者 |
eureka-feign-client | 8765 | feign客戶端 |
-
新建服務
eureka-feign-client
-
引入依賴
從此章開始,後續章節均使用consul作爲註冊中心
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-all</artifactId> </dependency> </dependencies>
-
配置
bootstrap.yml
server: port: 8765 spring: application: # 服務註冊名 name: eureka-feign-service cloud: consul: config: # 設置配置文件夾 prefix: config # 設置配置文件夾位置 default-context: application # 設置配置文件名稱 data-key: data # 設置配置文件顯示樣式,有properties和yaml兩種格式 format: yaml # 開啓監聽,動態刷新配置,默認每秒觸發一次 watch: enabled: true # 修改動態刷新配置時間 # delay: 2000 # 應用和profile之間的分割符,與spring.profiles.active配合使用 # profile-separator: , enabled: true # consul服務器地址 host: 192.168.1.100 # 默認端口號 port: 8500 # 是否註冊,默認爲true discovery: register: true # 健康檢查時間 health-check-timeout: 2m enabled: true heartbeat: enabled: true # 優先ip註冊 prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.hostname}:${spring.application.instance_id:${server.port}}
-
書寫啓動類
注意啓動類中需引入註解
@EnableDiscoveryClient
和@EnableFeignClients
@EnableDiscoveryClient:服務註冊至註冊中心可被發現
@EnableFeignClients:開啓feign
@SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class EurekaFeignclientApplication { public static void main(String[] args) { SpringApplication.run(EurekaFeignclientApplication.class, args); } }
-
書寫一個FeignClient,開始遠程調用
@FeignClient(name = "eureka-client") public interface EurekaFeignclientService { @GetMapping("/hi") String sayHello(@RequestParam String name); }
-
訪問
http://localhost:8765/hi
出現如下結果hi 張三, i am from port:8763
hi 張三, i am from port:8762
3.Feign工作原理簡單分析
Feign通過包掃描FeignClient的Bean,該源碼在FeignClientsRegistar類中。程序啓動時,會檢查是否有@EnableFeignClients註解,如果有該註解,則開啓包掃描,掃描被@FeignClient註解的接口
- 3.1 FeignClientsRegistrar分析
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {}
- 3.2 查看
registerBeanDefinitions()
方法可知註冊bean時需處理下述兩個方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
- 3.3 繼續分析其中的兩個方法
registerDefaultConfiguration
:掃描是否開啓@EnableFeignClients,如果開啓,通過調用registerClientConfiguration
方法,將該類加載進spring容器中
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
registerFeignClients
:掃描項目中配置了@FeignClient註解的對象,如果存在,則將該對象加載進spring容器中
-
3.4 ReflectiveFeign分析
注入BeanDefinition之後,通過JDK代理,當調用Feign Client接口裏邊的方法時,該方法被攔截,從而根據參數生成RequestTemplate對象,最終實現請求下游服務
-
3.5 負載均衡分析
FeignRibbonClientAutoConfiguration
中注入了HttpClientFeignLoadBalancedConfiguration
@ConditionalOnClass({ ILoadBalancer.class, Feign.class }) @Configuration @AutoConfigureBefore(FeignAutoConfiguration.class) @EnableConfigurationProperties({ FeignHttpClientProperties.class }) @Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class }) public class FeignRibbonClientAutoConfiguration {}
查看
HttpClientFeignLoadBalancedConfiguration
源碼中通過feignClient加載Client對象,最終將給LoadBalancerFeignClient
對象進行初始化@Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) { ApacheHttpClient delegate = new ApacheHttpClient(httpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }
查看
LoadBalancerFeignClient
對象中的execute()
,該方法主要解析url,服務名,最終將請求信息交由executeWithLoadBalancer()
處理@Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } //省略 }
繼續查看
executeWithLoadBalancer()
源碼中submit()中selectServer()方法爲具體選擇下游服務實現private Observable<Server> selectServer() { return Observable.create(new OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> next) { try { Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); } } });3.5 負載均衡分析 }
最終通過
loadBalancerContext
對象實現負載均衡,而loadBalancerContext
則通過上篇文章中ILoadBalancer
s獲取下游服務器列表實現,最終根據選擇的服務列表實現負載均衡 -
3.6 源碼實現總結
(1) 首先通過
@EnableFeignClients
註解開啓FeignClient功能,只有註解存在,纔會掃描@FeignClient(2) 根據掃描到的@FeignClient接口,將接口創建交給spring容器管理
(3) 當調用@FeignClient修飾的接口時,通過JDK代理生成具體的RequestTemplate模板對象
(4) 根據RequestTemplate再生成Http請求的Request對象
(5) Request對象交給Client去處理,其中的Client網絡請求框架可以是HttpURLConnection、HttpClient和OKHttp
(6) 最後Client被封裝到LoadBalancerClient類中,通過Ribbon實現負載均衡
4. Feign基礎功能及配置
4.1 默認配置文件
Feign | default | 用途 |
---|---|---|
Decoder | ResponseEntityDecoder | 編碼 |
Logger | Slf4jLogger | 日誌 |
Encoder | SpringEncoder | 解碼 |
Contract | SpringMvcContract | 協議 |
Feign.Builder | HystrixFeign.Builder | |
Client | 如果Ribbon可用則爲LoadBalancerFeignClient,否則爲默認Feign client |
- 使用自定義配置
可以自定義配置,使用默認值,但要需要注意,如果使用自定義配置,則不能將該配置置至@ComponentScan之內
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
- 使用配置文件,配置單個FeignClient
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
- 所有FeignClient使用一套配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
注意:如果配置文件和@Configuration同時存在,優先使用配置文件,如果想使用@Configuration則可使用feign.client.default-to-properties=false
4.2 Feign開啓GZIP壓縮
具體用法大家可以查看官方Feign官方文檔
feign:
compression:
response:
enabled: true
request:
enabled: true
4.3Feign日誌
日誌級別 | 說明 |
---|---|
NONE | 不打印日誌(默認) |
BASIC | 只打印請求方式及url和響應碼,執行時間 |
HEADERS | 只打印請求及響應頭 |
FULL | 表示展示所有信息 |
Feign默認未開啓日誌,如果需要開啓日誌只需執行如下幾步
1.調用方啓動類或配置文件創建日誌Bean,配置日誌輸出信息
2.配置文件中配置日誌級別
下圖爲配置日誌級別
箭頭所指處即爲對外提供的api全路徑,當服務消費者調用該api時,則會以debug日誌級別打印
debug日誌級別打印信息
4.4 @QueryMap
使用request請求時,接收對象,可使用@SpringQueryMap進行接收
@FeignClient("demo")
public class DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}
6.參考資料
- sprincloud官方文檔
- 《深入理解Spring Cloud與微服務構建》