• 最好的Http客户端--Feign 源码分析


    前面介绍了Feign的使用(Feign的使用),

    Feign整合Hystrix(Feign整合Hystrix) ,

    Feign整合(Feign整合Ribbon负载均衡),

    Feign的性能优化(Feign性能优化),

    现在介绍下Feign的源码。

    通过前面的使用过程,@EnableFeignClients和@FeignClient实现了Feign的功能

    一、@EnableFeignClients

    1、@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})

    2、FeignClientsRigistar实现了ImportBeanDefinitionRegistrar接口

    class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
    		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware{
            
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata,
    			BeanDefinitionRegistry registry) {
                    // 1.针对那些在@EnableFeignClients中添加了defaultConfiguration属性的进行操作
                    // 将这些类定义的bean添加到容器中
    		registerDefaultConfiguration(metadata, registry);
            
                  // 2.注册那些添加了@FeignClient的类或接口
    		registerFeignClients(metadata, registry);
    	}
    
        
    

      

    3、registerFeignClients(metadata, registry);方法

     public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {    
           ClassPathScanningCandidateComponentProvider scanner = this.getScanner();       
    
           scanner.setResourceLoader(this.resourceLoader);
            Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
            //@FeignClient注解过滤器
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
            Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
            //1、扫描包下所有带有@FeignClient注解的类
        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);
            }
    
        //2、针对所有带有@FeignClient注解的类或接口分别封装
        //并将类或接口注入到Spring中
            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);
                //2.1 注册其Configuration类(如果有的话)
                        this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                        //2.2将类或接口注入到Spring中
                this.registerFeignClient(registry, annotationMetadata, attributes);
                    }
                }
            }
    
        }
    

      从这里可知,@EnableFeignCliengs注解的主要功能就是把带有@FeignClient注解的类或接口注册到Spring容器中

      

     4、registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes)方法分析

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
          //1、获取类名称,这里为com.example.springclouddeep.consumer.feign.ProviderApi
           String className = annotationMetadata.getClassName();
          //2、BeanDefinitionBuilder 的作用是构建一个BeanDefinition。BeanDefinition的类为FeignClientFactoryBean
           BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
           this.validate(attributes);
          //添加FeignClientFactoryBean的属性,这些属性都是我们在@FeignClient中定义的属性
           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);
          //设置别名 name就是我们在@FeignClient中定义的name属性。 这里alias值为hello-service-providerFeignClient
           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;
           }
     
         //5、定义BeanDefinitionHolder,这里beanDefinition中的beanClass为           //org.springframework.cloud.openfeign.FeignClientFactoryBean<br>        
         //className为com.example.springclouddeep.consumer.feign.ProviderApi,类为FeignClientFactoryBean
           BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
           BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
       }
    

           通过分析可知:我们最终是向Spring中注册了一个bean,bean的名称就是类或接口的名称(这里com.example.springclouddeep.consumer.feign.ProviderApi,.ProviderApi代码参考Feign整合Hystrix),bean的实现类是FeignClientFactoryBean,其属性设置就是我们在@FeignClient中定义的属性。那FeignClientFactoryBean有什么作用呢?我们下面再来分析

    总结: 

    1)@EnableFeignClients注解类将FeignClientsRegistrar注册到Spring中

    2)FeignClientsRegistrar类的主要作用是扫码包路径下的所有类,将带有@FeignClient注解的类或接口注册到Spring中

    3)如何注册带有@FeignClient的类或接口呢? 就是生成一个BeanDefinitionHolder类,beanName为@FeignClient所在接口名称,beanDefinition为FeignClientFactoryBean,并将@FeignClient的属性添加到FeignClientFactoryBean中。

    二、FeignClientFactoryBean

     FeignClientFactoryBean实现了FactoryBean接口,当从ApplicationContext中获取该bean的时候,实际上调用的是getObject()方法

    class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    	
       public Object getObject() throws Exception {
            return this.getTarget();
         }
    
        <T> T getTarget() {
    	//1、获取容器中的FeignContext实现,默认实现在FeignAutoConfiguration类中
            FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
    	//2、使用构造器模式来构建一个Feign,后面详细介绍
            Builder builder = this.feign(context);
    	//3、判断是否有指定URL
            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();
    	    //关键方法,后面详细介绍	
                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();
                    }
    
                    if (client instanceof FeignBlockingLoadBalancerClient) {
                        client = ((FeignBlockingLoadBalancerClient)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));
            }
        }
    

      

    1、构建Builder

     protected Builder feign(FeignContext context) {
            FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
            Logger logger = loggerFactory.create(this.type);
            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));
            this.configureFeign(context, builder);
            return builder;
        }
    
    
        protected void configureFeign(FeignContext context, Builder builder) {
            FeignClientProperties properties = (FeignClientProperties)this.applicationContext.getBean(FeignClientProperties.class);
            if (properties != null) {
                if (properties.isDefaultToProperties()) {
                    this.configureUsingConfiguration(context, builder);
                    this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
                    this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
                } else {
                    this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
                    this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
                    this.configureUsingConfiguration(context, builder);
                }
            } else {
                this.configureUsingConfiguration(context, builder);
            }
    
        }
    
    
    
        protected void configureUsingConfiguration(FeignContext context, Builder builder) {
    	//下面的几项getOptional()主要功能就是从ApplicationContext中获取对应类的实现
    	//这些类可以自定义,默认从FeignAutoConfiguration中获取对应的bean
            Level level = (Level)this.getOptional(context, Level.class);
            if (level != null) {
                builder.logLevel(level);
            }
    
            Retryer retryer = (Retryer)this.getOptional(context, Retryer.class);
            if (retryer != null) {
                builder.retryer(retryer);
            }
    
            ErrorDecoder errorDecoder = (ErrorDecoder)this.getOptional(context, ErrorDecoder.class);
            if (errorDecoder != null) {
                builder.errorDecoder(errorDecoder);
            }
    
            Options options = (Options)this.getOptional(context, Options.class);
            if (options != null) {
                builder.options(options);
            }
    
            Map<String, RequestInterceptor> requestInterceptors = context.getInstances(this.contextId, RequestInterceptor.class);
            if (requestInterceptors != null) {
                builder.requestInterceptors(requestInterceptors.values());
            }
    
            QueryMapEncoder queryMapEncoder = (QueryMapEncoder)this.getOptional(context, QueryMapEncoder.class);
            if (queryMapEncoder != null) {
                builder.queryMapEncoder(queryMapEncoder);
            }
    
            if (this.decode404) {
                builder.decode404();
            }
    
            ExceptionPropagationPolicy exceptionPropagationPolicy = (ExceptionPropagationPolicy)this.getOptional(context, ExceptionPropagationPolicy.class);
            if (exceptionPropagationPolicy != null) {
                builder.exceptionPropagationPolicy(exceptionPropagationPolicy);
            }
    
        }
    

     

    2、获取负载均衡后的方法 protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) 

    protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
            //1、获取Client的实现类,默认为LoadBalancerFeignClient类
    	//实现在FeignRibbonClientAutoConfiguration中
    	Client client = (Client)this.getOptional(context, Client.class);
            if (client != null) {
    	    //2、将LoadBalancerFeignClient包装到Feign.Builder
                builder.client(client);
    	    //3、获取ApplicationContext中的Target实现
    	    //默认实现为HystrixTargeter,实现在FeignAutoConfiguration类中
                Targeter targeter = (Targeter)this.get(context, Targeter.class);
    	    //4、重点是这里
                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?");
            }
        }
    
    
    //HystrixTargeter.target
        public <T> T target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget<T> target) {
    	//feign不是feign.hystrix.HystrixFeign.Builder,直接返回return feign.target(target)
            if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
                return feign.target(target);
            } else {
                feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder)feign;
                String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() : factory.getContextId();
                SetterFactory setterFactory = (SetterFactory)this.getOptional(name, context, SetterFactory.class);
                if (setterFactory != null) {
                    builder.setterFactory(setterFactory);
                }
    
                Class<?> fallback = factory.getFallback();
                if (fallback != Void.TYPE) {
                    return this.targetWithFallback(name, context, target, builder, fallback);
                } else {
                    Class<?> fallbackFactory = factory.getFallbackFactory();
                    return fallbackFactory != Void.TYPE ? this.targetWithFallbackFactory(name, context, target, builder, fallbackFactory) : feign.target(target);
                }
            }
        }
    
    //feign.Feign.Builder.target(target)
    public <T> T target(Target<T> target) {
         return this.build().newInstance(target);
     }
    
     
    //feign.Feign.Builder.build()
    public Feign build() {
                Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
                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);
            }
    
    //ReflectiveFeign.newInstance(target)
     public <T> T newInstance(Target<T> target) {
    	//1、分析出具体方法和对应的Handler处理
            Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
            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, nameToHandler.get(Feign.configKey(target.type(), method)));
                    }
                }
            }
    
    	//2、由factory创建一个InvocationHandler,实现为FeignInvocationHandler
    	//target为HardCodedTarget
            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;
        }
    

     由以上分析可知,getObject()具体返回的是一个代理类,具体为FeignInvocationHandler 

    3、FeignInvocationHandler

    static class FeignInvocationHandler implements InvocationHandler {
            
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (!"equals".equals(method.getName())) {
                    if ("hashCode".equals(method.getName())) {
                        return this.hashCode();
                    } else {
    		    //非Object方法,则执行this.dispatch.get(method)).invoke(args)
    		    //dispath为map,方法的实现类为SynchronousMethodHandle。
    		    //我们接下来分析SynchronousMethodHandle.invoke(args)
                        return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
                    }
                } else {
                    try {
                        Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                        return this.equals(otherHandler);
                    } catch (IllegalArgumentException var5) {
                        return false;
                    }
                }
            }
    
            
          
        }
    

      1)、SynchronousMethodHandle.invoke(args)

     public Object invoke(Object[] argv) throws Throwable {
    	//1、根据请求参数创建一个RequestTemplate 
            RequestTemplate template = this.buildTemplateFromArgs.create(argv);
            Options options = this.findOptions(argv);
    	//2、用户定义的重试策略
            Retryer retryer = this.retryer.clone();
    
            while(true) {
                try {
    		//下面介绍这个方法
                    return this.executeAndDecode(template, options);
                } catch (RetryableException var9) {
                    RetryableException e = var9;
    
                    try {
                        retryer.continueOrPropagate(e);
                    } catch (RetryableException var8) {
                        Throwable cause = var8.getCause();
                        if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                            throw cause;
                        }
    
                        throw var8;
                    }
    
                    if (this.logLevel != Level.NONE) {
                        this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                    }
                }
            }
        
    

      

    2)executeAndDecode(template, options)

    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
            //分装请求信息 
    	Request request = this.targetRequest(template);
            if (this.logLevel != Level.NONE) {
                this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
            }
    
            long start = System.nanoTime();
    
            Response response;
            try {
    	    //执行。 client为LoadBalancerFeignClient,下面介绍exetute方法
                response = this.client.execute(request, options);
                response = response.toBuilder().request(request).requestTemplate(template).build();
            } catch (IOException var16) {
                if (this.logLevel != Level.NONE) {
                    this.logger.logIOException(this.metadata.configKey(), this.logLevel, var16, this.elapsedTime(start));
                }
    
                throw FeignException.errorExecuting(request, var16);
            }
        	//响应处理
            long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
            boolean shouldClose = true;
    
            Object var11;
            try {
                if (this.logLevel != Level.NONE) {
                    response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel, response, elapsedTime);
                }
    
                if (Response.class == this.metadata.returnType()) {
                    Response var19;
                    if (response.body() == null) {
                        var19 = response;
                        return var19;
                    }
    
                    if (response.body().length() != null && (long)response.body().length() <= 8192L) {
                        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                        Response var21 = response.toBuilder().body(bodyData).build();
                        return var21;
                    }
    
                    shouldClose = false;
                    var19 = response;
                    return var19;
                }
    
                Object result;
                if (response.status() >= 200 && response.status() < 300) {
                    if (Void.TYPE == this.metadata.returnType()) {
                        result = null;
                        return result;
                    }
    
                    result = this.decode(response);
                    shouldClose = this.closeAfterDecode;
                    var11 = result;
                    return var11;
                }
    
                if (!this.decode404 || response.status() != 404 || Void.TYPE == this.metadata.returnType()) {
                    throw this.errorDecoder.decode(this.metadata.configKey(), response);
                }
    
                result = this.decode(response);
                shouldClose = this.closeAfterDecode;
                var11 = result;
            } catch (IOException var17) {
                if (this.logLevel != Level.NONE) {
                    this.logger.logIOException(this.metadata.configKey(), this.logLevel, var17, elapsedTime);
                }
    
                throw FeignException.errorReading(request, response, var17);
            } finally {
                if (shouldClose) {
                    Util.ensureClosed(response.body());
                }
    
            }
    
            return var11;
        }
    

      

    3) LoadBalancerFeignClient的exetute方法

    public Response execute(Request request, Options options) throws IOException {
            try {
    	    //1、获得URI
                URI asUri = URI.create(request.url());
                String clientName = asUri.getHost();
                URI uriWithoutHost = cleanUrl(request.url(), clientName);
                //2、封装RibbonRequest请求
    	    RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
                //3、封装请求参数信息
    	    IClientConfig requestConfig = this.getClientConfig(options, clientName);
                //4、执行请求,并信息负载均衡
    	    1)lbClient(clientName)获得执行类。这里为FeignLoadBalancer
    	    2)FeignLoadBalancer.executeWithLoadBalancer() 执行请求。 下面介绍
    	    3)toResponse()获得响应
    	    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);
                }
            }
        }
    

      

    4) FeignLoadBalancer.executeWithLoadBalancer()执行请求

    public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
            LoadBalancerCommand command = this.buildLoadBalancerCommand(request, requestConfig);
    
            try {
    	    //这里是Hystrix的相关代码
                return (IResponse)command.submit(new ServerOperation<T>() {
                    public Observable<T> call(Server server) {
                        URI finalUri = AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());
                        ClientRequest requestForServer = request.replaceUri(finalUri);
    
                        try {
    			//执行ribbon负载均衡器
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } catch (Exception var5) {
                            return Observable.error(var5);
                        }
                    }
                }).toBlocking().single();
            } catch (Exception var6) {
                Throwable t = var6.getCause();
                if (t instanceof ClientException) {
                    throw (ClientException)t;
                } else {
                    throw new ClientException(var6);
                }
            }
        }
    

      总结:

      1、FeignClientFactoryBean.getObject()方法返回的是一个代理类,InvocationHandler中包含类中每个方法对应的MethodHandler,也就是SynchronousMethodHandler,方法真正执行就是SynchronousMethodHandler.invoke()方法

      2.LoadBalancerFeignClient.execute()方法进行业务的处理,在这一步操作中就用到了ribbon和Hystrix功能

  • 相关阅读:
    XAMPP安装过程中,出现的问题
    Javascript的数据类型和转换
    Vue组件之间的通信
    vue2.0在页面中自定义组件模块,以及页面与组件之间的数据传递
    本地起一个https服务器
    开发人员初始化操作
    添加环境变量
    公钥~gitlab~免密登录
    class继承随笔
    阿里云ECS随笔
  • 原文地址:https://www.cnblogs.com/linlf03/p/12555634.html
Copyright © 2020-2023  润新知