Feign源码备忘录

前言

Feign作为一个RPC组件,开发中经常会用到,为什么在Class上加一个FeignClient就可以实现远程调用呢?
这得从 @EnableFeignClients 这个注解说起了

我们可以通过这个 RemoteClientService 调用对应的服务


@FeignClient(serviceId = "pms"fallback = RemoteClientService.ForBack.class)
public interface RemoteClientService {
 @GetMapping("/inner/client/clientid")
    OauthClientDetailsDTO getOauthClientDetailsByName(@RequestParam("cilentId") String cilentId);
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] defaultConfiguration() default {};

    Class<?>[] clients() default {};
}

发现他导入了 FeignClientsRegistrar 这个class
这个 class 实现了 ImportBeanDefinitionRegistrar 不明白这个的先百度一下

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
	 
	 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 注册 注解中 定义的默认 Config 
        this.registerDefaultConfiguration(metadata, registry);
        this.registerFeignClients(metadata, registry);
    }
}

这里主要看 registerFeignClients 这个方法
这里主要是 在指定的 packageName 下 扫描 标记了 FeignClient
的Class 然后 调用 registerFeignClient 注册这个实例

 public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        // 过滤 加了 FeignClient 注解的class
        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);
                }
            }
        }

    }

接着看 registerFeignClient 这个方法
通过 生成一个 BeanDefinition 然后 实现类是 FeignClientFactoryBean
然后给 FeignClientFactoryBean 中的字段赋值
值得注意的是
definition.addPropertyValue(“type”, className);
这个type 填充的 className 就是我们编写的 接口 名称
(这里指的一开始提到的 RemoteClientService )
所以 spring 可以根据这个 type 知道我们要注入哪个实现类
最终把 BeanDefinition 添加到Spring容器中


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);
    }

通过这些代码 我们知道 FeignClientFactoryBean 是我们 RemoteClientService 这个接口的 实现工厂类
熟悉 Spring 的朋友应该知道 FactoryBean 和 普通 bean 的区别
区别就是 FactoryBean 获取实例 调用的是 getObject 方法 而不是直接通过
构造方法构建Bean 我们直接看 他的 getObject 方法

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    // 这些参数都是上面构建 BeanDefinition 的时候注入的
    private Class<?> type;
    private String name;
    private String url;
    private String contextId;
    private String path;
    private boolean decode404;
    private ApplicationContext applicationContext;
    private Class<?> fallback;
    private Class<?> fallbackFactory;

	public Object getObject() throws Exception {
        return this.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));
        }
}

通过上面的代码 可以看到 先获取了一个 FeignContext
然后调用 this.feign(context) 初始化 Builder
先看一下 this.feign(context)
构建一个 Builder
然后通过 this.configureFeign(context, builder) 设置这个 builder


protected Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);
        通过 
        Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
        this.configureFeign(context, builder);
        return builder;
    }

    protected void configureFeign(FeignContext context, Builder builder) {

client:
  config:
   	默认配置 
    default:
      connectTimeout: 10000
      readTimeout: 10000
    客户端级别的配置  
    myContextId: 这个ContextId就是我们编写的FeignClient(contextId="myContextId")
      connectTimeout: 10000
      readTimeout: 10000

        FeignClientProperties properties = (FeignClientProperties)this.applicationContext.getBean(FeignClientProperties.class);
        if (properties != null) {
            if (properties.isDefaultToProperties()) {
            这个地方 是 对 builder的一个配置 
                this.configureUsingConfiguration(context, builder);
   先使用 默认 的   Config
       this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
  再使用 各个 contextId 下的配置 实现各个Feign 独立配置 可以参考上面那个图
                this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
            } 

    }

这里比较绕的地方是,this.feign(context) 中 ,通过 FeignContext
获取对应的 Bean 但是 这个 Bean 不是在 当前上下文中创建的
而是 通过 contextId 再获取一个子 AnnotationConfigApplicationContext
然后 再通过 这个子Context 获取对应的Bean

public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = this.getContext(name);
        return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0 ? context.getBean(type) : null;
    }

接着FeignClientFactoryBean .getObject方法 继续往看

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    // 这些参数都是上面构建 BeanDefinition 的时候注入的
    private Class<?> type;
    private String name;
    private String url;
    private String contextId;
    private String path;
    private boolean decode404;
    private ApplicationContext applicationContext;
    private Class<?> fallback;
    private Class<?> fallbackFactory;

	public Object getObject() throws Exception {
        return this.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));
        }
}

可以看到通过 this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url)); 创建了一个实例

 protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
 	留意一下 这里的 Client 的实现类 其实是 LoadBalancerFeignClient
        Client client = (Client)this.getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            Targeter targeter = (Targeter)this.get(context, Targeter.class);
    		调用 这个方法创建 bean 
            return targeter.target(this, builder, context, target);
        } else {
            throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
        }
    }

targeter.target(this, builder, context, target) 最后调用到
Feign.target(Target target) 这个方法

 Feign
 public <T> T target(Target<T> target) {
 	最后创建了一个代理类,而代理类的 执行者是 synchronousMethodHandlerFactory 
            return this.build().newInstance(target);
 }
 
 public Feign build() {
  Factory 的 create方法 创建了 SynchronousMethodHandler
            Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
            ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
            return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
        }
 

看一下 SynchronousMethodHandler做了什么
因为是个一个代理类 主要看他的 invoke 方法
可以发现 执行的逻辑是在 executeAndDecode 方法里
外围方法 设置了 请求配置 和 重试策略等

public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        Options options = this.findOptions(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                return this.executeAndDecode(template, options);
            } catch (RetryableException var9) {
                RetryableException e = var9;

                try {
                    retryer.continueOrPropagate(e);
                } catch (RetryableException var8) {
                    Throwable cause = var8.getCause();
                    if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                        throw cause;
                    }

                    throw var8;
                }

                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }

executeAndDecode 最终调用到 LoadBalancerFeignClient

 Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
        Request request = this.targetRequest(template);
        if (this.logLevel != Level.NONE) {
            this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
        }

        long start = System.nanoTime();

        Response response;
        try {
        这个client 是之前提到的 LoadBalancerFeignClient 执行他的
        execute 方法
            response = this.client.execute(request, options);
            ...省略部分代码
}

LoadBalancerFeignClient 的 execute
这个负载均衡 涉及到 ribbon的代码 在此 就不展开篇幅去讲了

public Response execute(Request request, Options options) throws IOException {
        try {
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
            IClientConfig requestConfig = this.getClientConfig(options, clientName);
            获取一个 FeignLoadBalancer 然后执行 executeWithLoadBalancer
            方法  其实这个方法 就是 通过 LoadBalancer 从Nacos 或者其他注册中心 获取对应的实例 然后 执行请求的逻辑 
            return   ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
        } catch (ClientException var8) {
            IOException io = this.findIOException(var8);
            if (io != null) {
                throw io;
            } else {
                throw new RuntimeException(var8);
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值