OpenFeign是一個遠程客戶端請求代理,它的基本作用是讓開發者能夠以面向接口的方式來實現遠程調用,從而屏蔽底層通信的復雜性,它的具體原理如下圖所示。
在今天的內容中,我們需要詳細分析OpenFeign它的工作原理及源碼,我們繼續回到這段代碼。
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
IGoodsServiceFeignClient goodsServiceFeignClient;
@Autowired
IPromotionServiceFeignClient promotionServiceFeignClient;
@Autowired
IOrderServiceFeignClient orderServiceFeignClient;
/**
* 下單
*/
@GetMapping
public String order(){
String goodsInfo=goodsServiceFeignClient.getGoodsById();
String promotionInfo=promotionServiceFeignClient.getPromotionById();
String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
return result;
}
}
從這段代碼中,先引出對于OpenFeign功能實現的思考。
- 聲明
@FeignClient
注解的接口,如何被解析和注入的? - 通過
@Autowired
依賴注入,到底是注入一個什么樣的實例 - 基于FeignClient聲明的接口被解析后,如何存儲?
- 在發起方法調用時,整體的工作流程是什么樣的?
- OpenFeign是如何集成Ribbon做負載均衡解析?
帶著這些疑問,開始去逐項分析OpenFeign的核心源碼
OpenFeign注解掃描與解析
思考, 一個被聲明了
@FeignClient
注解的接口,使用@Autowired
進行依賴注入,而最終這個接口能夠正常被注入實例。
從這個結果來看,可以得到兩個結論
- 被
@FeignClient
聲明的接口,在Spring容器啟動時,會被解析。 - 由于被Spring容器加載的是接口,而接口又沒有實現類,因此Spring容器解析時,會生成一個動態代理類。
EnableFeignClient
@FeignClient
注解是在什么時候被解析的呢?基于我們之前所有積累的知識,無非就以下這幾種
- ImportSelector,批量導入bean
- ImportBeanDefinitionRegistrar,導入bean聲明并進行注冊
- BeanFactoryPostProcessor , 一個bean被裝載的前后處理器
在這幾個選項中,似乎ImportBeanDefinitionRegistrar
更合適,因為第一個是批量導入一個bean的string集合,不適合做動態Bean的聲明。 而BeanFactoryPostProcessor
是一個Bean初始化之前和之后被調用的處理器。
而在我們的FeignClient聲明中,并沒有Spring相關的注解,所以自然也不會被Spring容器加載和觸發。
那么
@FeignClient
是在哪里被聲明掃描的呢?
在集成FeignClient時,我們在SpringBoot的main方法中,聲明了一個注解@EnableFeignClients(basePackages = "com.gupaoedu.ms.api")
。這個注解需要填寫一個指定的包名。
嗯,看到這里,基本上就能猜測出,這個注解必然和@FeignClient
注解的解析有莫大的關系。
下面這段代碼是@EnableFeignClients
注解的聲明,果然看到了一個很熟悉的面孔FeignClientsRegistrar
。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrar
FeignClientRegistrar,主要功能就是針對聲明@FeignClient
注解的接口進行掃描和注入到IOC容器。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
}
果然,這個類實現了ImportBeanDefinitionRegistrar
接口
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
this.registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
這個接口有兩個重載的方法,用來實現Bean的聲明和注冊。
簡單給大家演示一下ImportBeanDefinitionRegistrar的作用。
在
gpmall-portal
這個項目的com.gupaoedu
目錄下,分別創建
- HelloService.java
- GpImportBeanDefinitionRegistrar.java
- EnableGpRegistrar.java
- TestMain
- 定義一個需要被裝載到IOC容器中的類HelloService
public class HelloService {
}
- 定義一個Registrar的實現,定義一個bean,裝載到IOC容器
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- 定義一個注解類
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
- 寫一個測試類
@Configuration
@EnableGpRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationContext.getBean(HelloService.class));
}
}
- 通過結果演示可以發現,
HelloService
這個bean 已經裝載到了IOC容器。
這就是動態裝載的功能實現,它相比于@Configuration配置注入,會多了很多的靈活性。 ok,再回到FeignClient的解析中來。
FeignClientsRegistrar.registerBeanDefinitions
-
registerDefaultConfiguration
方法內部從SpringBoot
啟動類上檢查是否有@EnableFeignClients
, 有該注解的話, 則完成Feign
框架相關的一些配置內容注冊。 -
registerFeignClients
方法內部從classpath
中, 掃描獲得@FeignClient
修飾的類, 將類的內容解析為BeanDefinition
, 最終通過調用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition
將解析處理過的FeignClient BeanDeifinition
添加到spring
容器中
//BeanDefinitionReaderUtils.resgisterBeanDefinition
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注冊@EnableFeignClients中定義defaultConfiguration屬性下的類,包裝成FeignClientSpecification,注冊到Spring容器。
//在@FeignClient中有一個屬性:configuration,這個屬性是表示各個FeignClient自定義的配置類,后面也會通過調用registerClientConfiguration方法來注冊成FeignClientSpecification到容器。
//所以,這里可以完全理解在@EnableFeignClients中配置的是做為兜底的配置,在各個@FeignClient配置的就是自定義的情況。
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
這里面需要重點分析的就是
registerFeignClients
方法,這個方法主要是掃描類路徑下所有的@FeignClient
注解,然后進行動態Bean的注入。它最終會調用registerFeignClient
方法。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerFeignClient(registry, annotationMetadata, attributes);
}
FeignClientsRegistrar.registerFeignClients
registerFeignClients方法的定義如下。
//# FeignClientsRegistrar.registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//獲取@EnableFeignClients注解的元數據
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//獲取@EnableFeignClients注解中的clients屬性,可以配置@FeignClient聲明的類,如果配置了,則需要掃描并加載。
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//默認TypeFilter生效,這種模式會查詢出許多不符合你要求的class名
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); //添加包含過濾的屬性@FeignClient。
Set<String> basePackages = getBasePackages(metadata); //從@EnableFeignClients注解中獲取basePackages配置。
for (String basePackage : basePackages) {
//scanner.findCandidateComponents(basePackage) 掃描basePackage下的@FeignClient注解聲明的接口
candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //添加到candidateComponents,也就是候選容器中。
}
}
else {//如果配置了clients,則需要添加到candidateComponets中。
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
//遍歷候選容器列表。
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) { //如果屬于AnnotatedBeanDefinition實例類型
// verify annotated class is an interface
//得到@FeignClient注解的beanDefinition
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); //獲取這個bean的注解元數據
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//獲取元數據屬性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
//獲取@FeignClient中配置的服務名稱。
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
FeignClient Bean的注冊
這個方法就是把FeignClient接口注冊到Spring IOC容器,
FeignClient是一個接口,那么這里注入到IOC容器中的對象是什么呢?
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName(); //獲取FeignClient接口的類全路徑
Class clazz = ClassUtils.resolveClassName(className, null); //加載這個接口,得到Class實例
//構建ConfigurableBeanFactory,提供BeanFactory的配置能力
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
//獲取contextId
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
//構建一個FeignClient FactoryBean,這個是工廠Bean。
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
//設置工廠Bean的相關屬性
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
//BeanDefinitionBuilder是用來構建BeanDefinition對象的建造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
//把@FeignClient注解配置中的屬性設置到FactoryBean中。
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject(); //factoryBean.getObject() ,基于工廠bean創造一個bean實例。
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //設置注入模式,采用類型注入
definition.setLazyInit(true); //設置延遲華
validate(attributes);
//從BeanDefinitionBuilder中構建一個BeanDefinition,它用來描述一個bean的實例定義。
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); //把BeanDefinition的這個bean定義注冊到IOC容器。
}
綜上代碼分析,其實實現邏輯很簡單。
- 創建一個BeanDefinitionBuilder。
- 創建一個工廠Bean,并把從@FeignClient注解中解析的屬性設置到這個FactoryBean中
- 調用registerBeanDefinition注冊到IOC容器中
關于FactoryBean
在上述的邏輯中,我們重點需要關注的是FactoryBean,這個大家可能接觸得會比較少一點。
首先,需要注意的是FactoryBean和BeanFactory是不一樣的,FactoryBean是一個工廠Bean,可以生成某一個類型Bean實例,它最大的一個作用是:可以讓我們自定義Bean的創建過程。
而,BeanFactory是Spring容器中的一個基本類也是很重要的一個類,在BeanFactory中可以創建和管理Spring容器中的Bean。
下面這段代碼是FactoryBean接口的定義。
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
從上面的代碼中我們發現在FactoryBean中定義了一個Spring Bean的很重要的三個特性:是否單例、Bean類型、Bean實例,這也應該是我們關于Spring中的一個Bean最直觀的感受。
FactoryBean自定義使用
下面我們來模擬一下@FeignClient解析以及工廠Bean的構建過程。
- 先定義一個接口,這個接口可以類比為我們上面描述的FeignClient.
public interface IHelloService {
String say();
}
- 接著,定義一個工廠Bean,這個工廠Bean中主要是針對IHelloService生成動態代理。
public class DefineFactoryBean implements FactoryBean<IHelloService> {
@Override
public IHelloService getObject() {
IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {
System.out.println("begin execute");
return "Hello FactoryBean";
});
return helloService;
}
@Override
public Class<?> getObjectType() {
return IHelloService.class;
}
}
- 通過實現ImportBeanDefinitionRegistrar這個接口,來動態注入Bean實例
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
DefineFactoryBean factoryBean=new DefineFactoryBean();
BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(
IHelloService.class,()-> factoryBean.getObject());
BeanDefinition beanDefinition=definition.getBeanDefinition();
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- 聲明一個注解,用來表示動態bean的注入導入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
- 編寫測試類,測試IHelloService這個接口的動態注入
@Configuration
@EnableGpRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
IHelloService is=applicationContext.getBean(IHelloService.class);
System.out.println(is.say());
}
}
- 運行上述的測試方法,可以看到IHelloService這個接口,被動態代理并且注入到了IOC容器。
FeignClientFactoryBean
由上述案例可知,Spring IOC容器在注入FactoryBean時,會調用FactoryBean的getObject()方法獲得bean的實例。因此我們可以按照這個思路去分析FeignClientFactoryBean
@Override
public Object getObject() {
return getTarget();
}
構建對象Bean的實現代碼如下,這個代碼的實現較長,我們分為幾個步驟來看
Feign上下文的構建
先來看上下文的構建邏輯,代碼部分如下。
<T> T getTarget() {
FeignContext context = beanFactory != null
? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
}
兩個關鍵的對象說明:
- FeignContext是全局唯一的上下文,它繼承了NamedContextFactory,它是用來來統一維護feign中各個feign客戶端相互隔離的上下文,FeignContext注冊到容器是在FeignAutoConfiguration上完成的,代碼如下!
//FeignAutoConfiguration.java
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
在初始化FeignContext時,會把configurations
放入到FeignContext中。configuration
表示當前被掃描到的所有@FeignClient。
- Feign.Builder用來構建Feign對象,基于builder實現上下文信息的構建,代碼如下。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class)); //contract協議,用來實現模版解析(后面再詳細分析)
// @formatter:on
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
從代碼中可以看到,feign
方法,主要是針對不同的服務提供者生成Feign的上下文信息,比如logger
、encoder
、decoder
等。因此,從這個分析過程中,我們不難猜測到它的原理結構,如下圖所示
父子容器隔離的實現方式如下,當調用get方法時,會從context
中去獲取指定type的實例對象。
//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + contextId);
}
return instance;
}
接著,調用NamedContextFactory
中的getInstance
方法。
//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
//根據`name`獲取容器上下文
AnnotationConfigApplicationContext context = this.getContext(name);
try {
//再從容器上下文中獲取指定類型的bean。
return context.getBean(type);
} catch (NoSuchBeanDefinitionException var5) {
return null;
}
}
getContext
方法根據name
從contexts
容器中獲得上下文對象,如果沒有,則調用createContext
創建。
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized(this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, this.createContext(name));
}
}
}
return (AnnotationConfigApplicationContext)this.contexts.get(name);
}
生成動態代理
第二個部分,如果@FeignClient注解中,沒有配置url
,也就是不走絕對請求路徑,則執行下面這段邏輯。
由于我們在@FeignClient注解中使用的是name
,所以需要執行負載均衡策略的分支邏輯。
<T> T getTarget() {
//省略.....
if (!StringUtils.hasText(url)) { //是@FeignClient中的一個屬性,可以定義請求的絕對URL
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name
+ "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
//省略....
}
loadBalance方法的代碼實現如下,其中Client
是Spring Boot自動裝配的時候實現的,如果替換了其他的http協議框架,則client則對應為配置的協議api。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//Feign發送請求以及接受響應的http client,默認是Client.Default的實現,可以修改成OkHttp、HttpClient等。
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client); //針對當前Feign客戶端,設置網絡通信的client
//targeter表示HystrixTarger實例,因為Feign可以集成Hystrix實現熔斷,所以這里會一層包裝。
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon or spring-cloud-starter-loadbalancer?");
}
HystrixTarget.target
代碼如下,我們沒有集成Hystrix,因此不會觸發Hystrix相關的處理邏輯。
//HystrixTarget.java
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { //沒有配置Hystrix,則走這部分邏輯
return feign.target(target);
}
//省略....
return feign.target(target);
}
進入到Feign.target
方法,代碼如下。
//Feign.java
public <T> T target(Target<T> target) {
return this.build().newInstance(target); //target.HardCodedTarget
}
public Feign build() {
//這里會構建一個LoadBalanceClient
Client client = (Client)Capability.enrich(this.client, this.capabilities);
Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
}).collect(Collectors.toList());
//OpenFeign Log配置
Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
//模版解析協議(這個接口非常重要:它決定了哪些注解可以標注在接口/接口方法上是有效的,并且提取出有效的信息,組裝成為MethodMetadata元信息。)
Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
//封裝Request請求的 連接超時=默認10s ,讀取超時=默認60
Options options = (Options)Capability.enrich(this.options, this.capabilities);
//編碼器
Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
//解碼器
Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
//synchronousMethodHandlerFactory, 同步方法調用處理器(很重要,后續要用到)
Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
//ParseHandlersByName的作用就是我們傳入Target(封裝了我們的模擬接口,要訪問的域名),返回這個接口下的各個方法,對應的執行HTTP請求需要的一系列信息
ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
build
方法中,返回了一個ReflectiveFeign
的實例對象,先來看ReflectiveFeign
中的newInstance
方法。
public <T> T newInstance(Target<T> target) { //修飾了@FeignClient注解的接口方法封裝成方法處理器,把指定的target進行解析,得到需要處理的方法集合。 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //定義一個用來保存需要處理的方法的集合 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); //JDK8以后,接口允許默認方法實現,這里是對默認方法進行封裝處理。 List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); //遍歷@FeignClient接口的所有方法 for (Method method : target.type().getMethods()) { //如果是Object中的方法,則直接跳過 if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) {//如果是默認方法,則把該方法綁定一個DefaultMethodHandler。 DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else {//否則,添加MethodHandler(SynchronousMethodHandler)。 methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } //創建動態代理類。 InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy;}
上述代碼,其實也不難理解。
-
解析@FeignClient接口聲明的方法,根據不同方法綁定不同的處理器。
- 默認方法,綁定DefaultMethodHandler
- 遠程方法,綁定SynchronousMethodHandler
使用JDK提供的Proxy創建動態代理
MethodHandler,會把方法參數、方法返回值、參數集合、請求類型、請求路徑進行解析存儲,如下圖所示。
FeignClient接口解析
接口解析也是Feign很重要的一個邏輯,它能把接口聲明的屬性轉化為HTTP通信的協議參數。
執行邏輯RerlectiveFeign.newInstance
public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //here}
targetToHandlersByName.apply(target);會解析接口方法上的注解,從而解析出方法粒度的特定的配置信息,然后生產一個SynchronousMethodHandler
然后需要維護一個<method,MethodHandler>的map,放入InvocationHandler的實現FeignInvocationHandler中。
public Map<String, MethodHandler> apply(Target target) { List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else { buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target); } if (md.isIgnored()) { result.put(md.configKey(), args -> { throw new IllegalStateException(md.configKey() + " is not a method handled by feign"); }); } else { result.put(md.configKey(), factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } } return result;}
為了更好的理解上述邏輯,我們可以借助下面這個圖來理解!
階段性小結
通過上述過程分析,被聲明為@FeignClient注解的類,在被注入時,最終會生成一個動態代理對象FeignInvocationHandler。
當觸發方法調用時,會被FeignInvocationHandler#invoke攔截,FeignClientFactoryBean在實例化過程中所做的事情如下圖所示。
總結來說就幾個點:
- 解析Feign的上下文配置,針對當前的服務實例構建容器上下文并返回Feign對象
- Feign根據上下圍配置把 log、encode、decoder、等配置項設置到Feign對象中
- 對目標服務,使用LoadBalance以及Hystrix進行包裝
- 通過Contract協議,把FeignClient接口的聲明,解析成MethodHandler
- 遍歷MethodHandler列表,針對需要遠程通信的方法,設置
SynchronousMethodHandler
處理器,用來實現同步遠程調用。 - 使用JDK中的動態代理機制構建動態代理對象。
遠程通信實現
在Spring啟動過程中,把一切的準備工作準備就緒后,就開始執行遠程調用。
在前面的分析中,我們知道OpenFeign最終返回的是一個#ReflectiveFeign.FeignInvocationHandler的對象。
那么當客戶端發起請求時,會進入到FeignInvocationHandler.invoke方法中,這個大家都知道,它是一個動態代理的實現。
//FeignInvocationHandler.java@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } return dispatch.get(method).invoke(args);}
接著,在invoke方法中,會調用this.dispatch.get(method)).invoke(args)
。this.dispatch.get(method)
會返回一個SynchronousMethodHandler,進行攔截處理。
this.dispatch,其實就是在初始化過程中創建的,private final Map<Method, MethodHandler> dispatch;
實例。
SynchronousMethodHandler.invoke
這個方法會根據參數生成完成的RequestTemplate對象,這個對象是Http請求的模版,代碼如下,代碼的實現如下:
@Override
public Object invoke(Object[] argv) throws Throwable { //argv,表示調用方法傳遞的參數。
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv); //獲取配置項,連接超時時間、遠程通信數據獲取超時時間
Retryer retryer = this.retryer.clone(); //獲取重試策略
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
上述代碼的執行流程中,需要先構造一個Request對象,然后調用client.execute方法執行網絡通信請求,整體實現流程如下。
executeAndDecode
經過上述的代碼,我們已經將RequestTemplate拼裝完成,上面的代碼中有一個executeAndDecode()
方法,該方法通過RequestTemplate生成Request請求對象,然后利用Http Client獲取response,來獲取響應信息。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template); //把template轉化為請求報文
if (logLevel != Logger.Level.NONE) { //設置日志level
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//發起請求,此時client是LoadBalanceFeignClient,需要先對服務名稱進行解析負載
response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 12 //獲取返回結果
response = response.toBuilder().request(request).requestTemplate(template).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null) //如果設置了解碼器,這需要對響應數據做解碼
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response, metadata.returnType(), elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
LoadBalanceClient
@Overridepublic
Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url()); //獲取請求uri,此時的地址還未被解析。
String clientName = asUri.getHost(); //獲取host,實際上就是服務名稱
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost); //加載客戶端的配置信息
IClientConfig requestConfig = getClientConfig(options, clientName); //創建負載均衡客戶端(FeignLoadBalancer),執行請求
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
} catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
從上面的代碼可以看到,lbClient(clientName) 創建了一個負載均衡的客戶端,它實際上就是生成的如下所述的類:
public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
整體總結
OpenFeign的整體通信原理解析,如下圖所示。