• Spring Cloud Netflix之OpenFeign


    OpenFeign

    实现远程调用的方式

    • Http接口(web接口、RestTemplate+Okhttp)
    • Feign
    • RPC调用(Dubbo、Socket编程)
    • Webservice

    什么是Feign

    Feign是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。

    Nacos注册中心很好的兼容了Feign,Feign默认集成了Ribbon,所以Nacos下使用Feign默认就实现了负载均衡的效果。

    Feign与OpenFeign的区别

    • Feign是Netflix开发的一个轻量级RestFul风格的http客户端,内置了Ribbon用来做客户端负载均衡,去调用服务注册中心的服务。
    • openFeign是Spring Cloud又对Feign进行了增强开发,其支持SpringMvc注解,其注解@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,使用更加方便。

    OpenFeign 使用

    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
        <version>2.2.10.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # application.class
    @SpringBootApplication
    @EnableFeignClients
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    # FeignClient
    @FeignClient("stores")
    public interface StoreClient {
        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        List<Store> getStores();
    
        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        Page<Store> getStores(Pageable pageable);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    OpenFeign的高级特性

    Spring Cloud OpenFeign 默认不为feign一下Bean,但是仍然从应用上下文中查找这些类型的bean来创建feign客户端。

    • Logger.Level
    • Retryer
    • ErrorDecoder
    • Request.Options
    • Collection
    • SetterFactroy
    • QueryMapEncoder
    设置超时时间

    通过Options可以配置连接超时时间和读取超时时间,Options的第一参数是连接的超时时间(ms);第二个是请求处理的超时时间(ms)。

    服务消费者:配置类配置(全局配置)

    @Configuration
    public class FeignClass {
        
        @Bean
        public Request.Options options() {
            return new Request.Options(5000, 10000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    服务消费者:配置类配置(局部配置)

    public class FeignConfig {
        
        @Bean
        public Request.Options options(){
            return new Request.Options(5000, 10000);
        }
    }
    
    @FeignClient(name = "stores", configuration = FeignConfig.class)
    public interface StoreClient {
        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        List getStores();
    
        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        Page getStores(Pageable pageable);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    服务消费者:配置文件配置

    # 配置feign的超时时间
    feign:
    	client:
    		config:
    			default:		# 默认超时时间
    			stock-service:	# 服务提供者的服务名
    			connectTimeout:	5000
    			readTimeout: 10000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Feign的底层用的是Ribbon,但是超时时间以Feign配置为准?

    日志配置

    服务消费者:配置类配置(全局配置)

    @Configuration
    public class FeignConfig {
        @Bean
        public Logger.Level feignLoggerLevel(){
            return Logger.Level.FULL;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    服务消费者:配置类配置(局部配置)

    public class FeignConfig {
        @Bean
        public Logger.Level feignLoggerLevel(){
            return Logger.Level.FULL;
        }
    }
    
    @FeignClient(name = "stores", configuration = FeignConfig.class)
    public interface StoreClient {
        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        List<Store> getStores();
    
        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        Page<Store> getStores(Pageable pageable);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    服务消费者:配置文件配置

    feign:
    	client:
    		config:
    			stock-service:	#被调用的服务提供者的服务名
    				loggerLevel:BASIC
    
    • 1
    • 2
    • 3
    • 4
    • 5

    OpenFeign日志级别:

    • NONE(默认)性能最佳
    • BASIC:适用于生产环境测试问题
    • HEADERS
    • FULL:适合开发和测试环境定位问题
    替换底层通信
    <dependency>
            <groupId>io.github.openfeigngroupId>
            <artifactId>feign-okhttpartifactId>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    feign.httpclient.enabled=false
    feign.okhttp.enabled=true
    
    • 1
    • 2
    拦截器

    服务消费者:配置类配置(全局配置)

    @Configuration
    public class OpenFeignConfig {
        /**
         * 自定义feign拦截器
         * @return
         */
        @Bean
        public CustomFeignInterceptor customFeignInterceptor() {
            return new CustomFeignInterceptor();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    /**
     * 自定义feign拦截器
     */
    public class CustomFeignInterceptor implements RequestInterceptor {
        Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            //写一些自己的逻辑 带上token 什么之类的
            String access_token = UUID.randomUUID().toString();
            requestTemplate.header("Authorization",access_token);
            logger.info("feign拦截器!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    服务消费者:配置文件配置

    feign:
      client:
        config:
          stock-service: #服务名
            requestInterceptors:
              - com.gaby.cloud.order.interceptor.feign.CustomFeignInterceptor
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    OpenFeign原理及源码分析

    OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式实现远程调用,从而屏蔽底层通信的复杂度,它的具体原理如下图所示:

    img

    对于OpenFeign功能实现的思考
    1. 声明@FeignClient注解的接口,如何被注释和注入的?
    2. 通过@Autowired依赖注入,到底注入的是一个什么样的实例?
    3. 基于Feigin声明的接口被解析后如何存储?
    4. 在发起方法调用时,整体的工作流程是什么?
    5. OpenFeign是如何集成Ribbon做负载均衡的?
    OpenFeign注解扫描与解析

    思考:一个被声明了@FeignClient注解的接口,使用@Autowired进行依赖注入,而最终这个接口能够正常被注入实例

    从这个结果来看,可以得出两个结论:

    1. 被@FeignClient声明的接口,在Spring容器启动的时候,会被解析。
    2. 由于被Spring容器加载的是接口,而接口又没有实现类,因此Spring容器解析时,会生成一个动态代理类。
    @EnableFeignClient

    @FeignClient注解是在什么时候解析的?基于我们之前的知识积累,无非就一下几种情况:

    • importSelector,批量导入bean
    • importBeanDefinitionRegistrar,导入bean声明并进行注册
    • BeanFactoryPostProcessor,一个bean被装载的前后处理器

    在这几个选项种,importBeanDefinitionRegistrar更合适,因为importSelector是批量导入一个bean的string,不适合做Bean的声明。而BeanFactoryPostProcessor是一个bean初始化之前和之后被调用的处理器。

    而在我们的FeignClient声明中,并没有Spring相关的注解,自然也不会被Spring容器加载和触发。

    那么@FeignClient是在哪里被声明扫描的呢?

    在集成FeiginClient时,我们在SpringBoot的main方法中,声明了在一个注解@EnableFeignClient(basePackages="com.zhangmen.api")。这个注解需要填写一个指定的包名。

    看到这里,基本上可以猜出,这个注解必然和@FeignClient注解的解析又莫大关系。

    下面这段代码是@EnableFeignClient注解的声明,可以看到一个很熟悉的面孔:FeignClientRegister

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Import({FeignClientsRegistrar.class})
    public @interface EnableFeignClients {
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    FeignClientRegistrar

    FeignClientregistrar,主要功能就是针对声明@FeignClient注解的接口进行扫描和注入到IOC容器。

    class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
        
    }
    
    • 1
    • 2
    • 3

    果然,这个类实现了ImportBeanDefinitionRegistrar接口。

    public interface ImportBeanDefinitionRegistrar {
    
    	public void registerBeanDefinitions(
    			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个接口用来实现Bean的声明和注册。

    简单演示一下ImportBeanDefinitionRegistrar的作用:

    在项目中分别创建

    1. HelloService.java
    2. MyImportBeanDefinitionRegistrar.java
    3. EnableMyRegistrar.java
    4. TestMain
    1. 定义一个需要被装载到IOC容器中的类HelloService
    public class HelloService {
        
    }
    
    • 1
    • 2
    • 3
    1. 定义一个Registar的实现,定义一个bean,装载到IOC容器
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            
            BeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClassName(HelloService.class.getName());
            registry.registerBeanDefinition("helloService", beanDefinition);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 定义一个注解类
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Import({MyImportBeanDefinitionRegistrar.class})
    public @interface EnableFeignClients {
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 写一个测试类
    @Configuration
    @EnableMyRegistrar
    public class TeatMain{
        
        public static void main(String[] args) {
            
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestMain.class);
            System.out.println(applicationnContext.getBean(helloService.class));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过结果演示可以发现,HelloService这个Bean已经装载到IOC容器。这就是动态装载功能的实现,它相对@Configuration配置注入,会多了很多的灵活性。

    FeignClientRegistrar.registerBeanDefinitions
    • registerDefaultConfiguration方法内部从Springboot启动类上检查是否有@EnableFeignClients有该注解的话,则完成Feign框架相关的一些配置内容注册。
    • registerFeignClients方法内部从classpath中扫描获得@FeignClient修饰的类,将类的内容解析为BeanDefinition,最终通过调用Spring框架中的BeanDefinitionReaderUtils.registerBeanDefinition将解析处理过的FeignClient添加到spring容器中。
    class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
    ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                                            BeanDefinitionRegistry registry) {
            //注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
            //在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
            //所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
            registerDefaultConfiguration(metadata, registry);
            registerFeignClients(metadata, registry);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里需要重点分析的就是registerFeignClients(matadata, registry)方法,这个方法主要是扫描路径下所有的@FeignClient注解,然后进行动态Bean的注入。它最终会调用registerFeignClient方法。

    public void registerFeignClients(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
        registerFeignClient(registry, annotationMetadata, attributes);
    }
    
    • 1
    • 2
    • 3
    • 4
    FeignClientRegistrar.registerFeignClients
    //# FeignClientsRegistrar.registerFeignClients
    public void registerFeignClients(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
    
       LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
       //获取@EnableFeignClients注解的元数据
       Map<String, Object> attrs = metadata
             .getAnnotationAttributes(EnableFeignClients.class.getName());
        //获取@EnableFeignClients注解中的clients属性,可以配置@FeignClient声明的类,如果配置了,则需要扫描并加载。
       final Class<?>[] clients = attrs == null ? null
             : (Class<?>[]) attrs.get("clients");
       if (clients == null || clients.length == 0) {
           //默认TypeFilter生效,这种模式会查询出许多不符合你要求的class名
          ClassPathScanningCandidateComponentProvider scanner = getScanner();
          scanner.setResourceLoader(this.resourceLoader);
          scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); //添加包含过滤的属性@FeignClient。
          Set<String> basePackages = getBasePackages(metadata); //从@EnableFeignClients注解中获取basePackages配置。
          for (String basePackage : basePackages) {
              //scanner.findCandidateComponents(basePackage) 扫描basePackage下的@FeignClient注解声明的接口
             candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //添加到candidateComponents,也就是候选容器中。
          }
       }
       else {//如果配置了clients,则需要添加到candidateComponets中。
          for (Class<?> clazz : clients) {
             candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
          }
       }
       //遍历候选容器列表。
       for (BeanDefinition candidateComponent : candidateComponents) { 
          if (candidateComponent instanceof AnnotatedBeanDefinition) { //如果属于AnnotatedBeanDefinition实例类型
             // verify annotated class is an interface
              //得到@FeignClient注解的beanDefinition
             AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
             AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();  //获取这个bean的注解元数据
             Assert.isTrue(annotationMetadata.isInterface(),
                   "@FeignClient can only be specified on an interface"); 
             //获取元数据属性
             Map<String, Object> attributes = annotationMetadata
                   .getAnnotationAttributes(FeignClient.class.getCanonicalName());
             //获取@FeignClient中配置的服务名称。
             String name = getClientName(attributes);
             registerClientConfiguration(registry, name,
                   attributes.get("configuration"));
    
             registerFeignClient(registry, annotationMetadata, attributes);
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    FeignClient Bean的注册

    这个方法就是把FeignClient接口注册到Spring IoC容器

    FeignClient是一个接口,那么这里注入到IoC容器中的对象是什么呢?

    private void registerFeignClient(BeanDefinitionRegistry registry,
          AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
       String className = annotationMetadata.getClassName();  //获取FeignClient接口的类全路径
       Class clazz = ClassUtils.resolveClassName(className, null); //加载这个接口,得到Class实例
        //构建ConfigurableBeanFactory,提供BeanFactory的配置能力
       ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory 
             ? (ConfigurableBeanFactory) registry : null;
        //获取contextId
       String contextId = getContextId(beanFactory, attributes);
       String name = getName(attributes);
        //构建一个FeignClient FactoryBean,这个是工厂Bean。
       FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        //设置工厂Bean的相关属性
       factoryBean.setBeanFactory(beanFactory);
       factoryBean.setName(name);
       factoryBean.setContextId(contextId);
       factoryBean.setType(clazz);
    
        //BeanDefinitionBuilder是用来构建BeanDefinition对象的建造器
       BeanDefinitionBuilder definition = BeanDefinitionBuilder
             .genericBeanDefinition(clazz, () -> {
                 //把@FeignClient注解配置中的属性设置到FactoryBean中。
                factoryBean.setUrl(getUrl(beanFactory, attributes));
                factoryBean.setPath(getPath(beanFactory, attributes));
                factoryBean.setDecode404(Boolean
                      .parseBoolean(String.valueOf(attributes.get("decode404"))));
                Object fallback = attributes.get("fallback");
                if (fallback != null) {
                   factoryBean.setFallback(fallback instanceof Class
                         ? (Class<?>) fallback
                         : ClassUtils.resolveClassName(fallback.toString(), null));
                }
                Object fallbackFactory = attributes.get("fallbackFactory");
                if (fallbackFactory != null) {
                   factoryBean.setFallbackFactory(fallbackFactory instanceof Class
                         ? (Class<?>) fallbackFactory
                         : ClassUtils.resolveClassName(fallbackFactory.toString(),
                               null));
                }
                return factoryBean.getObject();  //factoryBean.getObject() ,基于工厂bean创造一个bean实例。
             });
       definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //设置注入模式,采用类型注入
       definition.setLazyInit(true); //设置延迟华
       validate(attributes); 
       //从BeanDefinitionBuilder中构建一个BeanDefinition,它用来描述一个bean的实例定义。
       AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
       beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
       beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
    
       // has a default, won't be null
       boolean primary = (Boolean) attributes.get("primary");
    
       beanDefinition.setPrimary(primary);
    
       String[] qualifiers = getQualifiers(attributes);
       if (ObjectUtils.isEmpty(qualifiers)) {
          qualifiers = new String[] { contextId + "FeignClient" };
       }
    
       BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
             qualifiers);
       BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); //把BeanDefinition的这个bean定义注册到IOC容器。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    综上代码分析,其实逻辑很简单

    1. 创建一个BeanDefinitionBuilder
    2. 创建一个工程Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean中
    3. 调用registerbeanDefinition注册到IoC容器中
    关于FactoryBean

    关于上述的逻辑,重点需要关注的是FactoryBean。

    首先需要注意的是FactoryBean和BeanFactory是不一样的。FactoryBean是一个工厂Bean,可以生成某一个类型的Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。

    而BeanFactory是Spring容器中一个基本类,也是很重要的一个类,在BeanFactory中可以创建和管理Sping容器中的Bean。

    下面代码是FactoryBean接口的定义

    public interface FactoryBean<T> {
        String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    
        @Nullable
        T getObject() throws Exception;
    
        @Nullable
        Class<?> getObjectType();
    
        default boolean isSingleton() {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从上面的代码中可以发现在FactoryBean中定义了一个Spring Bean 的很重要的三个特性:是否单例、Bean类型、Bean实例,这也应该是我们关注Spring中的一个Bean最直观的感受。

    FactoryBean自定义使用

    下面我们来模拟一下@FeiginClient解析以及工程Bean的构建过程。

    1. 先定义一个接口,这个接口可以类比我们上面描述的FeignClient

      public interface IHelloService {
          String say();
      }
      
      • 1
      • 2
      • 3
    2. 接着,定义一个工程Bean,这个工厂Bean中主要是针对IHelloService生成动态代理。

      public class DefineFactoryBean implements FactoryBean<IHelloService> {
          
           @Override
          public IHelloService getObject()  {
             IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
                     new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {
                 System.out.println("begin execute");
                 return "Hello FactoryBean";
             });
              return helloService;
          }
      
          @Override
          public Class<?> getObjectType() {
              return IHelloService.class;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    3. 通过ImportBeanDefinitionRegistrar这个接口,实现动态注入Bean实例

      public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
      
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                              BeanDefinitionRegistry registry) {
      
              DefineFactoryBean factoryBean=new DefineFactoryBean();
              BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(
                      IHelloService.class,()-> factoryBean.getObject());
              BeanDefinition beanDefinition=definition.getBeanDefinition();
              registry.registerBeanDefinition("helloService",beanDefinition);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    4. 声明一个注解,用来表示动态Bean的注入导入。

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      @Documented
      @Import(MyImportBeanDefinitionRegistrar.class)
      public @interface EnableMyRegistrar {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    5. 编写测试类,测试IHelloService这个接口的动态注入

      @Configuration
      @EnableMyRegistrar
      public class TestMain {
      
          public static void main(String[] args) {
              ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
              IHelloService is=applicationContext.getBean(IHelloService.class);
              System.out.println(is.say());
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    6. 运行上述的测试方法,可以看到IHelloService这个接口,被动态代理并注入到IOC容器。

    FeignClientFactoryBean

    由上述案例可知,SpringIOC容器注入FactoryBean时,会调用FactoryBean的getObject()方法获得bean的实例。因此我们按照这个思路去分析FeiginClientFactoryBean

    @Override
    public Object getObject() {
       return getTarget();
    }
    
    • 1
    • 2
    • 3
    • 4

    构建对象Bean的实例代码如下,这个代码的实现较长,分为几个步骤来看

    Feign上下文的构建

    先看上下文的构建逻辑

    <T> T getTarget() {
       FeignContext context = beanFactory != null
             ? beanFactory.getBean(FeignContext.class)
             : applicationContext.getBean(FeignContext.class);
       Feign.Builder builder = feign(context);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    两个关键的对象说明:

    1. FeignContext是全局唯一的上下文,它继承了NameContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文,FeignContext注册到容器是在FeignAutoConfiguration中完成的,代码如下

      //FeignAutoConfiguration.java
      
      @Autowired(required = false)
      private List configurations = new ArrayList<>();
      @Bean
      public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      context.setConfigurations(this.configurations);
      return context;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      在初始化FeignContext时,会把configurations放入到FeignContext中。configuration表示当前被扫描到的所有@FeignClient

    2. Feign.Build是用来构建Feign对象,基于builder实现上下文信息的构建

      protected Feign.Builder feign(FeignContext context) {
          FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
          Logger logger = loggerFactory.create(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)); //contract协议,用来实现模版解析(后面再详细分析)
          // @formatter:on
      
          configureFeign(context, builder);
          applyBuildCustomizers(context, builder);
      
          return builder;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      从代码中可以看到,feign方法,主要是针对不同的服务提供者生成Feign的上下文信息,比如loggerencoderdecoder等。因此,从这个分析过程中,我们不难猜测到它的原理结构

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3BAcitXS-1667540284739)(v2-25538233eaabdaa4361960616d262023_r.jpg)]

      父子容器隔离的实现方式如下,当调用get方法时,会从context中去获取指定type的实例对象。

      //FeignContext.java
      protected <T> T get(FeignContext context, Class<T> type) {
          T instance = context.getInstance(contextId, type);
          if (instance == null) {
              throw new IllegalStateException(
                  "No bean found of type " + type + " for " + contextId);
          }
          return instance;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      接着,调用NamedContextFactory中的getInstance方法

      //NamedContextFactory.java
      public <T> T getInstance(String name, Class<T> type) {
          //根据`name`获取容器上下文
          AnnotationConfigApplicationContext context = this.getContext(name);
      
          try {
              //再从容器上下文中获取指定类型的bean。
              return context.getBean(type);
          } catch (NoSuchBeanDefinitionException var5) {
              return null;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      getContext方法根据namecontexts容器中获得上下文对象,如果没有,则调用createContext创建。

      protected AnnotationConfigApplicationContext getContext(String name) {
          if (!this.contexts.containsKey(name)) {
              synchronized(this.contexts) {
                  if (!this.contexts.containsKey(name)) {
                      this.contexts.put(name, this.createContext(name));
                  }
              }
          }
      
          return (AnnotationConfigApplicationContext)this.contexts.get(name);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      生成动态代理

      如果@FeignClient注解中,没有配置url,也就是不走绝对请求路径,则执行下面这段逻辑。由于我们在@FeignClient注解中使用的是name,所以需要执行负载均衡策略的分支逻辑。

       T getTarget() {
          //省略.....
           if (!StringUtils.hasText(url)) { //是@FeignClient中的一个属性,可以定义请求的绝对URL
      
            if (LOG.isInfoEnabled()) {
               LOG.info("For '" + name
                     + "' URL not provided. Will try picking an instance via load-balancing.");
            }
            if (!name.startsWith("http")) {
               url = "http://" + name;
            }
            else {
               url = name;
            }
            url += cleanPath();
            //
            return (T) loadBalance(builder, context,
                  new HardCodedTarget<>(type, name, url));
         }
          //省略....
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21

      loadBalance方法的代码实现如下,其中Client是Spring Boot自动装配的时候实现的,如果替换了其他的http协议框架,则client则对应为配置的协议api。

      protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                                  HardCodedTarget<T> target) {
          //Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
          Client client = getOptional(context, Client.class);
          if (client != null) {
              builder.client(client); //针对当前Feign客户端,设置网络通信的client
              //targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
              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 or spring-cloud-starter-loadbalancer?");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      HystrixTarget.target代码如下,我们没有集成Hystrix,因此不会触发Hystrix相关的处理逻辑。

      //HystrixTarget.java
      @Override
      public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
                          FeignContext context, Target.HardCodedTarget<T> target) {
          if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { //没有配置Hystrix,则走这部分逻辑
              return feign.target(target);
          }
         //省略....
      
          return feign.target(target);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      进入到Feign.target方法,代码如下。

      //Feign.java
      public <T> T target(Target<T> target) {
          return this.build().newInstance(target);  //target.HardCodedTarget
      }
      
      public Feign build() {
          //这里会构建一个LoadBalanceClient
          Client client = (Client)Capability.enrich(this.client, this.capabilities);
          Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
          List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
              return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
          }).collect(Collectors.toList());
          //OpenFeign Log配置
          Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
          //模版解析协议(这个接口非常重要:它决定了哪些注解可以标注在接口/接口方法上是有效的,并且提取出有效的信息,组装成为MethodMetadata元信息。)
          Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
          //封装Request请求的 连接超时=默认10s ,读取超时=默认60
          Options options = (Options)Capability.enrich(this.options, this.capabilities);
          //编码器
          Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
          //解码器
          Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
      
          InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
          QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
          //synchronousMethodHandlerFactory, 同步方法调用处理器(很重要,后续要用到)
          Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
          //ParseHandlersByName的作用就是我们传入Target(封装了我们的模拟接口,要访问的域名),返回这个接口下的各个方法,对应的执行HTTP请求需要的一系列信息
          ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
      
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      build方法中,返回了一个ReflectiveFeign的实例对象,先来看ReflectiveFeign中的newInstance方法。

      public <T> T newInstance(Target<T> target) {   
          //修饰了@FeignClient注解的接口方法封装成方法处理器,把指定的target进行解析,得到需要处理的方法集合。 
          Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);    
          //定义一个用来保存需要处理的方法的集合    
          Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();    
          //JDK8以后,接口允许默认方法实现,这里是对默认方法进行封装处理。    
          List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();       
          //遍历@FeignClient接口的所有方法    
          for (Method method : target.type().getMethods()) {        
              //如果是Object中的方法,则直接跳过        
              if (method.getDeclaringClass() == Object.class) {           
                  continue;                    
              } else if (Util.isDefault(method)) {
                  //如果是默认方法,则把该方法绑定一个DefaultMethodHandler。         
                  DefaultMethodHandler handler = new DefaultMethodHandler(method);         
                  defaultMethodHandlers.add(handler);            
                  methodToHandler.put(method, handler);        
              } else {
                  //否则,添加MethodHandler(SynchronousMethodHandler)。     
                  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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      1. 解析@FeignClient接口声明的方法,根据不同方法绑定不同的处理去。
        1. 默认方法,绑定DefaultMethodHandler
        2. 远程方法,绑定SyschronousMethodHandler
      2. 使用JDK提供的Proxy创建动态代理

      MethodHandler,会把方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储,如下图所示。
      在这里插入图片描述

    FeignClient接口解析

    接口解析也是Feign很重要的一个逻辑,它能把接口声明的属性转化为HTTP通信的协议参数。

    执行逻辑RerlectiveFeign.newInstance

    public <T> T newInstance(Target<T> target) {    
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); 
        //here
    }
    
    • 1
    • 2
    • 3
    • 4

    targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个的map,放入InvocationHandler的实现FeignInvocationHandler中。

    public Map<String, MethodHandler> apply(Target target) {   
        List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());    
        Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); 
        for (MethodMetadata md : metadata) {      
            BuildTemplateByResolvingArgs buildTemplate;       
            if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {   
                buildTemplate =            
                    new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);     
            } else if (md.bodyIndex() != null) {  
                buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);    
            } else {        
                buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);  
            }     
            if (md.isIgnored()) {        
                result.put(md.configKey(), args -> {       
                    throw new IllegalStateException(md.configKey() + " is not a method handled by feign");  
                });    
            } else {       
                result.put(md.configKey(),    
                           factory.create(target, md, buildTemplate, options, decoder, errorDecoder));        }    }  
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    总结

    通过上述过程分析,被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler。

    当触发方法调用时,会被FeignInvocationHandler#invoke拦截,FeignClientFactoryBean在实例化过程中所做的事情如下图所示。
    在这里插入图片描述

    总结来说就几个点:

    1. 解析Feign的上下文配置,针对当前的服务实例构建容器上下文并返回Feign对象
    2. Feign根据上下围配置把 log、encode、decoder、等配置项设置到Feign对象中
    3. 对目标服务,使用LoadBalance以及Hystrix进行包装
    4. 通过Contract协议,把FeignClient接口的声明,解析成MethodHandler
    5. 遍历MethodHandler列表,针对需要远程通信的方法,设置SynchronousMethodHandler处理器,用来实现同步远程调用。
    6. 使用JDK中的动态代理机制构建动态代理对象。
    远程通信实现

    在Spring启动过程中,把一切的准备工作准备就绪后,就开始执行远程调用。

    在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。

    那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是一个动态代理的实现。

    //FeignInvocationHandler.java
    @Overridepublic 
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        if ("equals".equals(method.getName())) {    
            try {     
                Object otherHandler =        
                    args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;   
                return equals(otherHandler);     
            } catch (IllegalArgumentException e) {    
                return false;   
            }   
        } else if ("hashCode".equals(method.getName())) {   
            return hashCode(); 
        } else if ("toString".equals(method.getName())) {     
            return toString();  
        }   
        return dispatch.get(method).invoke(args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    接着,在invoke方法中,会调用this.dispatch.get(method)).invoke(args)this.dispatch.get(method)会返回一个SynchronousMethodHandler,进行拦截处理。

    this.dispatch,其实就是在初始化过程中创建的,private final Map dispatch;实例。

    SynchronousMethodHandler.invoke

    这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下,代码的实现如下:

    @Override
    public Object invoke(Object[] argv) throws Throwable {    //argv,表示调用方法传递的参数。      
      RequestTemplate template = buildTemplateFromArgs.create(argv);  
      Options options = findOptions(argv); //获取配置项,连接超时时间、远程通信数据获取超时时间  
      Retryer retryer = this.retryer.clone(); //获取重试策略  
      while (true) {    
        try {      
          return executeAndDecode(template, options);    
        } catch (RetryableException e) {
          try {        
            retryer.continueOrPropagate(e);      
          } catch (RetryableException th) {
            Throwable cause = th.getCause();        
            if (propagationPolicy == UNWRAP && cause != null) {
              throw cause;        
            } else {
              throw th;
            }      
          }      
          if (logLevel != Logger.Level.NONE) {
            logger.logRetry(metadata.configKey(), logLevel);      
          }      
          continue;    
        }  
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    上述代码的执行流程中,需要先构造一个Request对象,然后调用client.execute方法执行网络通信请求,整体实现流程如下。

    在这里插入图片描述

    executeAndDecode

    经过上述的代码,我们已经将RequestTemplate拼装完成,上面的代码中有一个executeAndDecode()方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。

    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
      Request request = targetRequest(template);  //把template转化为请求报文    
      if (logLevel != Logger.Level.NONE) { //设置日志level      
        logger.logRequest(metadata.configKey(), logLevel, request); 
      }    
      Response response;   
      long start = System.nanoTime();  
      try {     
        //发起请求,此时client是LoadBalanceFeignClient,需要先对服务名称进行解析负载    
        response = client.execute(request, options);      // ensure the request is set. TODO: remove in Feign 12          //获取返回结果   
        response = response.toBuilder().request(request).requestTemplate(template).build(); 
      } catch (IOException e) { 
        if (logLevel != Logger.Level.NONE) {   
          logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));   
        }     
        throw errorExecuting(request, e);  
      }  
      long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);   
      if (decoder != null) //如果设置了解码器,这需要对响应数据做解码    
        return decoder.decode(response, metadata.returnType());   
      CompletableFuture<Object> resultFuture = new CompletableFuture<>();  
      asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,        metadata.returnType(),        elapsedTime);  
      try {  
        if (!resultFuture.isDone())  
          throw new IllegalStateException("Response handling not done"); 
        return resultFuture.join();   
      } catch (CompletionException e) {     
        Throwable cause = e.getCause();   
        if (cause != null)    
          throw cause;    
        throw e;  
      } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    LoadBalanceClient
    @Overridepublic
    Response execute(Request request, Request.Options options) throws IOException { 
      try {   
        URI asUri = URI.create(request.url()); //获取请求uri,此时的地址还未被解析。 
        String clientName = asUri.getHost(); //获取host,实际上就是服务名称  
        URI uriWithoutHost = cleanUrl(request.url(), clientName);    
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);        //加载客户端的配置信息     
        IClientConfig requestConfig = getClientConfig(options, clientName);       //创建负载均衡客户端(FeignLoadBalancer),执行请求   
        return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();   
      } catch (ClientException e) {
        IOException io = findIOException(e); 
        if (io != null) {     
          throw io;    
        }     
        throw new RuntimeException(e); 
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从上面的代码可以看到,lbClient(clientName) 创建了一个负载均衡的客户端,它实际上就是生成的如下所述的类:

    public class FeignLoadBalancer extends        AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {}
    
    • 1
    通信总结

    OpenFeign的整体通信原理解析,如下图所示。

    在这里插入图片描述
    open-feign原理分析转载 https://zhuanlan.zhihu.com/p/466130934 【 Mic带你学架构】

  • 相关阅读:
    学习Java的第三十二天。。。(XML)
    干货!基于GAN的稀有样本生成
    使用Spring Cloud设计电商系统架构
    什么是遗留代码:有效地处理遗留代码的8个小贴士
    8位阿里P8合著“Dubbo微服务进阶笔记”一经面世,Github上标星93K+
    Python实现简繁体转换,现在的人玩得老花了
    Graph RAG: 知识图谱结合 LLM 的检索增强
    Windows server 2012远程桌面会话主机和远程桌面授权
    Python中 utf-8和gbk以及unicode编码
    Qt项目天气预报(1) - ui界面搭建
  • 原文地址:https://blog.csdn.net/baidu_41934937/article/details/127687298