spring cloud openFeign
spring cloud openFeign
Feign 是一个声明式的 Web 服务客户端,它使 Java 编写 Web 服务客户端变得更加容易。
OpenFeign 是通过自动装配将Feign集成到应用程序中,它在Feign的基础上进行了扩展和定制,以更好地与Spring Cloud集成,并且提供了更多的功能和特性来满足微服务架构的需求。
1: 整合 Spring Cache 来执行 FeignClient 接口,拦截方法的执行增加上 缓存 的设置
2: 整合 CircuitBreaker 来执行 FeignClient 接口的方法,方法的执行委托给 CircuitBreaker 控制
3: 整合 Spring Cloud locaBalancer 后 Feign 使用负载均衡的HTTP客户端 来发送请求
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
FeignAutoConfiguration
主要是注册了 FeignContext、Targeter、CachingCapability、执行HTTP请求的工具(ApacheHttpClient、OkHttpClient)。
FeignContext 是用来隔离不同 FeignClient 的容器,每个 FeignClient 有单独的 IOC容器,容器中默认注册了 FeignClient 需要的 bean。
Targeter 是用来生成 FeignClient 接口的实现类的。
CachingCapability 是 用来配置 Feign.Builder 的,主要是对 InvocationHandlerFactory 进行增强,而 InvocationHandlerFactory 是用来生成 InvocationHandler 从而让方法的执行委托给 CacheInterceptor 来执行,也就是会处理 Spring Cache 相关的注解
FeignAcceptGzipEncodingAutoConfiguration
注册 FeignAcceptGzipEncodingInterceptor , 其是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Accept-Encoding=gzip,deflate
Accept-Encoding=gzip,deflate 是告诉服务器 客户端支持 gzip,deflate 压缩
FeignContentGzipEncodingAutoConfiguration
注册 FeignContentGzipEncodingInterceptor , 其是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Content-Encoding=gzip,deflate
请求头有 Content-Encoding=gzip,deflate 会对请求体进行编码后(压缩)再发送给到服务器
FeignLoadBalancerAutoConfiguration
目的都是注册 Client 的实现类,根据条件会注册 FeignBlockingLoadBalancerClient 或者是 RetryableFeignBlockingLoadBalancerClient
导入的三个配置类的区别在于:
HttpClientFeignLoadBalancerConfiguration 依赖 HttpClient 执行HTTP请求
OkHttpFeignLoadBalancerConfiguration 依赖 okhttp3.OkHttpClient 执行HTTP请求
DefaultFeignLoadBalancerConfiguration 依赖 Client.Default 执行HTTP请求
FeignClient扫描、注册
通过添加@EnableFeignClients 注解,开启FeignClient的扫描。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* 扫描的包名
*/
String[] value() default {};
/**
* 扫描的包名
*/
String[] basePackages() default {};
/**
* 通过类所在的包名得到要扫描的包名
*/
Class<?>[] basePackageClasses() default {};
/**
* 默认的配置类。是用来指定FeignContext要用到的配置类
*/
Class<?>[] defaultConfiguration() default {};
/**
* 指定FeignClient,如果有值那就不会进行包扫描
*/
Class<?>[] clients() default {};
}
其中@EnableFeignClients中import了 FeignClientsRegistrar
,FeignClientsRegistrar中会进行客户端的扫描和注册,主要是解析注解的元信息,然后扫描或者直接注册客户端和客户端配置。我们主要看他的registerBeanDefinitions方法。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
...........
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 根据注解 注册默认配置
registerDefaultConfiguration(metadata, registry);
// 注册 FeignClient
registerFeignClients(metadata, registry);
}
/**
* 根据注解值注册默认配置
*/
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取 @EnableFeignClients 注解的属性值
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"));
}
}
// 注册客户端
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 要注册的 BeanDefinition
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获取 @EnableFeignClients 注解的属性值
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 获取 clients 的值
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// @EnableFeignClients 没有设置 clients 值,那就扫描包下的类得到
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 配置 include 规则,只会收集有 @FeignClient 的类
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
/**
* @EnableFeignClients(value、basePackages、basePackageClasses) 的值作为要扫描的包路径,
* 若这三个注解值都没设置,那就是用标注了 @EnableFeignClients 注解所在的类的包作为要扫描的包
* */
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 开始扫描,将扫描的结果存到 candidateComponents
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
} else {
// @EnableFeignClients 设置了 clients 值,那就只使用这些值
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 遍历 candidateComponents 挨个映射成 BeanDefinition 注册到 BeanFactory 中
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// @FeignClient 标注的类 不是接口就报错
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 拿到注解的属性值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
/**
* 为空就依次获取属性 contextId -> value -> name 都没设置就报错。
* */
String name = getClientName(attributes);
/**
* 注册客户端配置
* */
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册 FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
// 注册客户端
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
// 拿到标注了注解的类名
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
/**
* 获取属性值,值为空就依次获取: contextId -> name -> value
* 最后使用 environment.resolvePlaceholders(value) 解析占位符,也就是说这些注解值是支持使用 ${}、#{} 的
* 如果都没有设置,那么 contextId 就是空字符串
* */
String contextId = getContextId(beanFactory, attributes);
/**
* 同上,只不过获取的是 name -> value
* */
String name = getName(attributes);
// new 一个 FeignClientFactoryBean
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
// 根据 属性 feign.client.refresh-enabled 设置
factoryBean.setRefreshableClient(isClientRefreshEnabled());
/**
* 设置 Supplier , 也就是实例化会回调 Supplier 得到实例
*
* Tips:因为每次实例化bean都会重新设置 url、path 的值且支持使用占位符,会根据属性文件配置的值进行替换,所以我们可以
* 将 bean 设置成 refresh 作用域的,然后就能实现 url、path 的动态更新
*
* 可以通过设置属性让bean变成refresh作用域的 spring.cloud.refresh.ExtraRefreshable=XxFeignClient
* */
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
// 根据 @FeignClient(url="")的值来设置,会解析占位符,还会补全http://
factoryBean.setUrl(getUrl(beanFactory, attributes));
// 获取 @FeignClient(path="")的值来设置,会解析占位符, 会补上前缀/,移除后缀/
factoryBean.setPath(getPath(beanFactory, attributes));
// 剩下的就是简单读取值然后设置给factoryBean
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true); // 懒加载的
// 校验 fallback、fallbackFactory 的值不能是接口
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
// 作为别名
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 注册到 BeanFactory 中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
/**
* 如果 feign.client.refresh-enabled 是true那就注册 OptionsFactoryBean 到容器中
* 而且是 refresh 作用域的
*
* 当 FeignClientFactoryBean.getObject() 时会拿到 OptionsFactoryBean 用来配置 Feign.Builder
* */
registerOptionsBeanDefinition(registry, contextId);
}
.........
}
可以看到,对于每个客户端其实注册的是FeignClientFactoryBean,bean的实例化是执行 FeignClientFactoryBean.getObject()得到的。
创建代理对象
FeignClientFactoryBean:
public Object getObject() {
return getTarget();
}
/**
* 创建客户端示例
*/
<T> T getTarget() {
// 从容器中获取 FeignContext
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
/**
* 根据配置创建 Feign.Builder
* */
Feign.Builder builder = feign(context);
/**
* url 没有值,那就使用 name + path 拼接成 url,然后构造出 负载均衡的实例对象
* */
if (!StringUtils.hasText(url)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 从容器中获取 Targeter 实例,执行接口方法得到 type 的实现类
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
// 补上协议名
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
// 拼接上 path
String url = this.url + cleanPath();
// 从容器中获取 Client 实例(会从父容器中找)
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
/**
* 根据 contextId 从 FeignContext 获取 FeignBuilderCustomizer 用来加工 builder
* */
applyBuildCustomizers(context, builder);
// 从容器中获取 Targeter 实例
Targeter targeter = get(context, Targeter.class);
/**
* 执行方法得到 type 的实现类
* */
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
/**
* 根据配置 创建 Feign.Builder
*/
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
// 应用配置
configureFeign(context, builder);
return builder;
}
// 应用配置到Feign.Builder
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
// 会尝试从父容器中获取
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
// 设置 inheritParentContext 属性
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
/**
* 有 FeignClientProperties 且继承父上下文(默认是true)
*
* inheritParentContext 为 true 那么获取bean时,会先找当前容器找不到会从父容器中找
* */
if (properties != null && inheritParentContext) {
if (properties.isDefaultToProperties()) {
// 根据 contextId 从 context 中获取相应的bean用来配置 builder
configureUsingConfiguration(context, builder);
// 属性文件配置的 FeignClientConfiguration 来对 builder 进行配置
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
可以看到最终执行的是targeter.target方法,我们看一下FeignCircuitBreakerTargeter实现,其实就是配置 invocationHandlerFactory 属性,从而能够将方法的执行委托给 CircuitBreaker 执行。
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
// 不是 FeignCircuitBreaker 类型的就不做处理
if (!(feign instanceof FeignCircuitBreaker.Builder)) {
return feign.target(target);
}
FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign;
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
/**
* 前置知识:Feign 其实是通过JDK动态代理 为接口创建出代理对象,所以要想拦截方法的执行只需要配置 InvocationHandler 即可。
*
* 下面的几行代码的最终目都是设置 FeignCircuitBreakerInvocationHandler 作为代理对象的 InvocationHandler,
* 所以关键还得看 FeignCircuitBreakerInvocationHandler
*
* {@link FeignCircuitBreakerInvocationHandler#invoke(Object, Method, Object[])}
* 大致流程是方法的执行交给 CircuitBreaker 执行 {@link CircuitBreaker#run(Supplier, Function)}
* CircuitBreaker 可以拿到 fallback 或者 fallbackFactory。可以决定什么时候回调 fallback 的逻辑
* */
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
// 存在 fallback 的情况
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
// 存在 fallbackFactory 的情况
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
}
return builder(name, builder).target(target);
}
FeignCircuitBreakerTargeter 会配置 FeignCircuitBreakerInvocationHandler 作为 FeignClient 接口代理对象的 InvocationHandler,作用是 将方法的执行 和 fallback的执行 交给 CircuitBreaker 来决定,比如方法执行出错了就执行 fallback。
class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
String circuitName = this.feignClientName + "_" + method.getName();
// 通过 CircuitBreakerFactory 得到 CircuitBreaker 实例
CircuitBreaker circuitBreaker = this.factory.create(circuitName);
// 定义方法的执行
Supplier<Object> supplier = asSupplier(method, args);
/**
* 存在 nullableFallbackFactory 就使用
*
* 比如这两种情况 nullableFallbackFactory 才会有值
* @FeignClient(fallback=A.class)
* @FeignClient(fallbackFactory=A.class)
* @FeignClient(fallback=A.class, fallbackFactory=A.class) // 两个都有的情况 只会使用 fallback
* */
if (this.nullableFallbackFactory != null) {
// 使用 nullableFallbackFactory 构造出 fallbackFunction
Function<Throwable, Object> fallbackFunction = throwable -> {
// 通过 nullableFallbackFactory 得到 fallback
Object fallback = this.nullableFallbackFactory.create(throwable);
try {
// 使用 fallback 执行当前出错的方法
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
};
// 使用 circuitBreaker 执行方法
return circuitBreaker.run(supplier, fallbackFunction);
}
// 使用 circuitBreaker 执行方法
return circuitBreaker.run(supplier);
}
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
return () -> {
try {
return this.dispatch.get(method).invoke(args);
}
catch (RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
};
}
}
最终feign调用newInstance方法创建feign的动态代理。
利用contract解析方法上的注解信息为MethodMetadata,封装MethodHandler使之与method对应。
将MethodHandler与方法的映射封装进InvocationHandler,这里是FeignInvocationHandler。
创建jdk动态代理对象。
ReflectiveFeign:
public <T> T newInstance(Target<T> target) {
// 利用contract解析方法上的注解信息为MethodMetadata。 封装MethodHandler使之与method对应
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
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;
}
需要注意的是在open feign中Contract实现类SpringMvcContract添加了对spring mvc注解的支持。
SpringMvcContract:
private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {
List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();
annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());
annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
annotatedArgumentResolvers.add(new RequestPartParameterProcessor());
annotatedArgumentResolvers.add(new CookieValueParameterProcessor());
return annotatedArgumentResolvers;
}
Feign调用
执行的是代理对象的FeignInvocationHandler。
FeignInvocationHandler:
@Override
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();
}
// 通过方法和handle的映射处理
return dispatch.get(method).invoke(args);
}
MethodHandler的默认实现:SynchronousMethodHandler
SynchronousMethodHandler:
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 执行请求
return executeAndDecode(template, options);
} 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;
}
}
}
基于重试以及负载均衡的客户端实现:
RetryableFeignBlockingLoadBalancerClient:
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(serviceId,
loadBalancerClient);
RetryTemplate retryTemplate = buildRetryTemplate(serviceId, request, retryPolicy);
return retryTemplate.execute(context -> {
Request feignRequest = null;
ServiceInstance retrievedServiceInstance = null;
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RetryableRequestContext.class, ResponseData.class, ServiceInstance.class);
String hint = getHint(serviceId);
DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
new RetryableRequestContext(null, buildRequestData(request), hint));
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
ServiceInstance serviceInstance = lbContext.getServiceInstance();
if (serviceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
+ "Reattempting service instance selection");
}
ServiceInstance previousServiceInstance = lbContext.getPreviousServiceInstance();
lbRequest.getContext().setPreviousServiceInstance(previousServiceInstance);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 负载均衡选择服务实例
retrievedServiceInstance = loadBalancerClient.choose(serviceId, lbRequest);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Selected service instance: %s", retrievedServiceInstance));
}
lbContext.setServiceInstance(retrievedServiceInstance);
}
if (retrievedServiceInstance == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("Service instance was not resolved, executing the original request");
}
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
retrievedServiceInstance);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RetryableRequestContext>(
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
feignRequest = request;
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Using service instance from LoadBalancedRetryContext: %s",
retrievedServiceInstance));
}
String reconstructedUrl = loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri)
.toString();
feignRequest = buildRequest(request, reconstructedUrl);
}
}
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
retrievedServiceInstance);
LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
// 执行请求调用
Response response = LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(delegate, options,
feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors, retrievedServiceInstance != null,
loadBalancerProperties.isUseRawStatusCodeInResponseData());
int responseStatus = response.status();
if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Retrying on status code: %d", responseStatus));
}
byte[] byteArray = response.body() == null ? new byte[] {}
: StreamUtils.copyToByteArray(response.body().asInputStream());
response.close();
throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray,
URI.create(request.url()));
}
return response;
}, new LoadBalancedRecoveryCallback<Response, Response>() {
@Override
protected Response createResponse(Response response, URI uri) {
return response;
}
});
}
总结
@EnableFeignClients注解开启扫描、注册FeignClient。
@FeignClient可以包含了客户端的信息。会为每个客户端生成单独的配置。
注册到容器中FeignClientFactoryBean
类型的bean。通过调用FeignClientFactoryBean
的getObject方法,获取客户端相应的配置,来创建相应的feign客户端。
执行调用是通过代理对象的MethodHandler。
通过整合spring cloud loadbalancer实现了负载均衡的客户端。
通过整合spring cloud circuitbreaker实现了熔断的客户端。