Spring Cloud源碼研讀(二):openFeign

Feign的工作原理

openFeign爲了實現高度的靈活和舒適的使用體驗,使用了大量的設計模式。簡直是設計模式學習的最佳範本。

初始化過程
  1. @EnableFeignClients註解將類FeignClientsRegistrar註冊到Spring中。
  2. 當springboot應用啓動時,FeignClientsRegistrar會掃描所有@FeignClients的註解的類,將這些接口bean註冊到spring容器中。
  3. 在spring初始化@FeignClients接口bean時,通過FeignClientFactoryBean生成相關接口的代理類。
  4. FeignClientFactoryBean生成代理時,Feign會爲每個接口方法創建一個RequestTemplate,該對象封裝HTTP請求需要的全部信息,如請求參數名,請求方法等。
  5. 在實際接口調用時,RequestTemplate生成Request,然後把Request交給Client去處理。
運行時調用棧
  1. ReflectiveFeign 被反射實例化
  2. 調用ReflectiveFeign.invoke
  3. 調用SynchronousMethodHandler.invoke。此處實例化RequestTemplate
  4. 調用SynchronousMethodHandler.executeAndDecode
  5. 將RequestTemplate build爲request,調用http客戶端執行
  6. 將Response Decode爲Object並返回
初始化過程重要源代碼

在spring-cloud-openfeign-core.jar!/META-INF/spring.factories中,定義了Feign自動配置的相關類。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

FeignAutoConfiguration是核心的配置,根據條件分別自動導入了FeignContext, Targeter, feign.Client。這些bean在不同情況下有不同的實現。

Bean註冊與初始化
  1. FeignClientsRegistrar在註冊FeignClient接口時,將FeignClientFactoryBean作爲構造工廠傳給了BeanDefinition,這樣後續bean初始化時可以通過FeignClientFactoryBean實現接口。
//FeignClientsRegistrar.java
	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
//此次省略若干行
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean) attributes.get("primary");
		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
  1. 以上代碼中BeanDefinitionBuilder提供了一種動態設置參數的能力。@EnableFeignClients註解的參數值在此時被設置到AbstractBeanDefinition
  2. 在初始化FeignClient聲明的某個接口時,FeignClientFactoryBean將首先被創建一次。由於每個FeignClient接口聲明不同的名稱,FeignClientFactoryBean會被重複創建。FeignClientFactoryBean創建後將基於上文beanDefinition的PropertyValues被逐一賦值。
  3. bean容器中的接口實例是通過FeignClientFactoryBean.getObject() -> getTarget()獲得的。
  4. 在FeignClientFactoryBean.getTarget()中,Feign.Builder是一個創建Feign的構造器,提供了一系列創建代理類的模板,Client通用配置都在這個builder中。
//FeignClientFactoryBean.java
	<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}
  1. FeignClientFactoryBean.getTarget()將具體的代理創建工作交給了Targeter.target();
    bean初始化相關的類如下:
    在這裏插入圖片描述
  2. ReflectiveFeign.newInstance()創建具體的代理類,指定FeignInvocationHandler執行接口響應。

至此,Feign的初始化過程結束。

http請求實現邏輯

當一個服務ServiceCaller調用FeignClient聲明的方法myMathod時,將觸發如下調用鏈:

ServiceCaller
1. -> $Proxy.myMathod(param) 
2. -> ReflectiveFeign$FeignInvocationHandler.invoke(Object,Method,Object[])
3. -> SynchronousMethodHandler.invoke(Object[])
4. -> SynchronousMethodHandler.executeAndDecode(RequestTemplate)
5. -> CloseableHttpClient.execute(HttpUriRequest)
  • 第1步是JDK實現的接口代理實現類,方法名就是我們聲明的方法名。
  • FeignInvocationHandler統一響應了接口所有的方法,通過Method實現了對具體的響應方法的分發。
  • 第3步調用RequestTemplate.Factory將請求參數封裝到RequesTemplate中。
  • 第4步是http請求的核心,包含了我們手寫http請求執行的常規動作:
    • 執行RequestInterceptor攔截器,對請求進行修飾;
    • 執行Target.apply()將RequestTemplate轉換爲Request;
    • 執行feign.Client.execute(request, options);即上面的第5步;
    • 將Response轉換成接口聲明的返回參數格式;
    • 如果有異常,執行異常處理流程decode404或者ErrorDecoder;

設計模式研讀

適配器模式

Feign支持 URLConnection、HTTP Client 和 OKHttp實現遠程調用,Feign通過適配器模式同時支持這些遠程調用庫。
先看一下經典的適配器模式UML圖:
在這裏插入圖片描述
通過抽象出feign.Client接口實現具體的http調用方案的解耦。通過ApacheHttpClient等對象,實現具體http請求方案的適配。
在這裏插入圖片描述

裝飾器模式

Feign通過Ribbon實現了客戶端負載均衡,負載均衡是http請求的一個增強功能,非常適合通過裝飾器模式實現。
經典的裝飾器模式UML圖:
在這裏插入圖片描述
依然還是Client接口,不同的是LoadBalancerFeignClient除了實現了Client外,還擁有一個Client的屬性,LoadBalancerFeignClient在執行http請求時,除了delegate執行具體的http請求以外,還提供客戶端負載均衡調度功能。
在這裏插入圖片描述

代理模式

爲了實現@FeignClient註解的接口,ReflectiveFeign直接使用了JDK的動態代理。

    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

ReflectiveFeign類圖:
在這裏插入圖片描述

適配器模式與裝飾器模式的區別

裝飾器與適配器都有一個別名叫做 包裝模式(Wrapper),它們看似都是起到包裝一個類或對象的作用,但是使用它們的目的很不一一樣。適配器模式的意義是要將一個接口轉變成另一個接口,它的目的是通過改變接口來達到重複使用的目的。而裝飾器模式不是要改變被裝飾對象的接口,而是恰恰要保持原有的接口,但是增強原有對象的功能,或者改變原有對象的處理方式而提升性能。所以這兩個模式設計的目的是不同的。

適配器模式與代理模式的區別

裝飾器模式關注於在一個對象上動態的添加業務邏輯,然而代理模式關注於控制對對象的訪問。換句話說,用代理模式在於對它的調用方客戶屏蔽一個業務邏輯的具體實現信息。因此,當我們使用裝飾器模式的時候,我們通常的做法是將原始對象作爲一個參數傳給裝飾者的構造器。當使用代理模式的時候,我們常常在一個代理類中創建一個對象的實例。

構造器模式(Builder)

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