Feign使用和原理的总结

article/2025/9/2 10:09:35

官方参考

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

使用

引入依赖

 <properties><java.version>1.8</java.version><spring-cloud.version>2021.0.1</spring-cloud.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!--openfeign依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

配置文件

配置文件需要配置eureka服务器信息,使用了ribbon负载均衡,需要配置负载均衡相关信息

server:port: 8889contextPath: /feignspring:#项目名字application:name: feign# eureka client配置
eureka:client:registryFetchIntervalSeconds: 5serviceUrl:defaultZone: http://localhost:8098/eureka/  #eureka服务端提供的注册地址 参考服务端配置的这个路径instance:hostname: feign #此实例注册到eureka服务端的唯一的实例IDprefer-ip-address: true #是否显示IP地址leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)leaseExpirationDurationInSeconds: 30 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒health-check-url-path: /actuator/healthribbon:MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试MaxAutoRetriesNextServer: 3 #切换实例的重试次数OkToRetryOnAllOperations: false  #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为falseConnectTimeout: 5000  #请求连接的超时时间ReadTimeout: 6000 #请求处理的超时时间# 调用USER-MGT微服务时使用随机策略
USER-MGT:ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

启动类增加注解

增加@EnableFeignClients注解,自动加载Feign的配置

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {public static void main(String[] args) {SpringApplication.run(FeignApplication.class, args);}}

定义feign调用标签

加入客户端要调用user-mgt微服务的一个接口获取当前的端口号,可以使用@FeignClient定义客户端

@FeignClient("user-mgt")
public interface UserMgtClient {@RequestMapping(method = RequestMethod.GET, value = "/sequence/number/port")String getPort();
}

调用目标接口

可以通过属性注入的方式将上面定义的UserMgtClient进行注入

@RestController
@RequestMapping("/feign")
@Slf4j
public class FeignTestController {@Autowiredprivate UserMgtClient userMgtClient;@RequestMapping(value = "/server/port", method = RequestMethod.GET, produces = "application/json")public String getServerPort(){String port = userMgtClient.getPort();log.info("port={}",port);return port;}
}

调用过程

调用的过程可以总结如下:

  • 首先通过JDK动态代理调用ReflectiveFeign#invoke
  • 然后调用SynchronousMethodHandler#invoke创建RestTemplate
  • 接着调用Client#execute处理负载均衡,替换serviceName为具体的服务器IP
  • 再调用LoadBalancerUtils#executeWithLoadBalancerLifecycleProcessing,完成HTTP的远程调用后返回结果

对上面的调用过程做一个具体的源码回顾

首先userMgtClient.getPort()会通过JDK代理的方式进行调用,ReflectiveFeign#invoke的实现中会判断是否调用的是常用的类方法equals、hashCode和toString,如果是直接返回,否则会获取具体的代理目标类来调用目标方法

@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();}// 获取代理类,调用目标方法return dispatch.get(method).invoke(args);
}

dispatch.get(method).invoke(args)会调用对应的处理器SynchronousMethodHandler#invoke方法,他会新建RestTemplate,执行调用,如果失败了,会进行一次重试

public Object invoke(Object[] argv) throws Throwable {// 新建RestTemplateRequestTemplate template = buildTemplateFromArgs.create(argv);// 获取optionsOptions 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;}}
}

executeAndDecode(template, options)方法中首先构造对目标接口调用的HTTP请求,这里可以扩展请求定制,通过实现RequestInterceptor接口可以为请求添加header属性等信息,如果微服务鉴权需要token,可以使用这种方式;然后进行客户端的调用,比通过异步任务的方式对响应进行解码

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {// 构造对目标接口调用的HTTP请求Request request = targetRequest(template);if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {// 客户端调用response = client.execute(request, options);// 构造响应response = response.toBuilder().request(request).requestTemplate(template).build();} 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);// 解码响应信息if (decoder != null)return decoder.decode(response, metadata.returnType());// 解码响应采用异步的方式,通过CompletableFuture实现CompletableFuture<Object> resultFuture = new CompletableFuture<>();asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,metadata.returnType(),elapsedTime);try {// 等待解码结果if (!resultFuture.isDone())throw new IllegalStateException("Response handling not done");// 异步执行结果反馈return resultFuture.join();} catch (CompletionException e) {Throwable cause = e.getCause();if (cause != null)throw cause;throw e;}
}Request targetRequest(RequestTemplate template) {// 通过实现RequestInterceptor接口可以为请求添加header属性等信息,如果微服务鉴权需要token,可以使用for (RequestInterceptor interceptor : requestInterceptors) {interceptor.apply(template);}// 执行目标对象的apply方法return target.apply(template);}

client.execute(request, options)客户端执行方法是调用的核心,

public Response execute(Request request, Options options) throws IOException {// 创建原始URIURI originalUri = URI.create(request.url());// 获取URI的HOSTString serviceId = originalUri.getHost();Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);// 获取hintString hint = this.getHint(serviceId);// 构造负载均衡请求DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest(new RequestDataContext(LoadBalancerUtils.buildRequestData(request), hint));// 获取支持生命周期的处理器Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(this.loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class);// 生命周期开始supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onStart(lbRequest);});// 根据负载均衡策略获取实例ServiceInstance instance = this.loadBalancerClient.choose(serviceId, lbRequest);// 构造负载均衡响应org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);String message;if (instance == null) { // 没有负载均衡实例message = "Load balancer does not contain an instance for the service " + serviceId;if (LOG.isWarnEnabled()) {LOG.warn(message);}// 生命周期结束supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, lbResponse));});return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();} else { // 存在负载均衡实例// 重新构造负载均衡请求,将serviceId替换为IP+端口message = this.loadBalancerClient.reconstructURI(instance, originalUri).toString();Request newRequest = this.buildRequest(request, message);// 请求处理return LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(this.delegate, options, newRequest, lbRequest, lbResponse, supportedLifecycleProcessors);}
}

LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing执行负载均衡处理的具体处理,主要完成负载均衡生命周期的处理,执行HTTP请求

static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced) throws IOException {// 生命周期开始supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));try {// 执行实际的HTTP请求Response response = feignClient.execute(feignRequest, options);if (loadBalanced) { // 启用了负载均衡// 生命周期结束supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,lbRequest, lbResponse, buildResponseData(response))));}return response;}catch (Exception exception) {if (loadBalanced) {supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, lbResponse)));}throw exception;}
}

feignClient.execute(feignRequest, options)客户端执行HTTP请求,主要是建立HTTP的连接,然后调用,再对返回结果进行封装

public Response execute(Request request, Options options) throws IOException {// 建立HTTP连接,执行请求HttpURLConnection connection = convertAndSend(request, options);// 转换返回结果return convertResponse(connection, request);
}

配置加载

首先,要使用Feign,需要在启动类添加@EnableFeignClients注解,这个注解里面的@Import会加载FeignClientsRegistrar类,该类中会进行Feign客户端相关的配置

@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接口,该接口在Spring创建bean的时候可以注入额外的属性信息
在这里插入图片描述
应用启动加载bean的时候,先执行FeignClientsRegistrar#registerBeanDefinitions方法,它会注册默认的配置和Feign客户端信息

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 注册默认配置registerDefaultConfiguration(metadata, registry);// 注册Feign客户端registerFeignClients(metadata, registry);
}

registerDefaultConfiguration会获取@EnableFeignClients注解的属性信息,然后注册客户端的配置

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"));}
}

registerFeignClient 先获取代码中所有添加了@EnableFeignClients注解的信息,根据注解信息获取clients数组, 如果clients为空,扫描代码里面所有的@FeignClient客户端client; 注册client时,先校验注解的类是不是一个接口,然后获取注解@FeignClient的属性,注册Client配置,再注册client;

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();// 获取@EnableFeignClients注解的信息Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// 获取clientsfinal Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");// 如果clients为空,代码里面的所有客户端配置信息if (clients == null || clients.length == 0) {ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}} else { // 如果clients不为空,候选组件中增加clientsfor (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));}}// 遍历候选组件,获取每一个组件的信息,校验,然后进行注册for (BeanDefinition candidateComponent : candidateComponents) {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 = getClientName(attributes);// 注册Client配置registerClientConfiguration(registry, name, attributes.get("configuration"));// 注册clientregisterFeignClient(registry, annotationMetadata, attributes);}}
}

registerFeignClient 会将具体的client信息注册到IOC容器,并为创建client的bean设置回调方法

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 获取类名String className = annotationMetadata.getClassName();// 解析类信息Class clazz = ClassUtils.resolveClassName(className, null);// 转化beanFactoryConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory? (ConfigurableBeanFactory) registry : null;String contextId = getContextId(beanFactory, attributes);String name = getName(attributes);// factoryBean属性设置FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 构造bean定义信息BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {// 以下为创建bean的实例的回调方法factoryBean.setUrl(getUrl(beanFactory, attributes));factoryBean.setPath(getPath(beanFactory, attributes));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));}// bean实例返回return factoryBean.getObject();});definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);// 懒加载definition.setLazyInit(true);validate(attributes);AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);// has a default, won't be nullboolean 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);// 注册client bean到IOCBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);// 如果isClientRefreshEnabled==true, 会注册Request.Options beansregisterOptionsBeanDefinition(registry, contextId);
}

factoryBean.getObject()会调用FeignClientFactoryBean#getTarget方法,

<T> T getTarget() {// 从IOC中获取上下文FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);// 构造feign,设置一些配置信息Feign.Builder builder = feign(context);// 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")) { // 如果不是http开头,在服务名前面加上http,如:http://user-mgturl = "http://" + name;}else {url = name;}url += cleanPath();// 加载负载均衡器return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}// url不为空if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}applyBuildCustomizers(context, builder);Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

loadBalance(builder, context, new HardCodedTarget<>(type, name, url))获取client的配置,然后引用客户化配置,再获取targeter(Feign#target),最后调用Feign接口的实现类ReflectiveFeign#newInstance,创建代理对象

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {// 获取clientClient client = getOptional(context, Client.class);if (client != null) {builder.client(client);// 应用客户化配置applyBuildCustomizers(context, builder);// 获取targeter,一般是DefaultTargeterTargeter targeter = get(context, Targeter.class);// DefaultTargeter#target会调用Feign#target,Feign#target调用它的实现类ReflectiveFeign#newInstancereturn targeter.target(this, builder, context, target);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}

ReflectiveFeign#newInstance为调用的目标方法设置handler,并创建JDK动态代理对象返回

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>();// 循环调用目标对象的方法,放到上面的handler中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)));}}// 创建JDK动态代理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;
}

总的来说,Feign 的源码实现过程如下:

  • 首先通过@EnableFeignClients注解开启FeignClient 的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描。
  • 根据Feign的规则实现接口,并在接口上面加上@FeignClient注解。
  • 程序启动后,会进行包扫描,扫描所有的@ FeignClient 的注解的类,并将这些信息注入IoC容器中。
  • 当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
  • 根据RequestTemplate再生成Http请求的Request对象。
  • Request 对象交给Client去处理,其中Client的网络请求框架可以是HtpURLConnection、HttpClient和OkHttp。
  • 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

替换默认的HTTP Client

Feign最终的HTTP请求是通过Client来完成,默认情况下,Client的实现类Client.Default主要使用HttpURLConnection完成HTTP调用,默认没有线程池,高并发的场景下有性能瓶颈。可以替换为HttpClient或者OKHTTP。

Feign通过FeignLoadBalancerAutoConfiguration自动配置类中使用@Import注解引入了HttpClientFeignLoadBalancerConfiguration、OkHttpFeignLoadBalancerConfiguration、DefaultFeignLoadBalancerConfiguration等配置,实现了对OKHTTP、HTTPClient的自动加载配置

@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,DefaultFeignLoadBalancerConfiguration.class })// 这三个按顺序优先加载前面的配置
public class FeignLoadBalancerAutoConfiguration {}

下面将使用OKHTTP来处理最后的HTTP请求,他是有线程池的,可以支持高并发的场景。他的配置主要是通过OkHttpFeignLoadBalancerConfiguration配置类实现的

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")  // 需要在配置文件中配置feign.okhttp.enabled: true
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@Import(OkHttpFeignConfiguration.class) // 引入OkHttpFeignConfiguration配置
@EnableConfigurationProperties(LoadBalancerProperties.class)
class OkHttpFeignLoadBalancerConfiguration {// 不需要重试的时候,创建bean:FeignBlockingLoadBalancerClient@Bean@ConditionalOnMissingBean@Conditional(OnRetryNotEnabledCondition.class)public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {OkHttpClient delegate = new OkHttpClient(okHttpClient);return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);}// 需要重试的时候,创建bean:RetryableFeignBlockingLoadBalancerClient@Bean@ConditionalOnMissingBean@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")@ConditionalOnBean(LoadBalancedRetryFactory.class)@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",matchIfMissing = true)public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,LoadBalancerClientFactory loadBalancerClientFactory) {OkHttpClient delegate = new OkHttpClient(okHttpClient);return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,properties, loadBalancerClientFactory);}}

@Import(OkHttpFeignConfiguration.class) 会引入OkHttpFeignConfiguration配置, OkHttpFeignConfiguration配置类上面的@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)表明只要引入OKHTTP的依赖就会触发该类的配置。

@Configuration(proxyBeanMethods = false)
// 引入依赖就会执行创建bean的配置
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
public class OkHttpFeignConfiguration {private okhttp3.OkHttpClient okHttpClient;// 创建线程池bean@Bean@ConditionalOnMissingBean(ConnectionPool.class)public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,OkHttpClientConnectionPoolFactory connectionPoolFactory) {int maxTotalConnections = httpClientProperties.getMaxConnections();long timeToLive = httpClientProperties.getTimeToLive();TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);}// 创建okhttp3.OkHttpClient bean@Beanpublic okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,FeignHttpClientProperties httpClientProperties) {boolean followRedirects = httpClientProperties.isFollowRedirects();int connectTimeout = httpClientProperties.getConnectionTimeout();Duration reaTimeout = httpClientProperties.getOkHttp().getReadTimeout();this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).readTimeout(reaTimeout).connectionPool(connectionPool).build();return this.okHttpClient;}@PreDestroypublic void destroy() {if (this.okHttpClient != null) {this.okHttpClient.dispatcher().executorService().shutdown();this.okHttpClient.connectionPool().evictAll();}}}

引入的依赖如下:

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>

参考官方的文档可以看出,还需要在配置文件中增加开启okHTTP的配置

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/appendix.html

feign:okhttp:enabled: true # Enables the use of the OK HTTP Client by Feign.

OkHttpClient主要是实现了Client接口,在实际调用的时候请求它的execute方法

public feign.Response execute(feign.Request input, feign.Request.Options options)throws IOException {okhttp3.OkHttpClient requestScoped;if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()|| delegate.readTimeoutMillis() != options.readTimeoutMillis()|| delegate.followRedirects() != options.isFollowRedirects()) {requestScoped = delegate.newBuilder().connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build();} else {requestScoped = delegate;}// 转化为OkHttpRequestRequest request = toOkHttpRequest(input);// 执行HTTP请求Response response = requestScoped.newCall(request).execute();// 结果转化为FeignResponsereturn toFeignResponse(response, input).toBuilder().request(input).build();
}

hystrix断路器的使用

feign在处理客户端请求的时候可以设置请求失败后的回调和服务降级处理,通过设置feign.circuitbreaker.enabled=true可以做到,但是要先引入对应的hystrix依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId><version>2.2.10.RELEASE</version>
</dependency>

配置文件中配置

feign:circuitbreaker:enabled: true # 开启断路器

然后在客户端代码上增加配置

@FeignClient(value = "user-mgt",fallback = UserMgtFallback.class)
public interface UserMgtClient {@RequestMapping(method = RequestMethod.GET, value = "/sequence/number/port")String getPort();
}

UserMgtFallback需要注入到IOC容器中

@Component
public class UserMgtFallback implements UserMgtClient{@Overridepublic String getPort() {return  "invoke port by feign encounter error";}
}

然后请求失败的时候返回
在这里插入图片描述


http://chatgpt.dhexx.cn/article/cHaU2l0S.shtml

相关文章

Feign的介绍

Feign是springcloud里面的一个功能组件&#xff0c;那么它是实现一个什么功能呢&#xff1f; 首先我们可以先从字面意思上去理解一下它&#xff0c;Feign&#xff0c;英文翻译过来就是伪装的意思&#xff0c;实际上它的功能也是和伪装相关的&#xff0c;在我们之前在客户端配置…

FeignClient原理解读

FeignClient原理解读 简介源码解读feign负载ribbon源码解读eurake的ribbon原理是&#xff1a;调度原理&#xff1a; 笔记参考文献 简介 这一章节主要介绍feignClient的地址如何来&#xff0c;如何从feign Server注册中心寻址和调用。 源码解读 在解读源码之前&#xff0c;建…

Spring Cloud-Feign设计原理

什么是Feign&#xff1f; Feign 的英文表意为“假装&#xff0c;伪装&#xff0c;变形”&#xff0c; 是一个http请求调用的轻量级框架&#xff0c;可以以Java接口注解的方式调用Http请求&#xff0c;而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解&am…

再学一下Feign的原理

简介 Feign是Spring Cloud Netflix组件中的一个轻量级Restful的HTTP服务客户端&#xff0c;它简化了服务间调用的方式。 Feign是一个声明式的web service客户端.它的出现使开发web service客户端变得更简单.使用Feign只需要创建一个接口加上对应的注解, 比如FeignClient注解。…

Feign的底层原理

Feign的底层原理 1 EnableFeignClients2 根据接口上的注解创建RequestTemplate3 发送请求 1 EnableFeignClients 这个注解标注在springboot的启动类上,作用是开启feign接口扫描 FeignClientsRegistrar.registerFeignClients()扫描被FeignClient标识的接口生成代理类 public vo…

深入理解Feign之源码解析

转载请标明出处&#xff1a; https://blog.csdn.net/forezp/article/details/73480304 本文出自方志朋的博客 出自方志朋的博客 个人博客纯净版&#xff1a;https://www.fangzhipeng.com/springcloud/2017/08/11/sc-feign-raw.html 什么是Feign Feign是受到Retrofit&#xff…

SpringCloud 中Feign原理(图解)

1 SpringCloud 中Feign原理 1.1 Feign简介 Feign是Netflix公司开源的轻量级rest客户端&#xff0c;使用Feign可以非常方便的实现Http 客户端。Spring Cloud引入Feign并且集成了Ribbon实现客户端负载均衡调用。 1.2 Feign远程调用的基本流程 Feign远程调用&#xff0c;核心就是…

Feign(简介和使用)

1. Feign介绍 通过RestTemplate调用其它服务的API时&#xff0c;所需要的参数须在请求的URL中进行拼接&#xff0c;如果参数少的话或许我们还可以忍受&#xff0c;一旦有多个参数的话&#xff0c;这时拼接请求字符串就会效率低下 Feign是一个声明式的Web Service客户端&#…

什么是Feign?

服务间调用介绍 现有的服务调用方式 利用拼接的方式。 虽然上面有的用不错就很好了 Feign解决了什么问题 Feign的调用方式 Feign体系架构解析-武装到牙齿 上一节我们了解了feign的主要功能&#xff0c;它就像一个自拍杆一样&#xff0c;方便了Eureka的远程调用。可是怎么看…

Feign 的实现原理

Feign 实现原理 Feign是申明式的 HTTP 客户端。代码中创建一个接口并加上FeingClient 注解即可使用。其底层封装了 HTTP 客户端构建并发送的复杂逻辑。同时也可以整合注册中心及 Ribbon 为其提供负载均衡能力&#xff1b;通过整合 Histrix/sentinal 实现熔断限流功能。本期主要…

OpenFeign的实现原理(附Feign和OpenFeign的区别)

目录 问题现象&#xff1a; 问题分析&#xff1a; 拓展&#xff1a; 1、Feign&#xff1a; 2、OpenFeign&#xff1a; 问题现象&#xff1a; 最近在复习SpringCloud的时候看到一个面试题&#xff1a; OpenFeign的实现原理&#xff1f; 问题分析&#xff1a; OpenFeign是Sp…

Feign原理

是一个HTTP请求调用轻量级框架&#xff0c;可以以Java接口注解的方式调用HTTP请求&#xff0c;而不用像Java中通过封装HTTP请求报文的方式直接调用。 Feign解决了什么问题 在服务调用的场景中&#xff0c;我们经常调用基于HTTP协议的服务&#xff0c;而我们经常使用到的框架可…

Feign的使用及原理剖析

feign使用及原理剖析 一、简介 Feign是一个http请求调用的轻量级框架&#xff0c;可以以Java接口注解的方式调用Http请求。Feign通过处理注解&#xff0c;将请求模板化&#xff0c;当实际调用的时候&#xff0c;传入参数&#xff0c;根据参数再应用到请求上&#xff0c;进而转…

Feign基本使用(超详细)

目录 一、Feign概述 二、Feign入门 1.创建服务提供者(provider) 2.创建feign接口 3、创建服务消费者(consumer) 三、Feign 原理 四、Feign优化 1、开启feign日志 2、feign超时问题 3、http连接池 4、gzip压缩 前言 当我们通过RestTemplate调用其它服务的API时&#xff0c;所…

简单理解Feign的原理与使用

文章目录 SpringCloud 总架构图一、简介1.1、负载均衡的概念2.2、Feign概念 二、入门案例2.1、导入依赖2.2、Feign的客户端2.3、调用Feign2.4、开启Feign功能2.5、启动测试2.6、Feign实现原理简单分析 三、负载均衡(Ribbon)四、熔断器支持五、请求压缩和响应压缩六、配置日志级…

【第四章】详解Feign的实现原理

1.1 Feign概述 这篇文章主要讲述如何通过Feign去消费服务&#xff0c;以及Feign的实现原理的解析。 Feign是Netflix开发的声明式、模板化的HTTP客户端&#xff0c;Feign可以帮助我们更快捷、优雅地调用HTTP API。 Feign是⼀个HTTP请求的轻量级客户端框架。通过 接口 注解的…

Feign底层原理分析-自动装载动态代理

本篇文章仅介绍Feign的核心机制&#xff0c;包括如何交由Spring容器托管、动态代理机制等内容&#xff0c;不会过分深入细节。 1、什么是Feign&#xff1f; 这里套用Feign官方Github上的介绍&#xff1a;“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户…

Feign原理 (图解)

1.1 简介&#xff1a;Feign远程调用的 Feign远程调用&#xff0c;核心就是通过一系列的封装和处理&#xff0c;将以JAVA注解的方式定义的远程调用API接口&#xff0c;最终转换成HTTP的请求形式&#xff0c;然后将HTTP的请求的响应结果&#xff0c;解码成JAVA Bean&#xff0c;放…

Feign的工作原理

Feign的工作原理 Feign是一个伪Java Http 客户端&#xff0c;Feign 不做任何的请求处理。Feign 通过处理注解生成Request模板&#xff0c;从而简化了Http API 的开发。开发人员可以使用注解的方式定制Request API模板。 在发送Http Request请求之前&#xff0c;Feign通过处理…

Linux命令——tar与gzip详解:文件的打包压缩与解压缩解打包

Linux系统中&#xff0c;最常用的打包命令就是tar了&#xff0c;不仅如此&#xff0c;tar命令还可以解打包解压缩&#xff0c;十分方便。如果单纯想压缩文件&#xff0c;就需要我们的gzip命令了。 使用tar打包归档的包叫做tar包&#xff0c;以.tar结尾 使用gzip压缩的文件&…