云社区 博客 博客详情

Feign终极解析

Mancccy 发表于 2020-06-09 18:35:55 2020-06-09
0
0

【摘要】 送Linux限量联名T恤!>>>Feign简介       在上一篇文章中分析了Eureka的注册、续约、服务剔除、服务自我保护等机制,地址在https://blog.csdn.net/lgq2626/article/details/80288992。这篇分析SpringCloud的feign。SpringCloud微服务项目之间调用是通过httprest请求来进行服务调用的,...

送Linux限量联名T恤!>>>

Feign简介
       在上一篇文章中分析了Eureka的注册、续约、服务剔除、服务自我保护等机制,地址在https://blog.csdn.net/lgq2626/article/details/80288992。这篇分析SpringCloud的feign。SpringCloud微服务项目之间调用是通过httprest请求来进行服务调用的,之前我们会用到HttpClient等工具来进行服务请求,Spring对这种请求进行了处理,封装成了可声明式的web客户端,使得编写web客户端更容易,feign还支持可插拔的编码器和解码器,Spring在用的时候增加了对@requestMapping的处理,同时,SpringCloud还对feign集成了注册中心(eureka)和客户端负载均衡(ribbon),使得我们拥有一个客户端负载均衡的web请求客户端。

Feign在项目中的配置和使用
       在Springcloud中使用feign的时候,需要在配置类中加入一个@EnableFeignClients注解。代码如下:

@SpringBootApplication//springboot 启动类
@EnableFeignClients//开启eureka扫描
@EnableDiscoveryClient//开启eureka客户端
public class Application {
    public static void main( String[] args ) throws ClassNotFoundException {
        SpringApplication.run(Application.class, args);
    }
}
1
2
3
4
5
6
7
8
配置feign调用客户端

@FeignClient(value = "xxx-server",configuration = FeignConfiguration.class)
public interface ConsumerSmsService extends SMSService{
    @RequestMapping(value = "/sms/smsMessage", method = RequestMethod.POST)
    RespSMSDto sendSms(ReqSMSDto smsReqDto);
}
1
2
3
4
5
经过上面的配置,直接在项目里面注入容器调用接口就可以了。

Feign源码分析
       在@EnableFeignClients标签中,import了一个FeignClientsRegistrar类,那么这个FeignClientsRegistrar#registerBeanDefinitions()在什么时候调用的呢?跟着Spring的源码走下去,看过源码的人都会直接看到AbstractApplicationContext#refresh()方法,整体整理一下代码:

  @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {

            // 扫描本项目里面的java文件,把bean对象封装成BeanDefinitiaon对象,然后调用DefaultListableBeanFactory#registerBeanDefinition()方法把beanName放到DefaultListableBeanFactory 的 List beanDefinitionNames 中去
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                postProcessBeanFactory(beanFactory);

                // 在这里调用到FeignClientsRegistrar对象的registerBeanDefinitions()方法
                invokeBeanFactoryPostProcessors(beanFactory);

                //从DefaultListableBeanFactory里面的beanDefinitionNames中找到所有实现了BeanPostProcessor接口的方法,如果有排序进行排序后放到list中
                registerBeanPostProcessors(beanFactory);

                //Spring的国际化
                initMessageSource();

                // 
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // 
                registerListeners();

                // Spring的IOC、ID处理。Spring的AOP。事务都是在IOC完成之后调用了BeanPostProcessor#postProcessBeforeInitialization()和postProcessBeforeInitialization()方法,AOP(事务)就是在这里处理的
                finishBeanFactoryInitialization(beanFactory);

                // 执行完之后调用实现了所有LifecycleProcessor接口的类的onRefresh()方法,同时调用所有观察了ApplicationEvent接口的事件(观察者模式)
                finishRefresh();
            }

            catch (BeansException ex) {

                // 找到所有实现了DisposableBean接口的方法,调用了destroy()方法,这就是bean的销毁
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                throw ex;
            }

            finally {
                resetCommonCaches();
            }
        }
    }
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
       根据上面整理的代码发现,FeignClientsRegistrar#registerBeanDefinitions()方法是在扫描完bean之后,只放了一个beanname的情况下, 并没有进行IOC注册的时候调用的,这就是Spring动态扩展Bean,实现BeanDefinitionRegistryPostProcessor接口的所有方法也会在这里调用下postProcessBeanDefinitionRegistry()方法。关于Spring的东西就分析到这里。下面回到正题,分析FeignClientsRegistrar#registerBeanDefinitions()方法:

 @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);//扫描EnableFeignClients标签里配置的信息,注册到beanDefinitionNames中。
        registerFeignClients(metadata, registry);
    }
     public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        //省略代码...根据EnableFeignClients配置的basePackages找到包下所有FeignClient注解的类,Spring的Commponet也是这么干的
        for (String basePackage : basePackages) {
            Set 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 attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

                    String name = getClientName(attributes);
                   /**
                     * 关键地方:Feign子容器概念:
                     * 在注入FeignAutoConfiguration类的时候,注入了一个FeignContext对象,这个就是Feign的子容器。
                     * 这里面装了List对象,FeignClientSpecification对象的实质就是在@feignClient上配置的name为key,value为configuration对象的值
                     * 比如feignclient 这样配置的@FeignClient(url="https://api.weixin.qq.com",name="${usercenter.name}", configuration = UserCenterFeignConfiguration.class, primary= false)
                     * 那么在FeignContext中就会出现一个FeignClientSpecification{name='sms-server', configuration=[class com.jfbank.sms.configuration.FeignConfiguration]}这样的数据。
                     *  这个地方比较关键,主要是因为后期对feign客户端的编码解码会用到自定义的类
                     */
                    //这个方法就是在ioc容器中塞入一个FeignClientSpecification对象,从而构建FeignContext子容器。
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));       
                    //重点分析这个
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);//对FeignClientFactoryBean对象生成一个BeanDefinition对象
        ...读取配置

        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 });
        //注册到beanDefinitionNames中对象
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);//
    }
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
64
65
66
67
68
       读过Dubbo源码的同学都知道,当在DubboNamespaceHandler中解析reference标签的时候,传入了一个ReferenceBean对象,把xml中配置的属性都塞到这个对象上,也是装到了beanDefinitionNames中,然后发现ReferenceBean类和FeignClientFactoryBean都实现了FactoryBean的接口,并且里面都有getObject()和getObjectType()方法。当接口调用到这个feign客户端的时候,会从IOC中读取这个FeignClientFactoryBean并且调用getObject方法。下面就是分析getObject方法:

 @Override
    public Object getObject() throws Exception {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //从上文中的子容器中获取编码器,解码器等自定义类,然后封装一个Feign.Builder类
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(this.url)) {//当@FeignClient没有配置url的时候
            String url;
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
            url += cleanPath();
            return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));//集成了ribbon客户端负载均衡,下一篇分析
        }
        //当@FeignClient配置了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 lod 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 targeter.target(this, builder, context, new HardCodedTarget<>(
                this.type, this.name, url));
    }
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
首先看配置了url的,指定了url的feignclient解析,一直跟着代码跟到了Feign.Builder#target()方法:

   public T target(Target target) {
      return build().newInstance(target);
    }

    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                               logLevel, decode404);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder,
                                  errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
       直接看ReflectiveFeign#newInstance()方法:

//ReflectiveFeign#newInstance()
    public T newInstance(Target target) {
                                                //动态代理的handler类目前穿进来的是ParseHandlersByName类,所以这里要看ParseHandlersByName#apply()直接看下一个方法
      Map nameToHandler = targetToHandlersByName.apply(target);
      Map methodToHandler = new LinkedHashMap();
      List defaultMethodHandlers = new LinkedList();

      for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
          continue;
        } else if(Util.isDefault(method)) {//默认方法会走到这里,比如toString(),hashCode()等方法
          DefaultMethodHandler handler = new DefaultMethodHandler(method);
          defaultMethodHandlers.add(handler);
          methodToHandler.put(method, handler);
        } else {//这里才是装配的调用类,上文分析到计息的handler是SynchronousMethodHandler#invoke()
          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);//jdk动态代理

      for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
      }
      return proxy;
    }


    //ParseHandlersByName#apply类,构建动态代理的handler
    public Map apply(Target key) {
        List metadata = contract.parseAndValidatateMetadata(key.type());
        Map result = new LinkedHashMap();
        for (MethodMetadata md : metadata) {
          BuildTemplateByResolvingArgs buildTemplate;
          if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
            buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);//通过自定义的encoder去解析参数
          } else if (md.bodyIndex() != null) {
            buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);//通过自定义的encoder去解析参数
          } else {
            buildTemplate = new BuildTemplateByResolvingArgs(md);
          }
          //创建handler,再看Factory#create()方法,下一个方法
          result.put(md.configKey(),
                     factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
        }
        return result;
      }
    //Factory#create(),构建一个SynchronousMethodHandler去处理请求,调用invoke方法
    public MethodHandler create(Target target, MethodMetadata md,
            RequestTemplate.Factory buildTemplateFromArgs,
            Options options, Decoder decoder, ErrorDecoder errorDecoder) {
        return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
                      logLevel, md, buildTemplateFromArgs, options, decoder,
                      errorDecoder, decode404);
    }

    //SynchronousMethodHandler#invoke()方法:实际调用的方法
    //@Override
    public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = buildTemplateFromArgs.create(argv);//构建requestTemplate对象
        Retryer retryer = this.retryer.clone();
        while (true) {
          try {
            return executeAndDecode(template);//下面不分析了,就是执行execute方法并且解码饭后返回值
          } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            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
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
64
65
66
67
68
69
70
71
72
73
Feign源码总结
       从读取注解到注入IOC容器,再到编码参数,发起请求,解码结果,整个封装过程都对我们开发带来了极大得便利,此文只是分析了feign带有url参数得解析方式,集成eureka和ribbon的在https://blog.csdn.net/lgq2626/article/details/80481514中做了分析。下面流程图总结下流程:
————————————————

https://www
https://www.jianshu.com/p/8cd5a001d256

https://www.zhihu.com/tardis/sogou/art/28593019

https://www.cnblogs.com/wang-meng/p/12174948.html
https://blog.csdn.net/lgq2626/article/details/80392914?locationNum=7&fps=1
Feign终极解析
https://mp.weixin.qq.com/s?__biz=MzI4ODQ3NjE2OA==&mid=2247484198&idx=1&sn=531f8e0acbfd4366415ff1cdabe51afb&chksm=ec3c9e41db4b17574ce2a2cff2e878e1948fcea600f639be960c47146a1a972c0e3692a6f115&scene=21

https://blog.csdn.net/woshilijiuyi/article/details/86663049

https://www.jianshu.com/p/a3ccbf608b73

文章来源: www.oschina.net,作者:xiaomin0322,版权归原作者所有,如需转载,请联系作者。

原文链接:https://my.oschina.net/xiaominmin/blog/4305597

登录后可下载附件,请登录或者注册

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:huaweicloud.bbs@huawei.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
评论文章 //点赞 收藏 0
点赞
分享文章到微博
分享文章到朋友圈

上一篇:温故知新-多线程-深入剖析AQS

下一篇:美团 MySQL 数据库巡检系统的设计与应用

评论 (0)


登录后可评论,请 登录注册

评论