前言
由上文我們知道針對某一個Feign介面,我們可以給他設定特定的配置類。那如果現在有一個服務,我們只想對A服務配置一個攔截器攔截請求而不影響其他服務,那應該怎麼做呢?
一、feign介面配置
由前面的文章我們知道了feign的代理過程以及呼叫過程。現在我們看一下feign都有哪些配置?
- @FeignClient可配置屬性列表
屬性 | 值型別 | 含義 |
---|---|---|
value | string | 服務名,無論是否有URL,服務名稱都不能為空,它可以設定為屬性值${server_name}這種形式 |
name | string | 同value |
qualifier | string | 指定feign介面在容器中的bean例項名稱(我們知道每一個feign介面最終都會向容器注入一個例項),等同於@Qualifier |
url | string | 絕對URL或可解析主機名(協議是可選的) |
decode404 | boolean | 是否應該解碼404而不是丟擲假異常 |
fallback | class | 指定一個會退類(比如發生未處理的異常後)。回退類必須實現feign介面,並註冊到容器。 |
fallbackFactory | class | 為指定的外部客戶機介面定義回退工廠。回退工廠必須實現FallbackFactory介面,此工廠介面返回一個feign介面對應的回滾類。回退工廠必須註冊到容器。 |
path | string | 指定相對路徑 |
primary | boolean,預設true | 當容器中存在同名的bean的實現,以當前代理物件的bean為主。 |
configuration | class | 當前feign介面的配置類,他會將配置類註冊到當前feign物件的容器中(非應用程式的上下文環境) |
- 配置是何時被載入的
這裡直接上核心程式碼(FeignClientsRegistrar.registerClientConfiguration)
// name @FeignClient的name屬性值
// configuration @FeignClient的class物件
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
由上面的程式碼我們知道,他是將@FeignClient物件的configuration屬性對應的配置類作為成員FeignClientSpecification的屬性注入到應用上下文。那看到這裡我們就會產生一個疑問:configuration對應的配置類到底什麼時候被初始化呢?最終發現在為@FeignClient生成代理物件的時候,他初始化了FeignClient對應的配置。如下(NamedContextFactory.createContext):
// 在獲取一個@FeignClient介面對應的代理物件前,先為其建立Context物件
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
// 註冊配置
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
// 註冊配置
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
// 為context註冊環境變數
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
顯然,在為@FeignClient介面的代理物件建立了Context後,將配置初始化到了該Context。這裡可以明確的是每一個@FeignClient只要name屬性不一致,那麼他們將擁有不同的上下文。
二、feign呼叫的沙箱設計思路
在現實中的sandbox,是一種兒童玩具,類如KFC中一個裝滿小球的容器,兒童可以在隨意玩耍,起到保護兒童的作用。(也可以理解為一種安全環境)。同樣在網路技術中也是一種按照安全策略限制程式行為的執行環境。在feign的呼叫過程中,針對不同的服務為其建立一個上下文,我們可以將這個上下文理解為sandbox。執行呼叫所需要的資源是從各自的沙箱環境中取。整體的職責分工如下圖所示。
從中我們可以看到,我們最終呼叫的執行器來源是來自沙箱,而沙箱的配置可以各不相同,建立不同的Client,這給了我們決定不同的服務怎樣去呼叫高度的自主權。也解決了文章開篇提出的問題。
三、小結
至此,feign呼叫暫時告一段落。由於後續要花時間做一個開源框架,文章的書寫便只能告一段落了。