springcloud原理篇——ribbon/feign

ribbon简单点讲就是通过@LoadBalanced去负载到相应的服务中去,其中通过http(一般配合RestTemplate)请求去访问提供者接口,ribbon通过拦截器/代理将服务名等替换为ip等信息进行访问,如果提供者存在集群情况,ribbon会根据相应的算法(默认轮询、随机、权重等)进行负载均衡。

而feign就是对于ribbon的一个简单封装。

一、Ribbon源码分析:

1、以LoadBalancerAutoConfiguration为入口(找这些组件一般以AutoConfiguration等默认配置类为入口)

这个配置类就是初始化几个bean。然后用restTemplateCustomizers去定制所有我们加了@LoadBalanced注解的restTemplate

定制的过程:将我们注入的restTemplate的拦截器放进拦截器集合list中。因为之后使用restTemplate会对拦截器集合进行统一拦截处理。(即之后调用restTemplate会处理其拦截方法)

//简单点就是将使用了@LoadBalanced的restTemplate的拦截器放进拦截器集合中,使得其拦截器在使用restTemplate时生效

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {

  //存放了所有被@LoadBalanced修饰的restTemplate
    @LoadBalanced
    @Autowired(required = false )
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    public LoadBalancerAutoConfiguration() {
    }
//1、SmartInitializingSingleton
//该bean会用restTemplateCustomizers定制所有的restTemplate

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> {
            restTemplateCustomizers.ifAvailable((customizers) -> {
                Iterator var2 = this.restTemplates.iterator();

                while(var2.hasNext()) {
                    RestTemplate restTemplate = (RestTemplate)var2.next();
                    Iterator var4 = customizers.iterator();

                    while(var4.hasNext()) {
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
                        customizer.customize(restTemplate);
                    }
                }

            });
        };
    }

//2、RestTemplateCustomizer 
//该bean用于定制restTemplate。定制的逻辑是将LoadBalancerInterceptor拦截器封装到restTemplate的拦截器集合去(之后restTemplate会对拦截器集合进行统一拦截处理处理)
   
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }

//3、LoadBalancerInterceptor ,这个拦截器就是我们去替换ip等请求路径的拦截器    

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    static class LoadBalancerInterceptorConfig {
        LoadBalancerInterceptorConfig() {
        }

        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }
}

2、下面我们直接去看看其拦截器的实现:拦截器一般就看intercept方法

我们在使用restTemplate时:

restTemplate.postForObject("http://cloud-payment-service"+"/payment/create",payment, CommonResult.class)

其中URI即截取上面的请求路径,而servername则是获取服务名。然后将服务名称放进execute方法。(后面会根据注册中心获取到相应服务名的真实ip端口等信息)

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }

这是我们进入excute方法,看看是怎么进行获取真实服务ip等信息的

 public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
      //1、获取一个负载均衡器
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
     //2、多个实例时,根据负载均衡器的算法选择出一个实例出来
        Server server = this.getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
            return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
        }
    }
2.1、那么我们需要进去看看他是怎么获取到一个负载均衡器的(这里我们不进去该方法,因为最后调用的是一个接口,所以我们直接去找他的实现类)
查找RibbonClientConfiguration配置类

此时我们已经找到了默认的负载均衡器ZoneAwareLoadBalancer了,至于该负载均衡器是怎么进行负载均衡的?我们后面讲

@Configuration
@EnableConfigurationProperties
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
   
    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
//如果配置文件有配置使用哪个负载均衡器就优先使用配置文件的。
//否则则使用ZoneAwareLoadBalancer
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
    }

//加载负载均衡规则rule
@Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
//先从配置文件中拿
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
//没有则使用默认的负载均衡规则
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing());
    }

    
}

2.2、多个实例时,根据负载均衡器的算法选择出一个实例出来

进入getServer方法:
这里调用了负载均衡器的loadBalancer.chooseServer方法,即选择一个实例出来。该chooseServer方法中会配合注册中心的信息去获取一个实例

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
        return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
    }

此时我们要搞清楚是怎么匹配注册中、怎么进行负载均衡的,这是就要回去负载均衡器中查看chooseServer方法是怎么实现的了。

2.3、进入默认的负载均衡器ZoneAwareLoadBalancer中,查看chooseServer方法是怎么实现负载均衡的?+怎么和nacos配合的?

2.3.1 、怎么实现负载均衡的?

public Server chooseServer(Object key) {
        if (ENABLED.get() && this.getLoadBalancerStats().getAvailableZones().size() > 1) {
            //省略
            }
        } else {

//进入该方法
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
    }

进入父类方法

public Server chooseServer(Object key) {
        if (this.counter == null) {
            this.counter = this.createCounter();
        }

        this.counter.increment();
        if (this.rule == null) {
            return null;
        } else {
            try {
                return this.rule.choose(key);
            } catch (Exception var3) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
                return null;
            }
        }
    }

进入rule.choose()方法。发现是一个接口,打开实现类发现就是所谓的8个负载均衡算法实现类

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

而我们默认的是走PredicateBasedRule这个实现类(轮询),(其实从RibbonClientConfiguration中注入的ribbonRule中使用的是默认的ZoneAvoidanceRule,而其实现又是PredicateBasedRule所以也可以再一次判断默认就是走PredicateBasedRule规则),进入看下该类:


public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
    public PredicateBasedRule() {
    }

    public abstract AbstractServerPredicate getPredicate();

    public Server choose(Object key) {
        ILoadBalancer lb = this.getLoadBalancer();
//这里使用轮询的算法
        Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        return server.isPresent() ? (Server)server.get() : null;
    }
}

进入轮询方法:

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {

//获取可以实例集合。这里在讲怎么配合nacos那里再讲怎么用
        List<Server> eligible = this.getEligibleServers(servers, loadBalancerKey);
//incrementAndGetModulo进行轮询算法
        return eligible.size() == 0 ? Optional.absent() : Optional.of(eligible.get(this.incrementAndGetModulo(eligible.size())));
    }

进去incrementAndGetModulo方法看看:

计算方法:current = this.nextIndex.get();      next = (current + 1) % modulo;  

这里我们拿到的是上一个next值,然后把我们现在的值current+1后取模然后cas存进去。下一次取的就是这次的next值。

例如我们有2台机器(分别代表0、1),则此时传进去的modulo是2。

第一次时:current为0,next为1。此时返回0回去,则代表访问第一台机器(0号机器)。

第二次时:corrent为1(上次的next),next为0,此时返回1,则代表访问第一台机器。一次轮询。

 private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextIndex.get();
            next = (current + 1) % modulo;
        } while(!this.nextIndex.compareAndSet(current, next) || current >= modulo);

        return current;
    }

到此怎么进行负载均衡的解析到此结束,下面要解析ribbon怎么去配合nacos拿到实例信息并且拦截替换请求地址的?

2.3.1、怎么配合nacos的?

在讲之前需要再说下ribbon和nacos client端的关系

其中ribbon自己也有自己对于服务实例的缓存列表,nacos client每次从nacos server拉取到服务列表后,ribbon会周期性的去nacos的client端更新相应的列表信息到ribbon本地缓存。

(riibon是可以自己去配置文件配置各个实例的ip信息,所以ribbon是可以不依赖注册中心/其他springcloud组件的)

其实在前面的类中就已经将serverlist(服务实例列表)存进负载均衡器中了

  @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {

//其中serverList即实例列表
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
    }

那我们不断的进入ZoneAwareLoadBalancer最后来到其父类DynamicServerListLoadBalancer,看器构造方法

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.isSecure = false;
        this.useTunnel = false;
        this.serverListUpdateInProgress = new AtomicBoolean(false);
        this.updateAction = new NamelessClass_1();
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter)filter).setLoadBalancerStats(this.getLoadBalancerStats());
        }

        this.restOfInit(clientConfig);
    }

进入restOfInit方法:
 

void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        this.setEnablePrimingConnections(false);

     //1、定时更新nacos的实例列表
        this.enableAndInitLearnNewServersFeature();

     //2、获取所有nacos实例列表
        this.updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections().primeConnections(this.getReachableServers());
        }

        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

2.3.1.1、此时我们看看怎么去更新nacos实例列表的?
进入其中的方法,其中调用了serverListUpdater.start(this.updateAction);方法,start方法不用进去看,他最后是一个接口,其实现是最后调用了updateAction的doUpdate方法

 public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", this.serverListUpdater.getClass().getSimpleName());
        this.serverListUpdater.start(this.updateAction);
    }
public synchronized void start(final UpdateAction updateAction) {
        if (this.isActive.compareAndSet(false, true)) {
            Runnable wrapperRunnable = new Runnable() {
                public void run() {
                    if (!PollingServerListUpdater.this.isActive.get()) {
                        if (PollingServerListUpdater.this.scheduledFuture != null) {
                            PollingServerListUpdater.this.scheduledFuture.cancel(true);
                        }

                    } else {
                        try {

//注意这个doUpdate的方法
                            updateAction.doUpdate();
                            PollingServerListUpdater.this.lastUpdated = System.currentTimeMillis();
                        } catch (Exception var2) {
                            PollingServerListUpdater.logger.warn("Failed one update cycle", var2);
                        }

                    }
                }
            };
//定时的去调用上面的doupdate方法

            this.scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(wrapperRunnable, this.initialDelayMs, this.refreshIntervalMs, TimeUnit.MILLISECONDS);
        } else {
            logger.info("Already active, no-op");
        }

    }

而update方法的实现类:(DynamicServerListLoadBalancer类中)其实现类中最后是调用了updateListOfServers方法。

public DynamicServerListLoadBalancer(IClientConfig clientConfig) {
        this.isSecure = false;
        this.useTunnel = false;
        this.serverListUpdateInProgress = new AtomicBoolean(false);

        class NamelessClass_1 implements UpdateAction {
            NamelessClass_1() {
            }

            public void doUpdate() {
                DynamicServerListLoadBalancer.this.updateListOfServers();
            }
        }

        this.updateAction = new NamelessClass_1();
        this.initWithNiwsConfig(clientConfig);
    }

updateListOfServers()?这个方法是不是很眼熟?这个就是上面获取所有nacos实例的方法(往上看)

那么周期更新nacos的实例最后还是去调用了获取nacos全部实例的方法。那么不就将定时更新和全部获取的分析合起来了。也就是说定时更新方法实际调用的还是获取全部实例的方法updateListOfServers

2.1.3.2,统一分析获取全部实例方法updateListOfServers

@VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList();
        if (this.serverListImpl != null) {

//1、获取nacos client的缓存列表
            servers = this.serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
            if (this.filter != null) {
                servers = this.filter.getFilteredListOfServers((List)servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
            }
        }

//2、将获取到的列表存到ribbon的本地缓存中

        this.updateAllServerList((List)servers);
    }

进入getUpdatedListOfServers方法,发现是个接口,找到其实现类NacosServerList,

public List<NacosServer> getUpdatedListOfServers() {
        return this.getServers();
    }

进入getServers,这里拿取到nacos的client端缓存的实例列表并返回回去

 private List<NacosServer> getServers() {
        try {

//从nacos的client端缓存中拿去列表信息
            List<Instance> instances = this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId, true);
            return this.instancesToServerList(instances);
        } catch (Exception var2) {
            throw new IllegalStateException("Can not get service instances from nacos, serviceId=" + this.serviceId, var2);
        }
    }

我们这个时候要回到前面的updateListOfServers方法中,其中有个updateAllServerList方法就是将这个信息存到ribbon的本地缓存中。

(注意,只获取健康的实例)

对于ribbon拿到信息怎么将uri由服务名换成ip端口等的就不用我多说了。

至此ribbon的解析到此结束

ribbon分析图:

二、ribbon的小总结

ribbon通过设置使用了@LoadBalanced的restTemplate的拦截器,当使用其restTemplate时会触发拦截器逻辑。ribbon会先初始化一个负载均衡器(其中包含服务实例列表+负载均衡规则(默认轮询,但可以自己配置或自定义))

对于ribbon怎么获取实例:ribbon本地也会维护一个缓存,会周期性的向nacos client中拉取健康的服务列表。(可以不依赖nacos,自己在配置文件中配置各个ip等,但一般不这么做)

对ribbon怎么进行负载均衡规则选取:如果由自定义/配置文件配置,默认先加载自定义/配置文件的,如果没有则使用轮询的规则。

ribbon在拿到实例信息时会将含有服务名的uri替换成ip/端口的uri,然后根据负载均衡规则发起http请求到相应的实例真实地址。

三、feign源码分析

feign的源码非常简单,就是使用代理将ribbon进行了封装。

@FeignClient(value = "cloud-payment-service",fallback = PaymentHystrixServiceImpl.class)//调用的服务名
@Component
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value="/payment/lb")
    public String getPaymentLB() throws InterruptedException;
}

启动类再加上个@EnableFeignClients注解

feign简单的讲就是根据ribbon拿到的实例信息,然后把上面的服务名+接口地址,然后帮我们把uri替换拼接,最后用http请求去访问。

???看到这里,是否就觉得,ribbon不是帮我们做了吗?没错,ribbon是有做这些,但是如果使用ribbon,我们需要去注入一个带有@LoadBalanced的restTemplate,然后再用这个restTemplate去自己发送请求(然后ribbon内部帮我们去修改uri)

当时feign不用我们去写这么多了,feign使用了声明式接口,帮我们把注入restTemplate和手动调用restTemplate的方法都给封装好了,我们只需要告诉他我们要调用哪个服务的哪个接口即可完成调用。

简单点讲,feign对于ribbon进行进一步封装,简化了我们的使用。

feign用的是okHttpclient

----------------------------------------------------------------------------------------------------

下面进行源码解析:

以@EnableFeignClients注解为入口

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};

    String[] basePackages() default {};

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

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

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

进入@Import({FeignClientsRegistrar.class})这个类,看到registerBeanDefinitions方法,该方法将@EnableFeignClients里配置的信息和@FeignClients的接口都注入spring容器中

注意:@EnableFeignClients如果没配置默认是同包下的扫描进来

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

//扫描@EnableFeignClients里配置的信息并注册
        this.registerDefaultConfiguration(metadata, registry);

//看名字即注册FeignClients,即我们写的feign的接口注册到spring容器中
        this.registerFeignClients(metadata, registry);
    }

进入下面的方法

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//扫描器
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);

//扫描所有feignclient注解的信息并放到容器中
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        Object basePackages;
        if (clients != null && clients.length != 0) {
            final Set<String> clientClasses = new HashSet();
            basePackages = new HashSet();
            Class[] var9 = clients;
            int var10 = clients.length;

            for(int var11 = 0; var11 < var10; ++var11) {
                Class<?> clazz = var9[var11];
                ((Set)basePackages).add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }

            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        } else {
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = this.getBasePackages(metadata);
        }

        Iterator var17 = ((Set)basePackages).iterator();

        while(var17.hasNext()) {
            String basePackage = (String)var17.next();
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            Iterator var21 = candidateComponents.iterator();

            while(var21.hasNext()) {
                BeanDefinition candidateComponent = (BeanDefinition)var21.next();
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                    String name = this.getClientName(attributes);
                    this.registerClientConfiguration(registry, name, attributes.get("configuration"));

//进入registerFeignClient方法
                    this.registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }

    }

进入registerFeignClient方法:
 

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();

//生成代理的工厂bean(FeignClientFactoryBean),并且将feignclient注解的相关信息给到该工厂bean,并且将其放进spring容器
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);

        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = this.getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(2);
        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

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

然后进入该工厂bean中,其中有个loadBalance方法利用jdk动态代理生成代理对象。


class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

 protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
        Client client = (Client)this.getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            Targeter targeter = (Targeter)this.get(context, Targeter.class);
            return targeter.target(this, builder, context, target);
        } else {
            throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
        }
    }

}

代码就不在深入,最后jdk动态代理会生成一个LoadBalancerFeignClient的代理对象。该类中有个execute方法,我们在使用feign接口调用时,实际是在使用LoadBalancerFeignClient的代理对象调用它的execute方法。

public class LoadBalancerFeignClient implements Client {
 
    public Response execute(Request request, Options options) throws IOException {
        try {
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);

//使用ribbonRequest去调用整合的ribbon从而实现的负载均衡
            RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
            IClientConfig requestConfig = this.getClientConfig(options, clientName);
            return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
        } catch (ClientException var8) {
            IOException io = this.findIOException(var8);
            if (io != null) {
                throw io;
            } else {
                throw new RuntimeException(var8);
            }
        }
    }

}

该execute方法就是整合了ribbon去实现的负载均衡。

其实ribbon也有一个LoadBalancerRibbonClient,不过ribbon是利用拦截器去调用的execute方法,而LoadBalancerFeignClient是利用jdk动态代理对象去调用的execute方法。

最后给个分析图:

四、Feign的小总结

feign通过动态代理得到一个代理工厂bean:FeignClientFactoryBean,该工厂bean最后会生成一个LoadBalancerFeignClient的代理对象,当我们去调用feign接口时,feign的代理对像会调用execute方法,将url进行拼接(含服务名的url,url的替换还是需要ribbon来完成),然后使用RibbonRequest去调用整合的Ribbon实现负载均衡。最后feign拿到了拼接好的url(替换了服务名的url)进行http请求(ribbon接到调用时,会通过拦截器去调用ribbon自身的execute方法从而实现负载均衡整个过程)(feign最后还是要用ribbon来替换服务名、服务列表信息等)

至此ribbon/feign解析到此结束,下篇要解析比较重要的nacos原理

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值