Spring-Cloud-Feign源码阅读(1)-FeignClient是如何初始化的

一、BeanDefinition和DefaultListableBeanFactory的关联

        BeanDefinition是对一个bean的描述,包括bean的各种信息,比如bean的类型、bean的属性、scope是单例还是原型、是否懒加载、init方法、destory方法等。博主这样理解,用@Bean注解定义一个bean时,bean的各种属性通常都是常量,BeanDefinition类的作用就是可以动态的去定义bean。

        DefaultListableBeanFactory类是Spring的核心类,有获取bean的各种方法。该类BeanDefinition的关联就是维护一个ConcurrentHashMap,key为beanName,value为BeanDefinition。该类中registerBeanDefinition的方法就是往ConcurrentHashMap里put一个值,这个过程称为bean的注册。该类的职责就是对所有bean的管理以及实例化。

二、FeignClient的注册器-FeignClientsRegistrar

        在Spring Boot应用中要启用Feign,要在SpringBootApplication类上面加上@EnableFeignClients注解。查看@EnableFeignClients注解源码,相比其他的普通注解多了一行@Import({FeignClientsRegistrar.class})。FeignClientsRegistrar类的对象也会被Spring容器管理。

package org.springframework.cloud.openfeign;

@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 {};
}

        FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以registerBeanDefinitions方法在应用启动时就执行。

package org.springframework.cloud.openfeign;

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    private ResourceLoader resourceLoader;
    private Environment environment;

    ...

    //应用启动时执行
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        this.registerDefaultConfiguration(metadata, registry);
        this.registerFeignClients(metadata, registry);
    }

    
    //将@EnableFeignClients注解里的defaultConfiguration配置
    private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
        //如果EnableFeignClients.defaultConfiguration,则会执行
        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }

            this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
        }

    }

    //注册FeignClient
    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //获取扫描器
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        //获取@EnableFeignClients注解上的配置
        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) {
            //如果@EnableFeignClients注解上的配置指定了clients
            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];
                //所有指定的clients的包路径都需要扫描
                ((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 {
            //如果@EnableFeignClients注解上的配置未指定clients,则扫描SpringBootApplication类的包以及子路径,并且是加了@FeignClient注解的类
            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");
                    //获取扫描到的类@FeignClient注解的配置
                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                    String name = this.getClientName(attributes);
                    //注册单个FeignClient的配置 
                    this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                    //注册单个@FeignClient类的定义
                    this.registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }

    }

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        //所有FeignClient的bean的类型指为FeignClientFactoryBean类型
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        //设置FeignClient的各种属性
        //获取@FeignClient配置的url  用于非微服务的请求url
        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        //获取@FeignClient配置的name  微服务服务名的转发
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = this.getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        //获取@FeignClient配置的name 用于请求状态码返回404情况下对response的编码转换
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        //获取@FeignClient配置的fallback 用于指定改FeignClient的熔断类
        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;
        }

        //注册FeignClient,将FeignClient的bean以及beanDefinition注册至DefaultListableBeanFactory,在实例化bean时如果bean的类型是FactoryBean则会继续调用FactoryBean的getObject方法生成bean
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

    //完成FeignClientSpecification bean的注册,该bean的类型是FeignClientSpecification是一个普通类型,则在bean实例化的时候,就会调用该类的构造方法生成实例
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        //注册FeignClientSpecification bean
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
        //添加构造参数
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
    }

    ...

}

        FeignClientsRegistrar类的主要职责是

        (1)获取FeignClient bean并为其指定生成对象的类FeignClientFactoryBean,同时设置一些请求地址、服务名、以及相关配置。

        (2)将@EnableFeignClients默认的Fegin配置以及@FeignClient的配置都以bean的方式初始化成FeignClientSpecification。

三、FeignClient的配置-FeignContext

        FeignContext是一个Bean,只将FeignClientSpecification类型的所有bean注入为属性。

package org.springframework.cloud.openfeign;

@Configuration
@ConditionalOnClass({Feign.class})
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
    @Autowired(
        required = false
    )
    private List<FeignClientSpecification> configurations = new ArrayList();

    public FeignAutoConfiguration() {
    }

    ...

    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }
}

        FeignContext继承了NamedContextFactory类,通过这种方式把初始化的FeignClientSpecification以FeignClient的name分隔从而生成一个包含所有FeignClient配置的FeignContext bean。

package org.springframework.cloud.openfeign;

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
    public FeignContext() {
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }
}
package org.springframework.cloud.context.named;


public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {
    //通过一个name将上下文隔离
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap();
    private Map<String, C> configurations = new ConcurrentHashMap();

    ...

    public void setConfigurations(List<C> configurations) {
        Iterator var2 = configurations.iterator();

        while(var2.hasNext()) {
            C client = (NamedContextFactory.Specification)var2.next();
            //结合FeignClientSpecification的代码,猜到FeignContext的key就是FeignClient的name
            this.configurations.put(client.getName(), client);
        }

    }

    //获取实例的时候,也先通过name获取响应的上下文
    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = this.getContext(name);
        return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0 ? context.getBean(type) : null;
    }

    ...

    public interface Specification {
        String getName();

        Class<?>[] getConfiguration();
    }
}

四、FeignClient的初始化-FeignClientFactoryBean

        FeignClientFactoryBean的getObject方法生成实例。

package org.springframework.cloud.openfeign;

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    //相关属性都是在bean注册时,通过set方法设置值
    private Class<?> type;
    private String name;
    private String url;
    //contextId在注册时,设置为FeignClient的name
    private String contextId;
    private String path;
    private boolean decode404;
    private ApplicationContext applicationContext;
    private Class<?> fallback;
    private Class<?> fallbackFactory;

    FeignClientFactoryBean() {
        this.fallback = Void.TYPE;
        this.fallbackFactory = Void.TYPE;
    }

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext = context;
    }

    //生成一个Builder
    protected Builder feign(FeignContext context) {
        //通过contextId和FeignContext获取FeignClient的配置
        FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);
        //生成FeignBuilder 主要包含的配置就是Feign的协议、日志等级配置、编码器、解码器等
        Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
        //获取feign.client.name相关配置并设置到builder里
        this.configureFeign(context, builder);
        return builder;
    }

    ...

    protected <T> T get(FeignContext context, Class<T> type) {
        T instance = context.getInstance(this.contextId, type);
        if (instance == null) {
            throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);
        } else {
            return instance;
        }
    }

    protected <T> T getOptional(FeignContext context, Class<T> type) {
        return context.getInstance(this.contextId, type);
    }

    protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
        //执行请求的Client 在FeignRibbonClientAutoConfiguration中初始化的,默认是LoadBalancerFeignClient
        Client client = (Client)this.getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            //FeignAutoConfiguration中Targeter 初始化为HystrixTargeter
            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?");
        }
    }

    //实例化FeignClient的方法
    public Object getObject() throws Exception {
        return this.getTarget();
    }

    <T> T getTarget() {
        //获取FeignContext 并生成Feign.builder
        FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
        Builder builder = this.feign(context);
        //如果@FeignClient的配置url没有值,微服务中url通常都没有值,都是用的name
        if (!StringUtils.hasText(this.url)) {
            if (!this.name.startsWith("http")) {
                this.url = "http://" + this.name;
            } else {
                this.url = this.name;
            }
            this.url = this.url + this.cleanPath();
            //调用loadBalance方法生成对象,将FeignClient的type, name, url创建一个HardCodedTarget对象
            return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));
        } else {
            if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
                this.url = "http://" + this.url;
            }

            String url = this.url + this.cleanPath();
            Client client = (Client)this.getOptional(context, Client.class);
            if (client != null) {
                if (client instanceof LoadBalancerFeignClient) {
                    client = ((LoadBalancerFeignClient)client).getDelegate();
                }

                builder.client(client);
            }

            Targeter targeter = (Targeter)this.get(context, Targeter.class);
            return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));
        }
    }
class DefaultFeignLoadBalancedConfiguration {
    DefaultFeignLoadBalancedConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Default((SSLSocketFactory)null, (HostnameVerifier)null), cachingFactory, clientFactory);
    }
}

public class FeignAutoConfiguration {

    ...

    @Configuration
    @ConditionalOnMissingClass({"feign.hystrix.HystrixFeign"})
    protected static class DefaultFeignTargeterConfiguration {
        protected DefaultFeignTargeterConfiguration() {
        }
        

        //如果没有HystrixFeign类,则Targeter  bean就是DefaultTargeter
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new DefaultTargeter();
        }
    }

    ...
    @Configuration
    @ConditionalOnClass(
        name = {"feign.hystrix.HystrixFeign"}
    )
    protected static class HystrixFeignTargeterConfiguration {
        protected HystrixFeignTargeterConfiguration() {
        }

        //如果HystrixFeign类,则Targeter  bean就是HystrixTargeter
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }
}

@Configuration
public class FeignClientsConfiguration {

    @Configuration
    @ConditionalOnClass({HystrixCommand.class, HystrixFeign.class})
    protected static class HystrixFeignConfiguration {
        protected HystrixFeignConfiguration() {
        }

        //如果feign.hystrix.enabled不是true,则FeignClientFactoryBean里获取的builder就是Feign.builder 而不是HystrixFeign.Builder
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            name = {"feign.hystrix.enabled"}
        )
        public Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }

}

         如果feign.hystrix.enabled不是true,则FeignClientFactoryBean里获取的builder就是Feign.builder 而不是HystrixFeign.Builder

class HystrixTargeter implements Targeter {
    HystrixTargeter() {
    }

    public <T> T target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget<T> target) {
        //根据Builder的类型判断是否需要熔断
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        } else {
            feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder)feign;
            SetterFactory setterFactory = (SetterFactory)this.getOptional(factory.getName(), context, SetterFactory.class);
            if (setterFactory != null) {
                builder.setterFactory(setterFactory);
            }

            Class<?> fallback = factory.getFallback();
            if (fallback != Void.TYPE) {
                return this.targetWithFallback(factory.getName(), context, target, builder, fallback);
            } else {
                Class<?> fallbackFactory = factory.getFallbackFactory();
                return fallbackFactory != Void.TYPE ? this.targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory) : feign.target(target);
            }
        }
    }

         以上都是获取各种配置以及根据是否开启熔断获取一个Builder。

      1.未开启熔断时, Feign.Builder

package feign;

public abstract class Feign {
    public Feign() {
    }

    ...

    public static class Builder {
       
        ...
 
        public <T> T target(Target<T> target) {
            return this.build().newInstance(target);
        }

        public Feign build() {
            //new一个 InvocationHandlerFactory
            Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
            //new一个Feign解析的处理器
            ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
            //生成ReflectiveFeign对象
            return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
        }

    }
}
package feign;

public class ReflectiveFeign extends Feign {
    private final ReflectiveFeign.ParseHandlersByName targetToHandlersByName;
    private final InvocationHandlerFactory factory;
    private final QueryMapEncoder queryMapEncoder;

    ReflectiveFeign(ReflectiveFeign.ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory, QueryMapEncoder queryMapEncoder) {
        this.targetToHandlersByName = targetToHandlersByName;
        this.factory = factory;
        this.queryMapEncoder = queryMapEncoder;
    }

    public <T> T newInstance(Target<T> target) {
        //通过HardCodedTarget里的type以及Contract里的协议解析成key为MethodHandler的configKey 由类型+方法名+参数类型组成  value为MethodHandler
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        
        //判断并过滤转换成key为Method value为MethodHandler的的类型
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method method = var5[var7];
            if (method.getDeclaringClass() != Object.class) {
                if (Util.isDefault(method)) {
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
                    methodToHandler.put(method, (MethodHandler)nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }
        
        //转换成InvocationHandler target指定类型,Map<Method, MethodHandler>根据方法分发
        InvocationHandler handler = this.factory.create(target, methodToHandler);
        //生成代理对象
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }

        return proxy;
    }

    ...
}

        2.未开启熔断时, HystrixFeign.Builder

           待补充

五、总结

FeignClient初始化过程如下:

(1)由FeignClientsRegistrar选择FeignClient,将FeignClient bean的类型通过BeanDefinition指定为FeignClientFactoryBean,并注册到DefaultListableBeanFactory由其管理。

(2)因为FeignClientFactoryBean实现了FactoryBean<Object>接口,DefaultListableBeanFactory在实例化FeignClient时,会调用FeignClientFactoryBean的getObject方法。

(3)通过FeignContext获取不同的FeignClient的配置以及是否开启熔断生成Feign.builder(HystrixFeign.Builder继承了Feign.Builder)。

(4)Feign.Builder创建调用处理器工厂InvocationHandlerFactory、语法解析ParseHandlersByName对象生成ReflectiveFeign对象。

(5)ReflectiveFeign类newInstance方法,将FeignClient的类通过contract解析成MethodHandler,再由InvocationHandlerFactory生成FeignInvocationHandler对象,再生成代理对象。

其他:

(1)可以通过BeanDefinition动态注册bean。

(2)集成NamedContextFactory可以通过指定的key隔离上下文,完成不同的bean可以获取到不同的配置。

(3)开启熔断后,FeignClientFactoryBean的builder就是HystrixFeign.Builder,实现了Feign.Builder,重写了其build方法,并且也有相应的调用处理器HystrixInvocationHandler和HystrixDelegatingContract(基于现有的Contract修改了返回类型)。

(4)feignClient的初始化对象是基于jdk的动态代理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
An active internet connection Java 8+ Docker Maven Git client Chapter 1, Introduction to Microservices, will introduce you to the microservices architecture, cloud environment, etc. You will learn the difference between a microservice based application and a monolith application while also learning how to migrate to a microservices application. Chapter 2, Spring for Microservices, will introduce you Spring Boot framework. You will learn how to effictively use it to create microservice application. We will cover such topics like creating REST API using Spring MVC annotations, providing API documentation using Swagger2, and exposing health checks and metrics using Spring Boot Actuator endpoints. Chapter 3, Spring Cloud Overview, will provide a short description of the main projects being a part of Spring Cloud. It will focus on describing the main patterns implemented by Spring Cloud and assigning them to the particular projects. Chapter 4, Service Discovery, will describe a service discovery pattern with Spring Cloud Netflix Eureka. You will learn how to run Eureka server in standalone mode and how to run multiple server instances with peer-to-peer replication. You will also learn how to enable discovery on the client side and register these clients in different zones. Chapter 5, Distributed Configuration with Spring Cloud Config, will describe how use distributed configuration with Spring Cloud Config in your applications. You will learn how to enable different backend repositories of property sources and push change notifications using Spring Cloud Bus. We will compare discovery first bootstrap and config first bootstrap approaches to illustrate integration between discovery service and configuration server. Chapter 6, Communication Between Microservices, will describe the most important elements taking a part in an inter-service communication: HTTP clients and load balancers. You will learn how to use Spring RestTemplate, Ribbon, and Feign clients with or without

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值