openfeign原理

openfeign原理

@EnableFeignClients注解启用Feign客户端,通过@Import注解导入了FeignClientsRegistrar类加载额外的Bean。FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,在Spring启动过程中会调用registerBeanDefinitions方法注册BeanDefinition

//FeignClientsRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    //处理@EnableFeignClients注解上的配置
    registerDefaultConfiguration(metadata, registry);
    //处理@FeignClient对应的接口
    registerFeignClients(metadata, registry);
}

FeignClientsRegistrar#registerFeignClients:

private void registerDefaultConfiguration(AnnotationMetadata metadata,
                                          BeanDefinitionRegistry registry) {
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            name = "default." + metadata.getClassName();
        }
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}

FeignClientsRegistrar#registerFeignClients:

public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {
    //找到@FeignClient标识的接口类
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    Set<String> basePackages;
    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    else {
        final Set<String> clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        for (Class<?> clazz : clients) {
            basePackages.add(ClassUtils.getPackageName(clazz));
            clientClasses.add(clazz.getCanonicalName());
        }
        AbstractClassTestingTypeFilter filter = new 
            AbstractClassTestingTypeFilter() {
            @Override
            protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                return clientClasses.contains(cleaned);
            }
        };
        scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }

    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                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"));
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

FeignClientsRegistrar#registerFeignClient:

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, 
                                 Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    //创建了FeignClientFactoryBean实例
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = 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(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
    // null
    beanDefinition.setPrimary(primary);
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

FeignClientFactoryBean#getObject:

//实现FactoryBean接口getObject方法,返回工厂管理的Bean实例
@Override
public Object getObject() throws Exception {
    return getTarget();
}

//根据指定的数据和上下文信息创建Feign客户端
<T> T getTarget() {
    //获得FeignContext对象
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        //获得Feign客户端
        return (T) loadBalance(builder, context,
                               new HardCodedTarget<>(this.type, this.name, this.url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(this.type, this.name, url));
}

//获得Feign.Builder实例
protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.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));
    // @formatter:on

    configureFeign(context, builder);

    return builder;
}

//使用FeignContext和Feign.Builder对象配置Feign
protected void configureFeign(FeignContext context, Feign.Builder builder) {
    FeignClientProperties properties = this.applicationContext
        .getBean(FeignClientProperties.class);
    if (properties != null) {
        if (properties.isDefaultToProperties()) {
            configureUsingConfiguration(context, builder);
            configureUsingProperties(
                properties.getConfig().get(properties.getDefaultConfig()),
                builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                                     builder);
        }
        else {
            configureUsingProperties(
                properties.getConfig().get(properties.getDefaultConfig()),
                builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                                     builder);
            configureUsingConfiguration(context, builder);
        }
    }
    else {
        configureUsingConfiguration(context, builder);
    }
}

//以负载均衡的方式获取Feign客户端
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    //获取Client对象
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        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?");
}

//默认的Target实现类
class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

}

//Feign#target
public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

//通过反射方式创建Feign对象
public Feign build() {
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler
        .Factory(client, retryer, requestInterceptors, logger,                                             logLevel, decode404, closeAfterDecode, propagationPolicy);
    ParseHandlersByName handlersByName = new ParseHandlersByName
        (contract, options, encoder, decoder, queryMapEncoder,
                                errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(
        handlersByName, invocationHandlerFactory, queryMapEncoder);
}

//使用JDK动态代理的方式创建Target的代理对象
@Override
public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
            continue;
        } else if (Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            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;
}
OpenFeign核心流程

OpenFeign核心流程图

  1. 在Spring项目启动阶段,服务OpenFeign框架会发起一个主动的扫描包的流程;
  2. 从指定目录下扫描并加载所有被@FeignClient注解修饰的接口,然后将这些接口转换成Bean,统一交给Spring来管理;
  3. 然后这些接口会经过MVC Contract协议解析,将方法上的注解解析出来,放到MethodMetadata中;
  4. 根据加载的每个FeignClient接口生成一个动态代理对象,指向一个包含对应方法的MethodHandler的HashMap。生成的动态代理对象会被添加到Spring容器中,并注入到对应的服务中;
  5. 服务A调用接口,准备发起远程调用;
  6. 从动态代理对象Proxy中找到一个MethodHandler实例,生成Request,包含欧服务的请求URL;
  7. 经过负载均衡算法找到一个服务的IP地址,拼接处请求的URL;
  8. 服务B处理服务A发起的远程调用请求,执行业务逻辑后,返回响应给服务A。
核心思想
  1. OpenFeign扫描带有@FeignClient注解的接口,然后为其生成一个动态代理;
  2. 动态代理中包含有接口方法的MethodHandlerMethodHandler中又包含经过MVC Contract解析注解后的元数据;
  3. 发起请求时,MethodHandler会生成一个Request
  4. 负载均衡器Ribbon会从服务列表中选取一个Server,拿到对应的IP地址后,拼接成最后的URL,就可以发起远程服务调用了。
总结
  1. OpenFeign是声明式的HTTP客户端,可以像访问本地方法一样进行远程调用;
  2. 提供了HTTP请求的模板,编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息;
  3. 整合了负载均衡组件Ribbon和服务熔断组件Hystrix
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
【2021年,将Spring全家桶系列课程进行Review,修复顺序等错误。进入2022年,将Spring的课程进行整理,整理为案例精讲的系列课程,并新增高级的Spring Security等内容,通过手把手一步步教你从零开始学会应用Spring,课件将逐步进行上传,敬请期待】 本课程是Spring案例精讲课程的第四部分Spring Cloud,Spring案例精讲课程以真实场景、项目实战为导向,循序渐进,深入浅出的讲解Java网络编程,助力您在技术工作中更进一步。 本课程聚焦Spring Cloud的核心知识点:注册中心、服务提供者与消费者、服务的调用OpenFeign、Hystrix监控、服务网关gateway、消息驱动的微服务Spring Cloud Stream、分布式集群、分布式配置中心的案例介绍, 快速掌握Spring Cloud的核心知识,快速上手,为学习及工作做好充足的准备。 由于本课程聚焦于案例,即直接上手操作,对于Spring的原理等不会做过多介绍,希望了解原理等内容的需要通过其他视频或者书籍去了解,建议按照该案例课程一步步做下来,之后再去进一步回顾原理,这样能够促进大家对原理有更好的理解。【通过Spring全家桶,我们保证你能收获到以下几点】 1、掌握Spring全家桶主要部分的开发、实现2、可以使用Spring MVC、Spring Boot、Spring Cloud及Spring Data进行大部分的Spring开发3、初步了解使用微服务、了解使用Spring进行微服务的设计实现4、奠定扎实的Spring技术,具备了一定的独立开发的能力  【实力讲师】 毕业于清华大学软件学院软件工程专业,曾在Accenture、IBM等知名外企任管理及架构职位,近15年的JavaEE经验,近8年的Spring经验,一直致力于架构、设计、开发及管理工作,在电商、零售、制造业等有丰富的项目实施经验  【本课程适用人群】如果你是一定不要错过!  适合于有JavaEE基础的,如:JSP、JSTL、Java基础等的学习者没有基础的学习者跟着课程可以学习,但是需要补充相关基础知识后,才能很好的参与到相关的工作中。 【Spring全家桶课程共包含如下几门】 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐江小鱼

知识创造财富,期待您的慷慨解囊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值