精华内容
下载资源
问答
  • SpringCloudFeign原理剖析

    2021-03-06 00:34:03
    Feign是什么?简单来说,feign是用在微服务中,各个微服务间的调用。它是通过声明式的方式来定义接口,而不用实现接口。接口的实现由它通过spring bean的动态注册来实现的。fei...
    Feign是什么?

    简单来说,feign是用在微服务中,各个微服务间的调用。它是通过声明式的方式来定义接口,而不用实现接口。接口的实现由它通过spring bean的动态注册来实现的。

    feign让服务间的调用变得简单,不用各个服务去处理http client相关的逻辑。并且它里面集成了ribbon用来做负载均衡,通过集成了hystrix用来做服务熔断和降级。

    在feign的使用中,我们主要用到它的两个注解,下面一一来说明。

    注解

    1、@EnableFeignClients

    • 用于表示该客户端开启Feign方式调用

    • 创建一个关于FeignClient的工厂Bean,这个工厂Bean会通过@FeignClient收集调用信息(服务、接口等),创建动态代理对象

    2、@FeignClient

    负责标识一个用于业务调用的Client,给FactoryBean提供创建代理对象,提供基础数据(类名、方法、服务名、URI等),作用是提供这些静态配置

    实现原理

    一般对于一个spring boot服务来说,如果要使用feign,都会这样定义:

    @Configuration
    @EnableScheduling
    @EnableDiscoveryClient
    @EnableFeignClients(value = {"com.ts"})
    @MapperScan(value = {"com.ts.xx.xx"}, nameGenerator = FullBeanNameGenerator.class)
    public class xxxxAutoConfig {
    }
    

    这里就使用到了上面说的EnableFeignClients这个注解,这个注解有什么用呢?我们进入该注解:

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

    可以看到该注解导入了FeignClientsRegistrar类,我们进入其中:

    class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {}
      @Override
      public void registerBeanDefinitions(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
      }  
    

    整个过程大概就是,通过配置类,或者package路径做扫描,收集FeignClient的静态信息,每个Client会把他的基本信息,类名、方法、服务名等绑定到FactoryBean上,这样就就具备了生成一个动态代理类的基本条件。

    这里穿插2个知识点

    1、spring bean的动态注册

    在spring中有两类bean:

    • 普通的bean:通过xml配置或者注解配置

    • 工厂bean:也是一个Bean,这个Bean我们业务中不会直接用到,它主要是用于生成其他的一些Bean,内部进行了高度封装,非常容易实现配置化的管理,屏蔽了实现细节。动态注册是解决什么问题,根据客户端的配置动态的,也就是可以按需做bean的注册。

    1.1 使用方式

    实现ImportBeanDefinitionRegistrar接口,重点实现registerBeanDefinitions方法,该接口需要配合@Configuration@Import注解,

    • @Configuration定义Java格式的Spring配置文件,

    • @Import注解导入实现了ImportBeanDefinitionRegistrar接口的类。这也就是上面我们看到的FeignClientsRegistrar

    那feign为什么要这样做呢?因为需要生成不同的代理类的实现bean。

    1.2 实现原理

    所有实现了该接口的类的都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖它的bean初始化的,也能被aop、validator等机制处理。

    2、FactoryBean

    FactoryBean的特殊之处在于它可以向容器中注册两个Bean,一个是它本身,一个是FactoryBean.getObject()方法返回值所代表的Bean。

    我们知道:在Spring容器启动阶段,会调用到refresh()方法,在refresh()中有调用了finishBeanFactoryInitialization()方法,最终会调用到beanFactory.preInstantiateSingletons()方法。 

    getObjectForBeanInstance()方法中会先判断bean是不是FactoryBean,如果不是,就直接返回Bean。如果是FactoryBean,且name是以&符号开头,那么表示的是获取FactoryBean的原生对象,也会直接返回。如果name不是以&符号开头,那么表示要获取FactoryBean中getObject()方法返回的对象。会先尝试从FactoryBeanRegistrySupport类的factoryBeanObjectCache这个缓存map中获取,如果缓存中存在,则返回,如果不存在,则去调用getObjectFromFactoryBean()方法。

    getObjectFromFactoryBean()方法中,主要是通过调用doGetObjectFromFactoryBean()方法得到bean,然后对bean进行处理,最后放入缓存。而且还会针对单例bean和非单例bean做区分处理,对于单例bean,会在创建完后,将其放入到缓存中,非单例bean则不会放入缓存,而是每次都会重新创建。

    注册FeignClient

    我们回到FeignClientsRegistrar类中,下面接着看下注册FeignClient的实现,代码如下:

    public void registerFeignClients(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            //创建一个类扫描器
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
    
    
            Set<String> basePackages;
            //获取EnableFeignClients注解包含的属性
            Map<String, Object> attrs = metadata
                    .getAnnotationAttributes(EnableFeignClients.class.getName());
            //这是一个FeignClient注解过滤器
            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) {
            //先扫描出含有@FeignClient注解的类
                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"));
                        //循环对使用@FeignClient注解的不同的类,做工厂bean注册
                        registerFeignClient(registry, annotationMetadata, attributes);
                    }
                }
            }
        }
    
    
        private void registerFeignClient(BeanDefinitionRegistry registry,
                AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
            //含有@FeignClient该类的名称
            String className = annotationMetadata.getClassName();
            //创建一个BeanDefinitionBuilder,内含AbstractBeanDefinition,指定待创建的Bean的名字是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);
            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 = name + "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);
        }
        //BeanDefinitionReaderUtils.registerBeanDefinition
        public static void registerBeanDefinition(
                BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
                throws BeanDefinitionStoreException {
    
    
            // Register bean definition under primary name.
            String beanName = definitionHolder.getBeanName();
            //BeanDefinitionRegistry.registerBeanDefinition实现具体的注册,会告知他需要注册的类名、以及AbstractBeanDefinition
            registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
    
            // Register aliases for bean name, if any.
            String[] aliases = definitionHolder.getAliases();
            if (aliases != null) {
                for (String alias : aliases) {
                    registry.registerAlias(beanName, alias);
                }
            }
        }
    

    创建代理类

    在上面提到的FactoryBean,可以看到FeignClientFactoryBean继承了它,通过调用实现类的getObject完成代理类的创建:

    @Override
      public Object getObject() throws Exception {
        return getTarget();
      }
      <T> T getTarget() {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);
        //先校验基础属性,基础属性是在FeignClientsRegistrar中给动态Bean,添加属性addPropertyValue时候赋值的
        //URL为空,则使用http://serviceName的方式拼接
        if (!StringUtils.hasText(this.url)) {
          String url;
          if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
          }
          else {
            url = this.name;
          }
          url += cleanPath();
          //创建动态的代理对象,采用JDK动态代理
          return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
              this.name, url));
        }
        //含有url,也就是FeignClient的注解接口里是一个绝对地址
        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));
      }
    

    从上面代码可以知道,如果用户在FeignClient的注解中直接使用了URL,这种方式一般用于调试环境,直接指定一个服务的绝对地址,这种情况下不会走负载均衡,走默认的Client,代码如下:

    @Override
        public Response execute(Request request, Options options) throws IOException {
          HttpURLConnection connection = convertAndSend(request, options);
          return convertResponse(connection).toBuilder().request(request).build();
        }
    

    如果用户在FeignClient中使用了seriveName,那么请求地址将会是http://serviceName,这种情况下是需要走负载均衡的,通过如下代码发现Feign的负载均衡也是基于Ribbon实现:

    public Response execute(Request request, Request.Options options) throws IOException {
        try {
          URI asUri = URI.create(request.url());
          String clientName = asUri.getHost();
          URI uriWithoutHost = cleanUrl(request.url(), clientName);
          FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
              this.delegate, request, uriWithoutHost);
    
    
          IClientConfig requestConfig = getClientConfig(options, clientName);
          return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
              requestConfig).toResponse();
        }
        catch (ClientException e) {
          IOException io = findIOException(e);
          if (io != null) {
            throw io;
          }
          throw new RuntimeException(e);
        }
      }
    

    相关配置

    1、服务配置

    大多数情况下,我们对于服务调用的超时时间可能会根据实际服务的特性做 一 些调整,所以仅仅依靠默认的全局配置是不行的。

    在使用SpringCloud Feign的时候,针对各个服务客户端进行个性化配置的方式与使用SpringCloud Ribbon时的配置方式是 一 样的, 都采用. ribbon.key=value 的格式进行 设置。

    在定义Feign客户端的时候, 我们使用了@FeignClient注解。在初始化过程中,SpringCloud Feign会根据该注解的name属性或value属性指定的服务名, 自动创建一 个同名的Ribbon客户端。

    也就是说,在之前的示例中,使用@FeignClient(value= "cloud-provider")来创 建 Feign 客 户 端 的 时 候 , 同时也创建了一个 名为cloud-provider的Ribbon客户端。既然如此, 我们就可以使用@FeignClient注解中的name或value属性值来设置对应的Ribbon参数, 比如:

    cloud-provider.ribbon.ConnectTimeout = 500 //请求连接的超时时间。
    cloud-provider.ribbon.ReadTimeout = 2000  //请求处理的超时时间。
    cloud-provider.ribbon.OkToRetryOnAllOperations = true //对所有操作请求都进行重试。
    cloud-provider.ribbon.MaxAutoRetriesNextServer = 2 //切换实例的重试次数。
    cloud-provider.ribbon.MaxAutoRetries = 1 //对当前实例的重试次数。
    

    2、日志配置

    Spring Cloud Feign 在构建被 @FeignClient 注解修饰的服务客户端时,会为每 一 个客户端都创建 一 个 feign.Logger 实例,我们可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。

    可以在 application.properties 文件中使用 logging.level. 的参数配置格式来开启指定 Feign 客户端的 DEBUG 日志, 其中 为 Feign 客户端定义接口的完整路径, 比如针对本文中我们实现的 HelloService 可以按如下配置开启:

    logging.level.com.wuzz.demo.HelloService = DEBUG
    

    但是, 只是添加了如上配置, 还无法实现对 DEBUG 日志的输出。这时由于 Feign 客户端默认的 Logger.Level 对象定义为 NONE 级别, 该级别不会记录任何 Feign 调用过程中的信息, 所以我们需要调整它的级别, 针对全局的日志级别, 可以在应用主类中直接加入 Logger.Level 的 Bean 创建, 具体如下:

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    

    或者添加个配置类:

    @Configuration
    public class FullLogConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    @FeignClient(name = "cloud-provider", configuration = FullLogConfiguration.class)
    public interface TestService {
        
    }
    

    3、服务降级

    根据目标接口,创建一个实现了FallbackFactory的类

    @Component
    public class HystrixClientService implements FallbackFactory<ClientService> {
        @Override
        public ClientService create(Throwable throwable) {
            return new ClientService() {
                @Override
                public String test() {
                    return "test sdfsdfsf";
                }
            };
        }
    }
    

    在目标接口上的@FeignClient中添加fallbackFactory属性值

    @FeignClient(value ="cloud-provider", fallbackFactory = HystrixClientService.class)
    public interface ClientService {
    
    
        @RequestMapping(value ="/test",method= RequestMethod.GET)
        String test() ;
    }
    

    修改 application.yml ,添加一下

    feign:
       hystrix:
        enabled: true
    
    
    
    展开全文
  • Spring Cloud Feign原理详解

    千次阅读 2019-05-05 16:17:37
    2.Open Feign vs Spring Cloud Feign 2.1.OpenFeign 2.2.Spring Cloud Open Feign 3.Spring Cloud Feign 的使用 4.Spring Cloud Feign 的源码解析 1.什么是Feign? Feign 的初衷是:feign makes writing java.....

    目录

    1.什么是Feign?

    2.Open Feign vs Spring Cloud Feign

    2.1.OpenFeign

    2.2.Spring Cloud Open Feign 

    3.Spring Cloud Feign 的使用

    4.Spring Cloud Feign 的源码解析


    1.什么是Feign?

    Feign 的初衷是:feign makes writing java http clients easier ,可以理解为一个Http Client。

    只不过这个http client 对http 请求进行了一个封装。后面我们会讲到它的一个工作方式就是处理注解,封装参数,放入到一个Http请求模板,并能解析返回的结果。

    2.Open Feign vs Spring Cloud Feign

    2.1.OpenFeign

    OpenFeign 是最原始,最早的feign。与Spring 无关。就是一个Java的组件,封装了对http请求和响应的处理。

    它的具体demo可以参考 Readme.txt

    2.2.Spring Cloud Open Feign 

    Spring Cloud 中的微服务都是以Http 接口的形式向外提供服务。

    提供Http 服务的形式有多种:

    • JDK 原生的URLConnction
    • Apache 的HttpClient
    • Spring 的RestTemplate

    Spring Cloud 对Feign 也进行了增强,直接支持Hystrix 和 Ribbon,也支持SpringMVC 的注解。这样使得Feign 的使用非常方便。

    3.Spring Cloud Feign 的使用

    在Spring Cloud 1.XX 系列版本中,feign 没有被单独拿出来,而是放在spring-cloud-netflix 下面。依赖的pom是:

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>

    而到了Spring Cloud 2.XX 系列版本后,feign 成为了Spring Cloud 下的一级项目,项目地址是:

    https://spring.io/projects/spring-cloud-openfeign

    依赖的pom是:

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    一定要注意的版本的兼容性,否则项目会启动不了。很多错误都有可能是这种版本不兼容导致的。

    现在的版本兼容状况可以参考:https://spring.io/projects/spring-cloud

    Table 1. Release train Spring Boot compatibility
    Release TrainBoot Version

    Greenwich

    2.1.x

    Finchley

    2.0.x

    Edgware

    1.5.x

    Dalston

    1.5.x

    上面的两个pom中,都没有写明version,这是因为使用Spring Cloud 都会集成dependencyManagement。这里会指定Spring-Cloud的版本。那它下面的组件版本就会和它保持一致

        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR2</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

    a.首先要在主类上增加注解@EnableFeignClients

    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    @EnableEurekaClient
    @EnableFeignClients
    public class ConsumerApp
    {
        public static void main( String[] args )
        {
            SpringApplication.run(ConsumerApp.class,args);
        }
    }

    b.声明一个调用Feign的接口,可以指定服务,接口参数,接口地址,请求方法等

    @FeignClient("producer-1")
    public interface ConsumerFeignClient {
    
        @GetMapping("/getProducerUser")
        String getProducerUser();
    }

    c.服务提供者接口编写

        @GetMapping("/getProducerUser")
        public String getProducerUser(){
            log.info("8004 producer-1 getUser");
            return "8004 producer-1";
        }

    d.将接口注入到调用处使用

        @Autowired
        private ConsumerFeignClient consumerFeignClient;
    
        @GetMapping("/getProducerUser")
        public String getProducerUser(){
            log.info("consumer getProducerUser");
            return consumerFeignClient.getProducerUser();
        }

    这样就可以完成Feign 的整个调用了。

    可以看到,Spring Cloud Feign 是从 Open Feign发展而来,又集成了很多自己的东西,比如对@RequestMapping,@GetMapping等的支持。

    另外需要注意:

    Open Feign 模式是开启Retry(重试)机制的,而Spring Cloud Feign 默认是不开启Retry 的,但支持配置打开。

    4.Spring Cloud Feign 的源码解析

    Feign的原理步骤可以理解为:

    a.通过主类上的EnableFeignClients 注解开启FeignClient;

    b.根据Feign 的规则实现接口,并加上FeignClient注解,供调用的地方注入调用;

    c.程序启动后,会扫描所有FeignClient 注解的类,并将这些信息注入到IOC 容器中;

    d.当b中接口被调用时,通过jdk代理,以及反射(Spring处理注解的方式),来生成具体的RequestTemplate

    e.RequestTemplate 生成Reqest

    f.Request 交给httpclient处理,这里的httpclient 可以是OkHttp,也可以是HttpUrlConnection 或者HttpClient

    g.最后Client被封装到LoadBalanceClient类,这个类结合Ribbon 实现负载均衡

    参考:

    深入理解Feign之源码解析

    feign client 浅谈

    快速使用Spring Cloud Feign作为客户端调用服务提供者

    展开全文
  • 目录SpringCloud 中 Feign 核心原理Feign远程调用的基本流程Feign 远程调用的重要组件Feigh 远程调用的执行流程 SpringCloud 中 Feign 核心原理 如果不了解 SpringCloud 中 Feign 核心原理,不会真正的了解 Spring...

    SpringCloud 中 Feign 核心原理

    如果不了解 SpringCloud 中 Feign 核心原理,不会真正的了解 SpringCloud 的性能优化和配置优化,也就不可能做到真正掌握 SpringCloud。

    本章从Feign 远程调用的重要组件开始,图文并茂的介绍 Feigh 远程调用的执行流程、Feign 本地 JDK Proxy 实例的创建流程,彻底的为大家解读 SpringCloud 的核心知识。使得广大的工程师不光做到知其然,更能知其所以然。


    Feign远程调用的基本流程

    Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示。
    在这里插入图片描述
    从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

    Feign 远程调用的重要组件

    在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

    为了清晰的介绍SpringCloud中Feign运行机制和原理,在这里,首先为大家梳理一下Feign中几个重要组件。

    远程接口的本地JDK Proxy代理实例

    远程接口的本地JDK Proxy代理实例,有以下特点:

    (1)Proxy代理实例,实现了一个加 @FeignClient 注解的远程调用接口;

    (2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP 请求;

    (3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者。

    下面以一个简单的远程服务的调用接口 DemoClient 为例,具体介绍一下远程接口的本地JDK Proxy代理实例的创建过程。

    DemoClient 接口,有两个非常简单的远程调用抽象方法:一个为hello() 抽象方法,用于完成远程URL “/api/demo/hello/v1”的HTTP请求;一个为 echo(…) 抽象方法,用于完成远程URL “/api/demo/echo/{word}/v1”的HTTP请求。具体如下图所示。

    在这里插入图片描述
    DemoClient 接口代码如下:

    package com.crazymaker.springcloud.demo.contract.client;
    //…省略import
    
    @FeignClient(
            value = "seckill-provider", path = "/api/demo/",
            fallback = DemoDefaultFallback.class)
    public interface DemoClient {
    
        /**
         * 测试远程调用
         *
         * @return hello
         */
        @GetMapping("/hello/v1")
        Result<JSONObject> hello();
    
    
        /**
         * 非常简单的一个 回显 接口,主要用于远程调用
         *
         * @return echo 回显消息
         */
        @RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET)
        Result<JSONObject> echo(
                @PathVariable(value = "word") String word);
    
    }
    

    注意,上面的代码中,在DemoClient 接口上,加有@FeignClient 注解。也即是说,Feign在启动时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。

    如何使用呢?可以通过@Resource注解,按照类型匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给需要的成员变量。

    DemoClient的 本地JDK Proxy 代理实例的使用的代码如下:

    package com.crazymaker.springcloud.user.info.controller;
    //…省略import
    @Api(value = "用户信息、基础学习DEMO", tags = {"用户信息、基础学习DEMO"})
    @RestController
    @RequestMapping("/api/user")
    public class UserController {
    
        @Resource
    DemoClient demoClient;  //装配 DemoClient 的本地代理实例
    
        @GetMapping("/say/hello/v1")
        @ApiOperation(value = "测试远程调用速度")
        public Result<JSONObject> hello() {
            Result<JSONObject> result = demoClient.hello();
            JSONObject data = new JSONObject();
            data.put("others", result);
            return Result.success(data).setMsg("操作成功");
        }
    //…
    }
    
    

    DemoClient的本地JDK Proxy代理实例的创建过程,比较复杂,稍后作为重点介绍。先来看另外两个重要的逻辑组件。


    调用处理器 InvocationHandler

    大家知道,通过 JDK Proxy 生成动态代理类,核心步骤就是需要定制一个调用处理器,具体来说,就是实现JDK中位于java.lang.reflect 包中的 InvocationHandler 调用处理器接口,并且实现该接口的 invoke(…) 抽象方法。

    为了创建Feign的远程接口的代理实现类,Feign提供了自己的一个默认的调用处理器,叫做 FeignInvocationHandler 类,该类处于 feign-core 核心jar包中。当然,调用处理器可以进行替换,如果Feign与Hystrix结合使用,则会替换成 HystrixInvocationHandler 调用处理器类,类处于 feign-hystrix 的jar包中。

    在这里插入图片描述

    默认的调用处理器 FeignInvocationHandler

    默认的调用处理器 FeignInvocationHandler 是一个相对简单的类,有一个非常重要Map类型成员 dispatch 映射,保存着远程接口方法到MethodHandler方法处理器的映射。

    以前面示例中DemoClient 接口为例,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员的内存结构图如图3所示。
    在这里插入图片描述
    默认的调用处理器 FeignInvocationHandle,在处理远程方法调用的时候,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器,然后交给MethodHandler 完成实际的HTTP请求和结果的处理。前面示例中的 DemoClient 远程调用接口,有两个远程调用方法,所以,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员,有两个Key-Value键值对。

    FeignInvocationHandler的关键源码,节选如下:

    package feign;
    //...省略import
    
    public class ReflectiveFeign extends Feign {
    
      //...
    
      //内部类:默认的Feign调用处理器 FeignInvocationHandler
      static class FeignInvocationHandler implements InvocationHandler {
    
        private final Target target;
        //方法实例对象和方法处理器的映射
        private final Map<Method, MethodHandler> dispatch;
    
        //构造函数    
        FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
          this.target = checkNotNull(target, "target");
          this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }
    
        //默认Feign调用的处理
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          //...
    	  //首先,根据方法实例,从方法实例对象和方法处理器的映射中,
    	  //取得 方法处理器,然后,调用 方法处理器 的 invoke(...) 方法
             return dispatch.get(method).invoke(args);
        }
        //...
      }
     
    

    源码很简单,重点在于invoke(…)方法,虽然核心代码只有一行,但是其功能是复杂的:

    (1)根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器;

    (2)调用MethodHandler方法处理器的 invoke(…) 方法,完成实际的HTTP请求和结果的处理。

    补充说明一下:MethodHandler 方法处理器,和JDK 动态代理机制中位于 java.lang.reflect 包的 InvocationHandler 调用处理器接口,没有任何的继承和实现关系。MethodHandler 仅仅是Feign自定义的,一个非常简单接口。


    **方法处理器 MethodHandler**

    Feign的方法处理器 MethodHandler 是一个独立的接口,定义在 InvocationHandlerFactory 接口中,仅仅拥有一个invoke(…)方法,源码如下:

    //定义在InvocationHandlerFactory接口中
    public interface InvocationHandlerFactory {
      //…
    
     //方法处理器接口,仅仅拥有一个invoke(…)方法
      interface MethodHandler {
        //完成远程URL请求
        Object invoke(Object[] argv) throws Throwable;
      }
    //...
    }
    
    

    MethodHandler 的invoke(…)方法,主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。Feign提供了默认的 SynchronousMethodHandler 实现类,提供了基本的远程URL的同步请求处理。有关 SynchronousMethodHandler类以及其与MethodHandler的关系,大致如图4所示。

    在这里插入图片描述
    为了彻底了解方法处理器,来读一下 SynchronousMethodHandler 方法处理器的源码,大致如下:

    package feign;
    //…..省略import
    final class SynchronousMethodHandler implements MethodHandler {
      //…
      // 执行Handler 的处理
    public Object invoke(Object[] argv) throws Throwable {
            RequestTemplate requestTemplate = this.buildTemplateFromArgs.create(argv);
            Retryer retryer = this.retryer.clone();
    
            while(true) {
                try {
                    return this.executeAndDecode(requestTemplate);
                } catch (RetryableException var5) {
                   //…省略不相干代码
                }
            }
    }
    
      //执行请求,然后解码结果
    Object executeAndDecode(RequestTemplate template) throws Throwable {
            Request request = this.targetRequest(template);
            long start = System.nanoTime();
            Response response;
            try {
                response = this.client.execute(request, this.options);
                response.toBuilder().request(request).build();
            }
    }
    }
    

    SynchronousMethodHandler的invoke(…)方法,调用了自己的executeAndDecode(…) 请求执行和结果解码方法。该方法的工作步骤:

    (1)首先通 RequestTemplate 请求模板实例,生成远程URL请求实例 request;

    (2)然后用自己的 feign 客户端client成员,excecute(…) 执行请求,并且获取 response 响应;

    (3)对response 响应进行结果解码。


    Feign 客户端组件 feign.Client

    客户端组件是Feign中一个非常重要的组件,负责端到端的执行URL请求。其核心的逻辑:发送request请求到服务器,并接收response响应后进行解码。

    feign.Client 类,是代表客户端的顶层接口,只有一个抽象方法,源码如下:

    package feign;
    
    /**客户端接口
     * Submits HTTP {@link Request requests}. 
    Implementations are expected to be thread-safe.
     */
    public interface Client {
      //提交HTTP请求,并且接收response响应后进行解码
      Response execute(Request request, Options options) throws IOException;
    
    }
    
    

    由于不同的feign.Client 实现类,内部完成HTTP请求的组件和技术不同,故,feign.Client 有多个不同的实现。这里举出几个例子:

    (1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;

    (2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;

    (3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类。

    (4)LoadBalancerFeignClient 类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。

    此外,还有一些特殊场景使用的feign.Client客户端实现类,也可以定制自己的feign.Client实现类。下面对上面几个常见的客户端实现类,进行简要介绍。

    在这里插入图片描述
    一:Client.Default类:

    作为默认的Client 接口的实现类,在Client.Default内部使用JDK自带的HttpURLConnnection类实现URL网络请求。

    在这里插入图片描述
    在JKD1.8中,虽然在HttpURLConnnection 底层,使用了非常简单的HTTP连接池技术,但是,其HTTP连接的复用能力,实际是非常弱的,性能当然也很低。具体的原因,参见后面的“SpringCloud与长连接的深入剖析”专题内容。


    二:ApacheHttpClient类

    ApacheHttpClient 客户端类的内部,使用 Apache HttpClient开源组件完成URL请求的处理。

    从代码开发的角度而言,Apache HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口。既提高了开发的效率,也方便提高代码的健壮性。

    从性能的角度而言,Apache HttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。关于带有连接池Apache HttpClient的性能提升倍数,具体可以参见后面的对比试验。

    ApacheHttpClient 类处于 feign-httpclient 的专门jar包中,如果使用,还需要通过Maven依赖或者其他的方式,倒入配套版本的专门jar包。

    三:OkHttpClient类

    OkHttpClient 客户端类的内部,使用OkHttp3 开源组件完成URL请求处理。OkHttp3 开源组件由Square公司开发,用于替代HttpUrlConnection和Apache HttpClient。由于OkHttp3较好的支持 SPDY协议(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。),从Android4.4开始,google已经开始将Android源码中的 HttpURLConnection 请求类使用OkHttp进行了替换。也就是说,对于Android 移动端APP开发来说,OkHttp3 组件,是基础的开发组件之一。

    四:LoadBalancerFeignClient 类

    LoadBalancerFeignClient 内部使用了 Ribben 客户端负载均衡技术完成URL请求处理。在原理上,简单的使用了delegate包装代理模式:Ribben负载均衡组件计算出合适的服务端server之后,由内部包装 delegate 代理客户端完成到服务端server的HTTP请求;所封装的 delegate 客户端代理实例的类型,可以是 Client.Default 默认客户端,也可以是 ApacheHttpClient 客户端类或OkHttpClient 高性能客户端类,还可以其他的定制的feign.Client 客户端实现类型。

    LoadBalancerFeignClient 负载均衡客户端实现类,具体如下图所示。

    在这里插入图片描述


    Feigh 远程调用的执行流程

    由于Feign远程调用接口的JDK Proxy实例的InvokeHandler调用处理器有多种,导致Feign远程调用的执行流程,也稍微有所区别,但是远程调用执行流程的主要步骤,是一致的。这里主要介绍两类JDK Proxy实例的InvokeHandler调用处理器相关的远程调用执行流程:

    (1)与 默认的调用处理器 FeignInvocationHandler 相关的远程调用执行流程;

    (2)与 Hystrix调用处理器 HystrixInvocationHandler 相关的远程调用执行流程。

    介绍过程中,还是以前面的DemoClient的JDK Proxy远程动态代理实例的执行过程为例,演示分析Feigh远程调用的执行流程。

    ** FeignInvocationHandler 相关的远程调用执行流程**

    FeignInvocationHandler是默认的调用处理器,如果不对Feign做特殊的配置,则Feign将使用此调用处理器。结合前面的DemoClient的JDK Proxy远程动态代理实例的hello()远程调用执行过程,在这里,详细的介绍一下与 FeignInvocationHandler 相关的远程调用执行流程,大致如下图所示。

    在这里插入图片描述
    整体的远程调用执行流程,大致分为4步,具体如下:

    第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。

    前文讲到,Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。

    然后,在本实例的UserController 调用代码中,通过@Resource注解,按照类型或者名称进行匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给@Resource注解所在的成员变量,本实例的成员变量的名称为 demoClient。

    在需要代进行hello()远程调用时,直接通过 demoClient 成员变量,调用JDK Proxy动态代理实例的hello()方法。

    第2步:执行 InvokeHandler 调用处理器的invoke(…)方法

    前面讲到,JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。

    通过前面 FeignInvocationHandler 调用处理器的详细介绍,大家已经知道,默认的调用处理器 FeignInvocationHandle,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射。FeignInvocationHandle 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。

    所以在第2步中,FeignInvocationHandle 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。

    第3步:执行 MethodHandler 方法处理器的invoke(…)方法

    通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,大家都知道,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。

    feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。

    第4步:通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果

    如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。

    如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果。

    通过以上四步,应该可以清晰的了解到了 SpringCloud中的 feign 远程调用执行流程和运行机制。

    实际上,为了简明扼要的介绍清楚默认的调用流程,上面的流程,实际上省略了一个步骤:第3步,实际可以分为两小步。为啥呢? SynchronousMethodHandler 并不是直接完成远程URL的请求,而是通过负载均衡机制,定位到合适的远程server 服务器,然后再完成真正的远程URL请求。换句话说,SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是 LoadBalancerFeignClient 客户端负载均衡类型。 因此,上面的第3步,如果进一步细分话,大致如下:(1)首先通过 SynchronousMethodHandler 内部的client实例,实质为负责客户端负载均衡 LoadBalancerFeignClient 实例,首先查找到远程的 server 服务端;(2) 然后再由LoadBalancerFeignClient 实例内部包装的feign.Client.Default 内部类实例,去请求server端服务器,完成URL请求处理。

    最后,说明下,默认的与 FeignInvocationHandler 相关的远程调用执行流程,在运行机制以及调用性能上,满足不了生产环境的要求,为啥呢? 大致原因有以下两点:

    (1) 没有远程调用过程中的熔断监测和恢复机制;

    (2) 也没有用到高性能的HTTP连接池技术。

    转自:https://www.cnblogs.com/crazymakercircle/p/11965726.html

    展开全文
  • SpringCloud原理feign

    2021-08-27 13:55:57
    这个是原生feign的调用过程,总的来说分为2部 一个是 客户端的封装,一个调用方法的封装 Spring Cloud Feign原理解析 我们前面看了原生的feign之后呢?对于Spring Cloud的Feign的话理解起来就很简单了,我们知道...

    前言

    文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger
    种一棵树最好的时间是十年前,其次是现在
    我知道很多人不玩qq了,但是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群,群聊号码:549684836 鼓励大家在技术的路上写博客

    絮叨

    前面一节我们学习了一下eureka,我们来回顾一下,首先它是一个cs架构,分为客户端和服务端,

    客户端 也分为 生成者和消费者,也就是服务提供方和服务消费方,具体客户端的作用如下

    • 当客户端启动的时候向服务端注册当前服务

    • 并和服务端维持心跳,用的是后台线程

    • 拉取服务端的各个节点集合,然后定时更新服务的信息到本地,因为客户端也是会缓存服务节点信息的

    • 当服务挂掉的时候,监听shutdown 然后上报自己挂掉的状态给服务端

    服务端

    • 启动后,从其他节点获取服务注册信息。

    • 运行过程中,定时运行evict任务,剔除没有按时renew的服务(包括非正常停止和网络故障的服务)。

    • 运行过程中,接收到的register、renew、cancel请求,都会同步至其他注册中心节点,分布式数据同步(AP)

    • 运行过程中,自我保护机制。等等

    • SpringCloud原理之eureka

    什么是Feign

    Feign是一种声明式、模板化的HTTP客户端(仅在Application Client中使用)。声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求。Spring Cloud的声明式调用, 可以做到使用 HTTP请求远程服务时能就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。Feign的应用,让Spring Cloud微服务调用像Dubbo一样,Application Client直接通过接口方法调用Application Service,而不需要通过常规的RestTemplate构造请求再解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。

    Feign是声明性Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用HttpMessageConvertersSpring Web中默认使用的注释。当使用Feign时,Spring Cloud集成了Ribbon和Eureka以提供负载平衡的http客户端。

    使用Feign开发时的应用部署结构

    Feign是如何设计的?

    原生的Feign

    虽然我们用SpringCloud全家桶比较多,但是其实呢?他只是对原生的fegin做了一些封装,所以刨根问底的话,我们还是多了解了解原生的Fegin,对于我们理解Spring Cloud feign是很有帮助的

    Feign使用简介

    基本用法

    基本的使用如下所示,一个对于canonical Retrofit sample的适配。

    interface GitHub {
     // RequestLine注解声明请求方法和请求地址,可以允许有查询参数
     @RequestLine("GET /repos/{owner}/{repo}/contributors")
     List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
    }
    static class Contributor {
     String login;
     int contributions;
    }
    public static void main(String... args) {
     GitHub github = Feign.builder()
                .decoder(new GsonDecoder())
                .target(GitHub.class, "https://api.github.com");
     // Fetch and print a list of the contributors to this library.
     List<Contributor> contributors = github.contributors("OpenFeign", "feign");
     for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
     }
    }
    

    自定义

    Feign 有许多可以自定义的方面。举个简单的例子,你可以使用 Feign.builder() 来构造一个拥有你自己组件的API接口。如下:

    interface Bank {
     @RequestLine("POST /account/{id}")
     Account getAccountInfo(@Param("id") String id);
    }
    ...
    // AccountDecoder() 是自己实现的一个Decoder
    Bank bank = Feign.builder().decoder(new AccountDecoder()).target(Bank.class, https://api.examplebank.com);
    

    Feign 动态代理

    Feign 的默认实现是 ReflectiveFeign,通过 Feign.Builder 构建。再看代码前,先了解一下 Target 这个对象。

    public interface Target<T> {
      // 接口的类型
      Class<T> type();
    
      // 代理对象的名称,默认为url,负载均衡时有用
      String name();
      // 请求的url地址,eg: https://api/v2
      String url();
    }
    

    其中 Target.type 是用来生成代理对象的,url 是 Client 对象发送请求的地址。

    public Feign build() {
        // client 有三种实现 JdkHttp/ApacheHttp/okHttp,默认是 jdk 的实现
        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);
    }
    

    总结:介绍一下几个主要的参数:

    • Client 这个没什么可说的,有三种实现 JdkHttp/ApacheHttp/okHttp

    • RequestInterceptor 请求拦截器

    • Contract REST 注解解析器,默认为 Contract.Default(),即支持 Feign 的原生注解。

    • InvocationHandlerFactory 生成 JDK 动态代理,实际执行是委托给了 MethodHandler。

    生成代理对象

    public <T> T newInstance(Target<T> target) {
        // 1. Contract 将 target.type 接口类上的方法和注解解析成 MethodMetadata,
        //    并转换成内部的MethodHandler处理方式
        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)));
            }
        }
    
        // 2. 生成 target.type 的 jdk 动态代理对象
        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;
    }
    

    总结:newInstance 生成了 JDK 的动态代理,从 factory.create(target, methodToHandler) 也可以看出 InvocationHandler 实际委托给了 methodToHandler。methodToHandler 默认是 SynchronousMethodHandler.Factory 工厂类创建的。

    MethodHandler 方法执行器

    ParseHandlersByName.apply 生成了每个方法的执行器 MethodHandler,其中最重要的一步就是通过 Contract 解析 MethodMetadata。

    public Map<String, MethodHandler> apply(Target key) {
        // 1. contract 将接口类中的方法和注解解析 MethodMetadata
        List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
        Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
        for (MethodMetadata md : metadata) {
            // 2. buildTemplate 实际上将 Method 方法的参数转换成 Request
            BuildTemplateByResolvingArgs buildTemplate;
            if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
                // 2.1 表单
                buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
            } else if (md.bodyIndex() != null) {
                // 2.2 @Body 注解
                buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
            } else {
                // 2.3 其余
                buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
            }
            // 3. 将 metadata 和 buildTemplate 封装成 MethodHandler
            result.put(md.configKey(),
                       factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
        }
        return result;
    }
    

    总结:这个方法由以下几步:

    Contract 统一将方法解析 MethodMetadata(*),这样就可以通过实现不同的 Contract 适配各种 REST 声明式规范。buildTemplate 实际上将 Method 方法的参数转换成 Request。将 metadata 和 buildTemplate 封装成 MethodHandler。

    这样通过以上三步就创建了一个 Target.type 的代理对象 proxy,这个代理对象就可以像访问普通方法一样发送 Http 请求,其实和 RPC 的 Stub 模型是一样的。了解 proxy 后,其执行过程其实也就一模了然。

    Feign 调用过程

    FeignInvocationHandler#invoke

    private final Map<Method, MethodHandler> dispatch;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ...
        // 每个Method方法对应一个MethodHandler
        return dispatch.get(method).invoke(args);
    }
    

    总结:和上面的结论一样,实际的执行逻辑实际上是委托给了 MethodHandler。

    SynchronousMethodHandler#invoke

    // 发起 http 请求,并根据 retryer 进行重试
    public Object invoke(Object[] argv) throws Throwable {
        // template 将 argv 参数构建成 Request
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        Options options = findOptions(argv);
        Retryer retryer = this.retryer.clone();
    
        // 调用client.execute(request, options)
        while (true) {
            try {
                return executeAndDecode(template, options);
            } catch (RetryableException e) {
                try {
                    // 重试机制
                    retryer.continueOrPropagate(e);
                } catch (RetryableException th) {
                    ...
                }
                continue;
            }
        }
    }
    

    总结:invoke 主要进行请求失败的重试机制,至于具体执行过程委托给了 executeAndDecode 方法。

    // 一是编码生成Request;二是http请求;三是解码生成Response
    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
        // 1. 调用拦截器 RequestInterceptor,并根据 template 生成 Request
        Request request = targetRequest(template);
        // 2. http 请求
        Response response = client.execute(request, options);
     // 3. response 解码
        if (Response.class == metadata.returnType()) {
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            return response.toBuilder().body(bodyData).build();
        }
        ...
    }
    
    Request targetRequest(RequestTemplate template) {
        // 执行拦截器
        for (RequestInterceptor interceptor : requestInterceptors) {
            interceptor.apply(template);
        }
        // 生成 Request
        return target.apply(template);
    

    这个是原生feign的调用过程,总的来说分为2部 一个是 客户端的封装,一个调用方法的封装

    Spring Cloud Feign 的原理解析

    我们前面看了原生的feign之后呢?对于Spring Cloud的Feign的话理解起来就很简单了,我们知道Spring cloud 是基于SpringBoot SpringBoot 又是基于Spring,那么Spring就是一个胶水框架,它就是把各个组件把它封装起来,所以呢,这样就简单很多了嘛

    小六六在这边就不一一的给大家演示SpringCloud 是如何使用Feign的了,小六六默认大家都懂,哈哈,那么就直接说原理吧

    工作原理

    我们来想想平时我们使用feign的时候,会是一个怎么样的流程

    • 添加了 Spring Cloud OpenFeign 的依赖

    • 在 SpringBoot 启动类上添加了注解 @EnableFeignCleints

    • 按照 Feign 的规则定义接口 DemoService, 添加@FeignClient 注解

    • 在需要使用 Feign 接口 DemoService 的地方, 直接利用@Autowire 进行注入

    • 使用接口完成对服务端的调用

    那我们基于这些步骤来分析分析,本文并不会说非常深入去看每一行的源码

    • SpringBoot 应用启动时, 由针对 @EnableFeignClient 这一注解的处理逻辑触发程序扫描 classPath中所有被@FeignClient 注解的类, 这里以 XiaoLiuLiuService 为例, 将这些类解析为 BeanDefinition 注册到 Spring 容器中

    • Sping 容器在为某些用的 Feign 接口的 Bean 注入 XiaoLiuLiuService 时, Spring 会尝试从容器中查找 XiaoLiuLiuService 的实现类

    • 由于我们从来没有编写过 XiaoLiuLiuService 的实现类, 上面步骤获取到的 XiaoLiuLiuService 的实现类必然是 feign 框架通过扩展 spring 的 Bean 处理逻辑, 为 XiaoLiuLiuService 创建一个动态接口代理对象, 这里我们将其称为 XiaoLiuLiuServiceProxy 注册到spring 容器中。

    • Spring 最终在使用到 XiaoLiuLiuService 的 Bean 中注入了 XiaoLiuLiuServiceProxy 这一实例。

    • 当业务请求真实发生时, 对于 XiaoLiuLiuService 的调用被统一转发到了由 Feign 框架实现的 InvocationHandler 中, InvocationHandler 负责将接口中的入参转换为 HTTP 的形式, 发到服务端, 最后再解析 HTTP 响应, 将结果转换为 Java 对象, 予以返回。

    所以我们基于原生的feign来分析分析,其实就是多了2步,前面的原生feign会帮助我们生成代理对象,这个是我们调用方法的主体,也是这个代理对象才有能力去请求http请求,那么spring就想办法,把这一类的对象放到spring的上下文中,那么我们下次调用的时候,这个对象当然就有了http请求的能力了。

    结束

    我是小六六,三天打鱼,两天晒网,今天我的分享就到了。

    展开全文
  • Spring Cloud Feign相关原理分析

    千次阅读 2019-05-24 12:31:49
    1.认识Spring Cloud Feign 在使用Spring Cloud构建的微服务架构中,Feign用来提供声明式的服务客户端定义方式。 Feign是一个伪客户端(不做任何的请求处理),它通过处理注解生成request,以此来简化HTTP API的开发。 ...
  • feign架构原理解析 目录 feign与ribbon对接的关键点 feign与ribbon对接主要还是在Client对象上做文章,将Client替换为继承Ribbon模板的实现类,这样就可以对执行请求前后做一些负载逻辑 什么是feign? 在使用feign...
  • SpringCloudFeign讲解

    2021-12-06 17:04:03
    一、什么是Feign Feign是一种声明式、模板化的HTTP客户端(仅在Application Client中使用)。声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求。  Spring Cloud的声明式调用, 可以做到...
  • SpringCloud——Feign实例及原理

    万次阅读 2018-08-06 11:19:27
    1、配置feign 添加依赖 在maven的pom中添加feign &amp;amp;amp;amp;lt;dependency&amp;amp;amp;amp;gt; &amp;amp;amp;amp;lt;groupId&amp;amp;amp;amp;gt;org.springframework.cloud&amp...
  • 该实例主要是提供给大家一个实例,告诉大家在实际的项目中使用SpringCloudFeign完成A服务调用B服务的实现步骤,不涉及原理讲解.
  • 2021年SpringCloud面试题及答案汇总

    千次阅读 2021-05-13 08:13:27
    5、 springcloud核⼼组件及其作⽤,以及springcloud⼯作原理: 6、 接⼝限流⽅法? 7、 Spring Cloud Task 此处,仅展示前7道,查看更多30道… SpringCloud最新2021年面试题大汇总,附答案 1、 设计微服务的最佳实践...
  • 在介绍之前,我们先来对 Feign 做一下简单的介绍,先来理解一下什么是 Feign。 1. Feign 是什么? 我们看一下官方的解释:Feign 是一个声明式 WebService 客户端。使用 Feign 能让编写的 WebService 客户端更加...
  • Spring Cloud Feign使用详解

    千次阅读 2018-08-29 13:44:25
    ▪️首先,创建一个Spring Boot基础工程,取名为kyle-service-feign,并在pom.xml中引入spring-cloud-starter-eureka和spring-cloud-starter-feign依赖,具体内容如下所示。 < parent > < groupId > org....
  • springcloud学习笔记之Feign组件

    万次阅读 2019-07-06 17:02:24
    springClound有关feign的分析,作者从示例开头... 2、通过@EnableFeignClient注解 分析了feign和spring结合的原理 3、通过@FeignClient注解,分析feign如何使用 希望能给你在学习springClound的过程中带来一些帮助。
  • SpringCloudAlibaba微服务原理与实战 谭锋 电子工业出版社 ISBN-9787121388248 仅供参考, 自建索引, 以备后查 一、应用架构演进、微服务发展史 二、SpringCloud简介 三、...
  • 超详细的SpringCloud底层原理

    万次阅读 多人点赞 2019-10-03 15:21:40
    面试中面试官喜欢问组件的实现原理,尤其是常用技术,我们平时使用了SpringCloud还需要了解它的实现原理,这样不仅起到举一反三的作用,还能帮助轻松应对各种问题及有针对的进行扩展。 以下是 《Java深入微服务原理...
  • 对于服务之间的调用当然是通过rpc调用,要比我们自己手动拼接url的好 Spring Cloud Alibaba整合了Feign,使用Fegin实现服务之间的调用,默认集成Ribbon,可以实现负载均衡 什么是Feign Feign是Spring Cloud提供的一...
  • SpringCloud FeignClient底层实现原理(二)

    千次阅读 2020-06-21 23:15:46
    SpringCloud FeignClient底层实现原理(二) 在上一篇文章中我们分析到了FeignClientFactoryBean的getTarget方法,该方法中我们先分析下loadBalance方法 <T> T getTarget() { FeignContext context = this....
  • 1 背景: 在进行Feign调用时对于耗时较长的业务会发生超时而返回异常,以此需要根据业务需求配置超时时间,其中涉及到Hystrix超时时间和Ribbon超时时间,对于Hystrix的隔离机制分为信号量隔离和线程池隔离,对于请求...
  • Spring Cloud Feign 实现服务降级

    千次阅读 2018-01-18 14:49:14
    若在高负载的情况下,如果不做处理的话,这些问题会导致系统崩溃.Feign已经为我们集成了Hystrix,默认就会使用断路器包裹所有方法下面来看下简单的服务降级使用过程:继续使用前面搭建的Eureka,Feign,Service服务一 : ...
  • SpringCloud

    千次阅读 2019-11-18 08:54:06
    SpringCloud 作者:小阳 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ia5iSekQ-1574038415836)(D:\TinkingCat\SpringCloud\assets\1571138581444.png)] 微服务介绍 ...
  • 在需要接口所在项目中导入pom文件如下: 对应上面的account-feign微服务 <?xml version="1.0" encoding="UTF-8"?> <project xmlns=...
  • 服务调用——Netflix Feign 连接 熔断器——Netflix Hystrix 连接 服务网关——Netflix Zuul 连接 分布式配置——Spring Cloud Config 消息总线 —— Spring Cloud Bus 连接 连接 学习分布式: 分布式...
  • 详见《springcloudalibaba 微服务原理与实战-page30》。我们继续分析原理,我们继续看这个selectImports,这个方法里面主要是两个功能方法,一个是AutoConfigurationMetadataLoader.loadMetadata从META-INF/spring-...
  • SpringCloud和Dubbo的区别及Dubbo和Feign远程调用的差异
  • SpringCloudFeign 是声明式RESTful请求客户端,所以它不会侵入服务提供者程序的实现。也就是说,服务提供者只需要提供Web Service的API接口,至于具体实现既可以是 SpringControler 也可以是 Jersey 。我们只需要...
  • Feign分析

    2019-06-11 15:52:06
    Feign 是一个声明web服务客户端,这便得编写web服务客户端更容易,使用Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器,支持拦截器,支持...
  • addr: localhost:8848 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url: nacos-user-service: http://nacos-payment-provider # 激活Sentinel对Feign的支持 feign: sentinel: enabled: ...
  • Feign的继承特性

    2019-03-22 17:47:13
    SpringCloudFeign提供了继承特性,将一些公共操作弄到父接口,可以进一步减少编码量,从而简化开发。 编写hello-service-api 1.创建一个基础的Maven工程,命名为hello-service-api 2.编写pom.xml <?xml ...
  • 1.什么是Feign 1.1Feign优势 2.spring cloud alibaba整合Feign 3.Spring Cloud Feign日志配置 4.Feign契约配置 5.Feign超时时间配置 6.Open Feign自定义拦截器 1.什么是Feign Feign是Netflix开发的声明式、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 903
精华内容 361
关键字:

springcloudfeign原理

spring 订阅