OpenFeign 服務註冊和呼叫原理

CyrusHuang發表於2024-09-05

Feign 註冊到容器

和 springboot 自動配置原理類似,在 springboot 啟動時會掃描到 EnableFeignClients 註解,這個註解匯入了一個 FeignClientsRegistrar 類

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}

FeignClientsRegistrar 實現了 ImportBeanDefinitionRegistrar,所以會透過 registerBeanDefinitions 方法注入一批 BeanDefinition

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 註冊配置類(上一篇文章說過的 EnableFeignClients.defaultConfiguration 屬性)
    registerDefaultConfiguration(metadata, registry);
    // 註冊 feign 客戶端
    registerFeignClients(metadata, registry);
}

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
// registerFeignClients,引數應該不陌生了,一個是元註解資訊,一個是 ImportBeanDefinitionRegistrar 用來註冊 bean 的
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

    LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
    // 透過元註解獲取到註解屬性(就是 EnableFeignClients 的配置項)
    Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    // 先看有沒有配 clients
    final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        // 掃描使用了 FeignClient 註解的類 
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        Set<String> basePackages = getBasePackages(metadata);
        for (String basePackage : basePackages) {
            candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
        }
    }
    else {
        // clients 指定的類
        for (Class<?> clazz : clients) {
            candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
        }
    }

    for (BeanDefinition candidateComponent : candidateComponents) {
        if (candidateComponent instanceof AnnotatedBeanDefinition) {
            ...
            registerFeignClient(registry, annotationMetadata, attributes);
        }
    }
}

實際註冊 bean 的方法 registerFeignClient,裡面根據配置資訊構建了一個 BeanDefinition,構建的過程還是比較簡單就是設定 beanName、type、lazy 等基礎資訊和 EnableFeignClients 配置的 fallback 等

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    // 是否延遲載入,讀取的配置項,true:使用 feign 時才建立;false(預設):專案啟動時建立
    if (String.valueOf(false).equals(this.environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
        this.eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
    } else {
        this.lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
    }
}

// org.springframework.cloud.openfeign.FeignClientsRegistrar#eagerlyRegisterFeignClientBeanDefinition
private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes, BeanDefinitionRegistry registry) {
    this.validate(attributes);
    // 構建一個 BeanDefinitionBuilder(genericBeanDefinition 方法把 bealClass 設定為 FeignClientFactoryBean)
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    definition.addPropertyValue("url", this.getUrl((ConfigurableBeanFactory)null, attributes));
    definition.addPropertyValue("path", this.getPath((ConfigurableBeanFactory)null, attributes));
    String name = this.getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = this.getContextId((ConfigurableBeanFactory)null, attributes);
    definition.addPropertyValue("contextId", contextId);
    
    // 省略了 BeanDefinitionBuilder 的賦值操作
    ...
    
    // 根據 beanDefinition 再構建一個 BeanDefinitionHolder
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    // 關聯 BeanDefinitionHolder 和 registry(registry 持有 BeanDefinitionHolder,後續透過 registry 建立物件)
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    
    // 方法呼叫了兩次,但是引數不一樣,一個是建立 Feign 的配置項(Request.Options,超時配置),一個是 Feign
    this.registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
    this.registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
}

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerRefreshableBeanDefinition
private void registerRefreshableBeanDefinition(BeanDefinitionRegistry registry, String contextId, Class<?> beanType,
        Class<?> factoryBeanType) {
    if (isClientRefreshEnabled()) {
        String beanName = beanType.getCanonicalName() + "-" + contextId;
        BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(factoryBeanType);
        definitionBuilder.setScope("refresh");
        definitionBuilder.addPropertyValue("contextId", contextId);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
                beanName);
        // 反射建立物件(前面說了 beanClass 是 FactoryBean)
        definitionHolder = ScopedProxyUtils.createScopedProxy(definitionHolder, registry, true);
        // 註冊 beanDefinition
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }
}

FactoryBean 這個在前面的文章有說過,這是 spring 的知識,工廠方式構建 bean 的方式:1,使用靜態方法;2:使用例項方法;3:使用 FactoryBean。如果是 FactoryBean,最終的物件是 getObject 方法返回的物件作為 bean,最終的代理物件怎麼生成的就不貼原始碼了,程式碼量也不多,看下最終的代理物件長啥樣吧

OpenFeign 服務註冊和呼叫原理

Feign 呼叫流程

就是把容器中 Feign 的代理物件取出來,然後反射執行方法的過程,先對比下剛才放到單例池的代理物件和注入的物件

OpenFeign 服務註冊和呼叫原理

先判斷是否是 Object 的方法,然後執行代理物件的方法,最後傳送一個 http 請求,呼叫鏈如下

OpenFeign 服務註冊和呼叫原理

相關文章