Feign源码分析

前言
通过对feign的使用,发现@EnableFeignClients和@FeignClient两个注解就实现了Feign的功能,那就从
@EnableFeignClients注解开始分析Feign的源码
1、EnableFeignClients注解

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

通过 @EnableFeignClients 引入了FeignClientsRegistrar客户端注册类
2、FeignClientsRegistrar注册类

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        this.registerDefaultConfiguration(metadata, registry);
        this.registerFeignClients(metadata, registry);
    }

通过其类结构可知,由于实现了ImportBeanDefinitionRegistrar接口,那么就会自动执行
registerBeanDefinitions()中的注册对象,主要注册的对象类型有两种:
1)、注册缺省配置的配置信息
2)、注册那些添加了@FeignClient的类或接口 : 这也是我们讨论的重点

ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        Object basePackages;
        if (clients != null && clients.length != 0) {
            final Set<String> clientClasses = new HashSet();
            basePackages = new HashSet();
            Class[] var9 = clients;
            int var10 = clients.length;
            for(int var11 = 0; var11 < var10; ++var11) {
                Class<?> clazz = var9[var11];
                ((Set)basePackages).add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        } else {
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = this.getBasePackages(metadata);
        }
        Iterator var17 = ((Set)basePackages).iterator();
        while(var17.hasNext()) {
            String basePackage = (String)var17.next();
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            Iterator var21 = candidateComponents.iterator();
            while(var21.hasNext()) {
                BeanDefinition candidateComponent = (BeanDefinition)var21.next();
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    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 = this.getClientName(attributes);
                    this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                    this.registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

该方法主要是扫描类路径,对所有的FeignClient生成对应的 BeanDefinitio 。同时又调用了
registerClientConfiguration 注册配置的方法,这里是第二处调用。这里主要是将扫描的目录下,
每个项目的配置类加载的容器当中。调用 registerFeignClient 注册对象
其中Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());便是扫描@EnableFeignClients注解
3、注册FeignClient对象

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = this.getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(2);
        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

通过分析可知:最终是向Spring中注册了一个bean,bean的名称就是类或接口的名称(也就是本
例中的FeignService),bean的实现类是FeignClientFactoryBean,其属性设置就是在
@FeignClient中定义的属性。那么下面在Controller中对FeignService的的引入,实际就是引入了
FeignClientFactoryBean 类
4、FeignClientFactoryBean类
对@EnableFeignClients注解的源码进行了分析,了解到其主要作用就是把带有@FeignClient注解的
类或接口用FeignClientFactoryBean类注册到Spring中。

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { 
	public Object getObject() throws Exception { 
		return this.getTarget();
	 }
	 //略
  }

通过 FeignClientFactoryBean 类结构可以发现其实现了FactoryBean类,那么当从
ApplicationContext中获取该bean的时候,实际调用的是其getObject()方法。返回调用getTarget()方法

<T> T getTarget() {
        FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
        Builder builder = this.feign(context);
        if (!StringUtils.hasText(this.url)) {
            if (!this.name.startsWith("http")) {
                this.url = "http://" + this.name;
            } else {
                this.url = this.name;
            }
            this.url = this.url + this.cleanPath();
            return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));
        } else {
            if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
                this.url = "http://" + this.url;
            }
            String url = this.url + this.cleanPath();
            Client client = (Client)this.getOptional(context, Client.class);
            if (client != null) {
                if (client instanceof LoadBalancerFeignClient) {
                    client = ((LoadBalancerFeignClient)client).getDelegate();
                }
                builder.client(client);
            }
            Targeter targeter = (Targeter)this.get(context, Targeter.class);
            return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));
        }
    }

1)、FeignClientFactoryBean实现了FactoryBean的getObject、getObjectType、isSingleton方法;
实现了InitializingBean的afterPropertiesSet方法;实现了ApplicationContextAware的
setApplicationContext方法
2)、getObject调用的是getTarget方法,它从applicationContext取出FeignContext,然后构造
Feign.Builder并设置了logger、encoder、decoder、contract,之后通过configureFeign根据
FeignClientProperties来进一步配置Feign.Builder的retryer、errorDecoder、 request.Options、requestInterceptors、queryMapEncoder、decode404
3)、初步配置完Feign.Builder之后再判断是否需要loadBalance,如果需要则通过loadBalance方法来
设置,不需要则在Client是LoadBalancerFeignClient的时候进行unwrap

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值