OpenFeign是如何找到标了@FeignClient注解的接口呢?

412 阅读3分钟

OpenFeign的使用步骤

在我们使用 OpenFeign 的时候只需要添加两个注解即可正常使用(添加依赖、在配置文件yml指定了注册中心后)

  • 一个是主启动类上的 @EnableFeignClients
  • 另一个是接口类的 @FeignClient

主启动类

/**
 * 主启动类
 */
@SpringBootApplication
@EnableEurekaClient // 这里的注册中心采用的是 Eureka
@EnableFeignClients
public class OpenFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderByOpenFeignMain80.class,args);
    }
}

FeignClient 接口

@Service
@FeignClient(value = "PROVIDER-8001")
public interface OrderFeignClient {
    
    @GetMapping("payment/hello")
    String hello();
    /**
     * 流程 : provider注册进eureka,eureka中有controller接口的映射信息
     *       此时,消费者可以从eureka中获取相应映射,并通过反射生成代理类实现接口
     *
     *       Feign使用JDK动态代理技术时,需要提前将接口(例如OrderService)带 @RequestMapping/@GetMapping.../... 之类的方法解析出来
     */
}

使用高版本 OpenFeign 的踩坑点

做负载均衡时,低版本的 OpenFeign 集成了 Ribbon 的负载均衡,但高版本的 OpenFeign 把 LoadBalancer 剔除掉了

因此想要做到集群负载均衡的话,要额外添加 LoadBalancer 的依赖

        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- LB负载均衡依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

标注了 @FeignClient 的类是怎么自动注入到 Spring 容器中的?

首先我们来看看主启动类上的注解:@EnableFeignClients

@EnableFeignClients 的定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    //注解的属性省略
}

我们看到了这个注解 Import 了一个注册类:FeignClientsRegistrar

继续点进这个类看看是啥情况

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
}

我们看见了,这个类,实现了 ImportBeanDefinitionRegistrar 接口,我们知道在处理 @Configuration 类时可以通过 @Import 注册其他Spring Bean定义的能力

在上面提到一个疑问:标注了 @FeignClient 的类是怎么自动注入到 Spring 容器中的?是怎么扫描到标注了 @FeignClient 的类的?

我们看看这个FeignClientsRegistrar类的方法,重点是 registerFeignClients() 方法!!!

// 截取了部分代码
// 注:这个源码的版本是 3.1.1,不同的源码可能稍有出入

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 获取 @EnableFeignClients 上的相关属性并用这些属性做一些基本配置Bean的注册
        registerDefaultConfiguration(metadata, registry);
         // 注册Bean
        registerFeignClients(metadata, registry);
    }
 
    // 主要方法
    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
        
        if (clients == null || clients.length == 0) {
            
            // 获取包路径下的扫描器
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set<String> basePackages = getBasePackages(metadata);
            
            for (String basePackage : basePackages) {
                // 将所有 @FeignClient 的接口的 BeanDefinition 拿到
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
        else {
            for (Class<?> clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }

        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface(源码的注释)
                 // 看,源码也说了,这里必须要使用接口,@FeignClient 注解必须在接口上面
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                
                // 根据这些属性和接口来注册 FeignClient Bean
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
 
    // 注册 FeignClient Bean
    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
                                     Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        // 使用FactoryBean,将Bean的具体生成过程收拢到FeignClientFactoryBean之中
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        definition.addPropertyValue("type", className);
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
 
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
 
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        // 将这个使用了 @FeignClient 的接口的工厂Bean的 BeanDefinition 注册到Spring容器中
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
}

所以在 FeignClientRegistrar 类中需要做的就是扫描某些路径的接口类,识别对应的 @FeignClient ,给这些接口类创建代理对象。而为了把这些代理对象注入到Spring 容器中,所以还得借助原始的 Spring 中的 FactoryBean 的能力。

(扫描的路径:配置Spring扫描路径、@EnableFeignClients中配置的路径)

再往后 FeignClientFactoryBean 就是拿到业务接口、然后去构造代理类了。

FeignClientFactoryBean 的底层是使用了 Feign 来实现代理对象的

关于 @FeignClient 如何根据接口实现接口代理类,咱们后面再研究!!!