原理

  1. 主程序入口添加@EnableFeignClients注解开启对Feign Client扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClient注解。
  2. 当程序启动时,会进行包扫描,扫描所有@FeignClient注解的类,并将这些信息注入Spring IoC容器中。当定义的Feign接口中的方法被调用时,通过JDK的代理方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装了HTTP请求需要的全部信息,如请求参数名、请求方法等信息都在这个过程中确定。
  3. 然后由RequestTemplate生成Request,然后把这个Request交给client处理,这里指的Client可以是JDK原生的URLConnection、Apache的Http Client,也可以是Okhttp。最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用。

压缩

服务端provider配置

#服务端开启压缩
server.compression.enabled=true

调用方consumer配置

#配置请求GZIP压缩
feign.compression.request.enabled=true
#配置响应GZIP压缩
feign.compression.response.enabled=true
#单位是B
feign.compression.request.min-request-size=100

源码

两大流程

  1. 程序启动时:接口的bean实例时如何初始化的,被@FeignClient修饰的接口类。构建Bean。

  2. 网络调用时:调用上面类的方法时如何发送网络请求。网络请求。

    源码分为两部分入手:一部分初始化bean实例,一部分发送网络请求。

核心组件

FeignClientFactoryBean是创建@FeignClient修饰的接口类Bean实例的工厂类;

FeignContext是配置组件的上下文环境,保存着相关组件的不同实例,这些实例由不同的FeignConfiguration配置类构造出来;想象一下如图:

SynchronousMethodHandler是MethodHandler的子类,可以在FeignClient相应方法被调用时发送网络请求,然后再将请求响应转化为函数返回值进行输出。

流程

  1. 启动时会首先进行相关的BeanDefinition的动态注册,
  2. 然后当Spring容器注入相关实例时会进行实例初始化,
  3. 最后当feign接口类实例函数调用时会发送网络请求。

入口

spring-cloud-starter-openfeign-2.1.2.RELEASE.jar
中基于spring-cloud-openfeign-core-2.1.2.RELEASE.jar
自动注入一大堆:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

先记着FeignAutoConfiguration

开关

打开源码看。

从开关开始@EnableFeignClients,点进去此注解。

@EnableFeignClients有三个作用:

一是引入FeignClientsRegistrar;

@Import

@Import(FeignClientsRegistrar.class)

DANGER

在@Import注解的参数中可以填写类名,例如@Import(Abc.class),根据类Abc的不同类型,spring容器有以下四种处理方式:

  1. 如果Abc类实现了ImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法;
  2. DeferredImportSelector是ImportSelector的子类,如果Abc类实现了DeferredImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法,和ImportSelector的实例不同的是,DeferredImportSelector的实例的selectImports方法调用时机晚于ImportSelector的实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用(具体逻辑在ConfigurationClassParser.processDeferredImportSelectors方法中)
  3. 如果Abc类实现了ImportBeanDefinitionRegistrar接口,spring容器就会实例化Abc类,并且调用其registerBeanDefinitions方法;
  4. 如果Abc没有实现ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一个,spring容器就会实例化Abc类;

我们此时的FeignClientsRegistrar,属于第三种情况。

二是指定扫描FeignClient的包信息,就是指定FeignClient接口类所在的包名;

value(),basePackages(),basePackageClasses() ,默认都为空,如果要指定,可以在注解中加。

三是指定FeignClient接口类的自定义配置类。

defaultConfiguration(),看注释:默认是:FeignClientsConfiguration,

clients(),罗列被@FeignClient修饰的类

FeignClientsRegistrar

上面提到的org.springframework.cloud.openfeign.FeignClientsRegistrar implements ImportBeanDefinitionRegistrar。

FeignClientsRegistrar是ImportBeanDefinitionRegistrar的子类,Spring用ImportBeanDefinitionRegistrar来动态注册BeanDefinition。OpenFeign通过FeignClientsRegistrar也能实现动态注册beanfefinition的功能。即处理@FeignClient修饰的FeignClient接口类,将这些接口类的BeanDefinition注册到Spring容器中,这样就可以使用@Autowired等方式来自动装载这些FeignClient接口类的Bean实例。

BeanDefinition
Spring使用BeanDefinition来描述bean

BeanDefinitionBuilder是Builder模式的应用。通过这个类我们可以方便的构建BeanDefinition的实例对象
建造者模式:https://www.runoob.com/design-pattern/builder-pattern.html

其实就是将Bean的定义信息存储到这个BeanDefinition相应的属性中,后面对Bean的操作就直接对BeanDefinition进行,例如拿到这个BeanDefinition后,可以根据里面的类名、构造函数、构造函数参数,使用反射进行对象创建。
打断点可以,看到启动的时候执行到这个方法。
class FeignClientsRegistrar
中:
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 从开关EnableFeignClients的属性值来构建Feign的自定义Configuration进行注册。看其代码的第一句。
		registerDefaultConfiguration(metadata, registry);
		// 注册被@FeignClient的修饰的接口类的信息。
		registerFeignClients(metadata, registry);
	}

两个功能:
1、注册@EnableFeignClients提供的自定义配置类中的相关bean。此时的配置类是被 @Configuration注解修饰的配置类,它会提供一系列组装FeignClient的各类组件实例,比如Decoder、Encoder等。
2、根据@EnableFeignClients提供的包信息扫描@FeignClient修饰的接口类,并注册。
	

registerDefaultConfiguration方法

点第一个方法进去,registerDefaultConfiguration。
	private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
			// 获取@EnableFeignClients中属性键值对。
		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();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
debug看出name是:default.com.online.taxi.passenger.ApiPassengerApplication	

点进去registerClientConfiguration,此方法进行BeanDefinitionRegistry注册。
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		// 先生成beanDefinition。
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
		
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		// 注册beandefinition
		registry.registerBeanDefinition(
				name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}

上面方法第一个参数:BeanDefinitionRegistry是Spring框架中用于动态注册BeanDefinition信息的接口,调用其registerBeanDefinition方法可以将BeanDefinition注册到Spring容器中,此方法第一个参数是beanName,name属性就是注册BeanDefinition的名称(default.com.online.taxi.passenger.ApiPassengerApplication)。 

上面FeignClientSpecification
class FeignClientSpecification implements NamedContextFactory.Specification
FeignClientSpecification持有自定义配置类提供的组件实例,供OpenFeign使用。

Spring Cloud框架使用NamedContextFactory创建一系列的运行上下文(ApplicationContext),来让对应的Specification在这些上下文中创建实例对象。这样使得各个子上下文中的实例对象相互独立,互不影响,可以方便地通过子上下文管理一系列不同的实例对象。意思就是:此处的FeignClientSpecification持有的自定义配置类的组件在feign的上下文中和其他上下文独立。feign组件就是feign的组件,和其他组件区分开。
        
        NamedContextFactory有三个功能,
        一是创建AnnotationConfigApplicationContext子上下文;
        二是在子上下文中创建并获取Bean实例;
        三是当子上下文消亡时清除其中的Bean实例(通过其父类DisposableBean的destory实现)。
        我们看NamedContextFactory的实现类有:FeignContext。
        构造方法中有:super(FeignClientsConfiguration.class, "feign", "feign.client.name");
        可以看出FeignContext存储了各类 openFeign的 组件实例。
        
        此时我们发现一个类FeignContext。
        
        而FeignContext组件实例是通过:FeignAutoConfiguration自动配置的。
        我们看到在org.springframework.cloud.openfeign.FeignAutoConfiguration中,定义了一个bean:
    @Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();
		// 此时将上面注册的FeignClientSpecification设置到feignContext的configuration中。
		context.setConfigurations(this.configurations);
		return context;
	}
看构造函数:
public FeignContext() {
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}
	发现了上面所说开关中的默认配置FeignClientsConfiguration类。
	     	   	
上面就是:将@EnableFeignClients注解中的自定义配置注册到spring中。
	

registerFeignClients

第二个方法。注册feignclient接口的beanDefinition。
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
				
				注册被@FeignClient的修饰的接口类的信息。
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);

此方法中有两个for循环:
for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {

第一层循环是 项目主包,第二层循环是循环所有@FeignClient注解修饰的接口。找出来后注册到spring,beandefinition。			

小结:

FeignClientsRegistrar的registerBeanDefinitions方法主要做了两个事情:

一是注册@EnableFeignClients提供的自定义配置类中的相关Bean实例,

二是注册@FeignClient注解修饰的FeignCleint接口类,然后进行Bean实例注册。

@EnableFeignClients的自定义配置类是被@Configuration注解修饰的配置类,它会提供一系列组装FeignClient的各类组件实例。这些组件包括:Client、Targeter、Decoder、Encoder和Contract等


实例初始化

上面讲了BeanDefinition注册。下面进行实例初始化。

在spring-cloud-openfeign-core-2.1.2.RELEASE中,org.springframework.cloud.openfeign.FeignClientFactoryBean。Spring容器通过调用它的getObject来获取对应的bean实例。此时的实例是指被@FeignClient修饰的接口类的实例。点getTarget方法进去。

意思:每个feignclient的实例都通过此工厂类,获取对应的实例。

Client client = getOptional(context, Client.class);获取client对象。

org.springframework.cloud.openfeign.Targeter有两个实现类:DefaultTargeter和HystrixTargeter

主要说DefaultTargeter。

class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

}

其中:Feign.Builder feign,作用:负责生成被@FeignClient修饰的接口类实例,通过Java的反射机制,生成实例,当feignclient的方法被调用时,InvocationHandler的回调函数会被调用。在回调函数中发送网络请求。

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

    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }

ReflectiveFeign有个newInstance方法,2个功能:1:扫描feignclient接口类的所有函数,生成对应的Handler2:用Proxy生成feignclient的实例对象。
@SuppressWarnings("unchecked")
  @Override
  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else 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)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

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

此方法中apply方法作用:通过Contract的parseAndValidatateMetadata方法获得了接口类中所有方法的元数据,这些信息中包含了每个方法所对应的网络请求信息。比如说请求的路径(path)、参数(params)、头部(headers)和body。接下来apply方法会为每个方法生成一个MethodHandler。

此方法中factory.create作用:创建接口类的实例,然后通过bindTo将InvocationHandler绑定到接口类实例上,用于处理函数调用。

函数调用

在配置和实例生成结束之后,就可以直接使用FeignClient接口类的实例,调用它的函数来发送网络请求。在调用其函数的过程中,由于设置了MethodHandler,所以最终函数调用会执行SynchronousMethodHandler的invoke方法。在该方法中,OpenFeign会将函数的实际参数值与之前生成的RequestTemplate进行结合,然后发送网络请求。

feign.SynchronousMethodHandler方法中

@Override
  public Object invoke(Object[] argv) throws Throwable {
  // 生成请求类似于:GET /uri HTTP/1.1
  argv:[BaseOrder(startLatitude=labore et laboris eiusmod, startLongitude=ut cupidatat, endLatitude=sit sint111, endLongitude=Excepteur Lorem reprehend)]
  
  template:
  POST /forecast/single HTTP/1.1
Content-Length: 148
Content-Type: application/json;charset=UTF-8


    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

构建RequestTemplate,用RequestTemplate.Factory.create,构建url,queryMap,headerMap等。

上面提到一个:executeAndDecode点进去,有一句:response = client.execute(request, options);
此时的client,就是具体发送请求的client。此时发送完请求后,还会将结果封装成Response

feign和ribbon结合的源码,

打断点到feign.SynchronousMethodHandler的invoke第一行。

feign.SynchronousMethodHandler。
上面讲到invoke。
里面有executeAndDecode
此代码主要功能:构建request数据,然后通过request和options去通过LoadBalancerFeignClient.execute()方法去获得返回值。
F5进executeAndDecode。
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 构建request对象,类似于:GET /uri HTTP/1.1

request:
POST http://service-valuation/forecast/single HTTP/1.1
Authorization: Basic cm9vdDpyb290
Content-Length: 148
Content-Type: application/json;charset=UTF-8

{"startLatitude":"labore et laboris eiusmod","startLongitude":"ut cupidatat","endLatitude":"sit sint111","endLongitude":"Excepteur Lorem reprehend"}


    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
    // 这个client就是之前构建的LoadBalancerFeignClient,是Client的实现类LoadBalancerFeignClient。
      response = client.execute(request, options);
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

F5点进去execute方法。(TraceLoadBalancerFeignClient.execute).走了这一行
response = super.execute(request, options);
F5进去
实际就是org.springframework.cloud.openfeign.ribbon.execute
@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		try {
			// asUri:  http://service-valuation/forecast/single
			URI asUri = URI.create(request.url());
			// clientName:service-valuation
			String clientName = asUri.getHost();
			// uriWithoutHost:http:///forecast/single
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			// 真正执行负载均衡的地方:
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}
	
F5 进入	lbClient()。
	private FeignLoadBalancer lbClient(String clientName) {
		return this.lbClientFactory.create(clientName);
	}
	
	public FeignLoadBalancer create(String clientName) {
		FeignLoadBalancer client = this.cache.get(clientName);
		if (client != null) {
			return client;
		}
		IClientConfig config = this.factory.getClientConfig(clientName);
		// 获取Ribbon ILoadBalancer信息,鼠标放到lb上,发现:我们自己配置的com.netflix.loadbalancer.RandomRule@498bbb15
		
		ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
		ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
				ServerIntrospector.class);
		client = this.loadBalancedRetryFactory != null
				? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
						this.loadBalancedRetryFactory)
				: new FeignLoadBalancer(lb, config, serverIntrospector);
		this.cache.put(clientName, client);
		return client;
	}
	
F7回到:
return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();

F5进入executeWithLoadBalancer。
AbstractLoadBalancerAwareClient的下面方法:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }
        
    }
    
 打断点到:com.netflix.loadbalancer.reactive.LoadBalancerCommandpublic Observable<T> submit(final ServerOperation<T> operation) {
        final ExecutionInfoContext context = new ExecutionInfoContext();
        
看这行代码: (server == null ? selectServer() : Observable.just(server))
进入selectServer()。
执行到(打断点到此行  F8Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);

打断点:com.netflix.loadbalancer.LoadBalancerContextpublic Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;

打断点: ILoadBalancer lb = getLoadBalancer();

打断点:Server svc = lb.chooseServer(loadBalancerKey);

终于看到ribbon的东西了。

进入chooseServer

进入if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        
来到:com.netflix.loadbalancer.BaseLoadBalancer
    public Server chooseServer(Object key) {

看到了return rule.choose(key);
	

小结:

  1. 注册beanDefinition。
  2. 实例化
  3. 调用

前2步在启动时执行。

feign在调用其他微服务接口前,会去请求该微服务的相关信息(地址、端口等),并做一些初始化操作,由于默认的懒加载特性,导致了在第一次调用时,出现超时的情况
ribbon:
  eager-load:
    enabled: true
    clients:
    - SERVICE-SMS
配置ribbon立即加载,此处需要注意的是,光配置立即加载是不生效的,还要配置客户端列表.    

总结

  1. feign的使用。
  2. feign的独立使用。(大家课下实践,feignClient(name="",url="http://ip:port/xxx"))
  3. feign和ribbon结合。(配置负载均衡的地方)
  4. 原理,源码。
  5. 继承,压缩,日志(方便开发)。

RestTemplate,自由,更贴近httpclient,方便调用别的第三方的http服务。

feign,更面向对象一些,更优雅一些。