Feign
Feign是Netflix开发的⼀个轻量级RESTful的HTTP服务客户端(⽤它来发起请求,远程调⽤的) ,是以
Java接⼝注解的⽅式调⽤Http请求,⽽不⽤像Java中通过封装HTTP请求报⽂的⽅式直接调⽤, Feign被
⼴泛应⽤在Spring Cloud 的解决⽅案中。
类似于Dubbo,服务消费者拿到服务提供者的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的
是远程的请求。
Feign可帮助我们更加便捷,优雅的调⽤HTTP API:不需要我们去拼接url然后呢调⽤
restTemplate的api,在SpringCloud中,使⽤Feign⾮常简单,创建⼀个接⼝(在消费者–服务调
⽤⽅这⼀端),并在接⼝上添加⼀些注解,代码就完成了
SpringCloud对Feign进⾏了增强,使Feign⽀持了SpringMVC注解(OpenFeign)
本质:封装了Http调⽤流程,更符合⾯向接⼝化的编程习惯,类似于Dubbo的服务调⽤
Dubbo的调⽤⽅式其实就是很好的⾯向接⼝编程
依赖
在服务调⽤者⼯程(消费)创建接⼝(添加注解)
(效果) Feign = RestTemplate+Ribbon+Hystrix
服务消费者⼯程(⾃动投递微服务)中引⼊Feign依赖(或者⽗类⼯程)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启消费者
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients // 开启Feign
public class AutodeliverApplication8090 {
public static void main(String[] args) {
SpringApplication.run(AutodeliverApplication8090.class,args);
}
// 使用RestTemplate模板对象进行远程调用
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
此时去掉Hystrix熔断的⽀持注解@EnableCircuitBreaker即可包括引⼊的依赖,因为Feign
会⾃动引⼊
编写客户端
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "edu-service-resume")
public interface ResumeFeignClient {
@GetMapping("/resume/openstate/{userId}")
public Integer findDefaultResumeState(@PathVariable(value ="userId" ) Long userId) ;
}
name:调⽤的服务名称,和服务提供者yml⽂件中spring.application.name保持⼀致
1) @FeignClient注解的name属性⽤于指定要调⽤的服务提供者名称,和服务提供者yml⽂件中
spring.application.name保持⼀致
2)接⼝中的接⼝⽅法,就好⽐是远程服务提供者Controller中的Hander⽅法(只不过如同本地调
⽤了),那么在进⾏参数绑定的时,可以使⽤@PathVariable、 @RequestParam、
@RequestHeader等,这也是OpenFeign对SpringMVC注解的⽀持,但是需要注意value必须设
置,否则会抛出异常
使⽤接⼝中⽅法完成远程调⽤(注⼊接⼝即可,实际注⼊的是接⼝的实现)
在Controller中
@Autowired
private ResumeFeignClient resumeFeignClient;
@GetMapping("/feign/{userId}")
public Integer findfeign(@PathVariable Long userId) {
String requestUrl = "http://edu-service-resume/resume/openstate/"+userId;
System.out.println("获取服务实现 拼接的url : "+requestUrl);
Integer forObject = resumeFeignClient.findDefaultResumeState(userId);
return forObject;
}
Feign 本身已经集成了Ribbon依赖和⾃动配置,因此我们不需要额外引⼊依赖,可以通过 ribbon.xx 来
进 ⾏全局配置,也可以通过服务名.ribbon.xx 来对指定服务进⾏细节配置配置
Feign默认的请求处理超时时⻓1s,有时候我们的业务确实执⾏的需要⼀定时间,那么这个时候,我们
就需要调整请求处理超时时⻓, Feign⾃⼰有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准
- Ribbon设置
#针对的被调⽤⽅微服务名称,不加就是全局⽣效
xxx-service-resume:
ribbon:
#请求连接超时时间
#ConnectTimeout: 2000
#请求处理超时时间
#ReadTimeout: 5000
#对所有操作都进⾏重试
OkToRetryOnAllOperations: true
####根据如上配置,当访问到故障请求的时候,它会再尝试访问⼀次当前实例(次数由
MaxAutoRetries配置),
####如果不⾏,就换⼀个实例进⾏访问,如果还不⾏,再换⼀次实例访问(更换次数由
MaxAutoRetriesNextServer配置),
####如果依然不⾏,返回失败信息。
MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第⼀次调⽤
MaxAutoRetriesNextServer: 0 #切换实例的重试次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载策略调整
Feign对熔断器的⽀持
在Feign客户端⼯程配置⽂件(application.yml)中开启Feign对熔断器的⽀持
# 开启Feign的熔断功能
feign:
hystrix:
enabled: true
Feign的超时时⻓设置那其实就上⾯Ribbon的超时时⻓设置
Hystrix超时设置(就按照之前Hystrix设置的⽅式就OK了)
注意:
1)开启Hystrix之后, Feign中的⽅法都会被进⾏⼀个管理了,⼀旦出现问题就进⼊对应的回退逻辑处理
2)针对超时这⼀点,当前有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最
⼩值来进⾏的,即处理时⻓超过最短的那个超时时间了就熔断进⼊回退降级逻辑
hystrix:
command:
default:
execution:
isolation:
thread:
##########################################Hystrix的超时时⻓设置
timeoutInMilliseconds: 15000
- ⾃定义FallBack处理类(需要实现FeignClient接⼝)
/**
* 降级回退逻辑需要定义⼀个类,实现FeignClient接⼝,实现接⼝中的⽅法
* *
*/
@Component // 别忘了这个注解,还应该被扫描到
public class ResumeFallback implements ResumeServiceFeignClient {
@Override
public Integer findDefaultResumeState(Long userId) {
return -6;
}
}
- 3在@FeignClient注解中关联2)中⾃定义的处理类
@FeignClient(value = "edu-service-resume",fallback =
ResumeFallback.class,path = "/resume") // 使⽤fallback的时候,类上的
@RequestMapping的url前缀限定,改成配置在@FeignClient的path属性中
//@RequestMapping("/resume")
public interface ResumeServiceFeignClient {
Feign对请求压缩和响应压缩的⽀持
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值
min-request-size: 2048 # 设置触发压缩的⼤⼩下限,此处也是默认值
response:
enabled: true # 开启响应压缩
Feign的⽇志级别配置
Feign是http请求客户端,类似于咱们的浏览器,它在请求和接收响应的时候,可以打印出⽐较详细的
⼀些⽇志信息(响应头,状态码等等)
如果我们想看到Feign请求时的⽇志,我们可以进⾏配置,默认情况下Feign的⽇志没有开启
-
- 开启Feign⽇志功能及级别
/ Feign的⽇志级别(Feign请求过程信息)
// NONE:默认的,不显示任何⽇志----性能最好
// BASIC:仅记录请求⽅法、 URL、响应状态码以及执⾏时间----⽣产问题追踪
// HEADERS:在BASIC级别的基础上,记录请求和响应的header
// FULL:记录请求和响应的header、 body和元数据----适⽤于开发及测试环境定位问题
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLevel() {
return Logger.Level.FULL;
}
}
-
- 配置log⽇志级别为debug
logging:
level:
# Feign⽇志只会对⽇志级别为debug的做出响应
com.lagou.edu.controller.service.ResumeServiceFeignClient: debug
Feign核⼼源码剖析
添加上@FeignClient,真的没有实现的话,能完成远程请求么?
-
- 调用对象发现是个代理
- 调用对象发现是个代理
-
- 从@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 {};
}
-
- FeignClientsRegistrar
- FeignClientsRegistrar
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//4.Feign的全局默认配置注入
registerDefaultConfiguration(metadata, registry);
//5. 把标记了@FeignClient注解的类注入
registerFeignClients(metadata, registry);
}
-
- registerDefaultConfiguration
把@EnableFeignClients 中的defaultConfiguration属性配置到类并注入到容器中
- registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
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"));
}
}
- 5.registerFeignClients
定义扫描器,主要扫描@FeignClient类,并注入的容器
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());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
//扫描包下的@FeignClient注解
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//FeignClient中有一个configuration属性把它取出来
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//6.注册客户端为每个客户端生成一个代理
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
注册客户端,给每⼀个客户端⽣成代理对象
-
- registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//根据FeignClient中的属性配置,封装BeanDefinition对象FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//获取配置的下面的属性
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = 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"));
//这个设置注入模式setAutowireMode 根据类型注入AUTOWIRE_BY_TYPE
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
关注FeignClientFactoryBean这个⼯⼚Bean的getObject⽅法,会返回我们的代理对象
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//判断FeignClient中的url是否为空,
if (!StringUtils.hasText(this.url)) {
//不为空说明是,请求固定的服务提供者,用于测试
//判断是否为http开始的
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
//为空就=服务提供者的name
url = this.name;
}
//生成一个代有负载均衡功能的Feign客户端口
url += cleanPath();
//1. 生成有带有负载均衡功能的Feign
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
-
- loadBalance
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//最终执行请求的Client 2.
Client client = getOptional(context, Client.class);
//使用builder构造器,包装client
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
//3.
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
-
- getOptional
使用上下文获取根据contextId获取Client
- getOptional
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.contextId, type);
}
-
- org.springframework.cloud.openfeign.HystrixTargeter # target
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
//4. 这个对创建
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
//4. 创建
return feign.target(target);
}
-
- feign.target -feign.Feign
public <T> T target(Target<T> target) {
// 反射创建 5.build
//newInstance 7.
return build().newInstance(target);
}
-
- build()方法
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);
//6. ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
-
- ReflectiveFeign
ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
QueryMapEncoder queryMapEncoder) {
this.targetToHandlersByName = targetToHandlersByName;
this.factory = factory;
this.queryMapEncoder = queryMapEncoder;
}
-
- newInstance
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);
//最终返回JDK的代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
⼊增强逻辑的,所以接下来我们要关注增强逻辑部分,FeignInvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//1. 具体的增加又交了了方法
return dispatch.get(method).invoke(args);
}
1. SynchronousMethodHandler#invoke
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//执行后面的逻辑 2
return executeAndDecode(template);
} catch (RetryableException e) {
try {
-
- executeAndDecode
Object executeAndDecode(RequestTemplate template) throws Throwable {
//封装请求
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//真正执行 client 就是LoadBalancerFeignClient
response = client.execute(request, options);
LoadBalancerFeignClient.execute
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//构建Rabbion请求
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);
}
}
com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer()
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);
}
}
}
进⼊submit⽅法,发现使⽤Ribbon在做负载均衡了
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
if (listenerInvoker != null) {
try {
listenerInvoker.onExecutionStart();
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
//1. selectServer选择服务
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
selectServer
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
getServerFromLoadBalancer
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
// Various Supported Cases
// The loadbalancer to use and the instances it has is based on how it was registered
// In each of these cases, the client might come in using Full Url or Partial URL
//获取负载均衡Ribbon的逻辑
ILoadBalancer lb = getLoadBalancer();
();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
//选择Server
Server svc = lb.chooseServer(loadBalancerKey);
默认区域的负载均衡
回到### LoadBalancerFeignClient.execute中
AbstractLoadBalancerAwareClient
org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
最终请求的发起使⽤的是HttpURLConnection
feign.Client
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}