微服務通信之feign的配置隔離

前言

由上文我們知道針對某一個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屬性不一致(2.1版本以後可以手動指定contextId),那麼他們將擁有不同的上下文。

二、feign調用的沙箱設計思路

在現實中的sandbox,是一種兒童玩具,類如KFC中一個裝滿小球的容器,兒童可以在隨意玩耍,起到保護兒童的作用。(也可以理解爲一種安全環境)。同樣在網絡技術中也是一種按照安全策略限制程序行爲的執行環境。在feign的調用過程中,針對不同的服務爲其創建一個上下文,我們可以將這個上下文理解爲sandbox。執行調用所需要的資源是從各自的沙箱環境中取。整體的職責分工如下圖所示。

從中我們可以看到,我們最終調用的執行器來源是來自沙箱,而沙箱的配置可以各不相同,創建不同的Client,這給了我們決定不同的服務怎樣去調用高度的自主權。也解決了文章開篇提出的問題。

三、小結

至此,feign調用暫時告一段落。由於後續要花時間做一個開源框架,文章的書寫便只能告一段落了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章