在前面我们分别学习
Spring Cloud Ribbon
和
Spring Cloud Eureka
两个组件,今天我们学习
Spring Cloud
的第三个组件
OpenFeign
。
今天的文章分为如下两部分:
文章中的示例代码可自行到
github
下载:
Spring Cloud Sample
1 OpenFeign的使用
今天的代码我们还在之前的示例代码中进行修改,在product-service模块中加入如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在product-service模块的启动类上添加@EnableFeignClients
注解。
创建UserFeignClient
类,内容如下:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "user-service")
public interface UserFeignClient {
@GetMapping(value = "/user/{name}")
String getByName(@PathVariable String name);
在这个模块的启动类中注入UserFeignClient
并创建一个接口,如下
@Resource
private UserFeignClient userFeignClient;
@GetMapping(value = "/product/user/{name}/feign")
public String getUserByNameFromFeign(@PathVariable String name) {
return userFeignClient.getByName(name);
至此我们通过OpenFeign调用其他服务的示例就完成了,大家可以自行测试,效果会和之前的示例一致。
OpenFeign封装了http客户端,让我们像调用本地方法的方式调用远程服务。
2 OpenFeign的原理
通过上面的示例可以看出,我们使用OpenFeign可以很方便的调用一个http接口。那么OpenFeign是如何实现帮我们实现这些功能的呢?我们在下面内容中将通过查看源码的方式来学习下其工作原理。
使用OpenFeign我们需要在服务的启动类上添加@EnableFeignClients
注解,这个注解的源码如下:
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
这个注解上会通过@Import
引入FeignClientsRegistrar
,这个类实现了ImportBeanDefinitionRegistrar
,在Spring容器启动时会加载这个类中的registerBeanDefinitions
方法,这个方法的逻辑如下:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
// 注册feign客户端
registerFeignClients(metadata, registry);
这个主要看registerFeignClients
方法,其逻辑如下:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
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");
if (clients == null || clients.length == 0) {
// 获取需要扫描包路径下有FeignClient注解的类
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 {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// FeignClient修饰的类必须是接口
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);
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册feignClient
registerFeignClient(registry, annotationMetadata, attributes);
在上面这段代码中主要的逻辑就是解析出项目可扫描路径下被@FeignClient修饰的接口,然后调用registerFeignClient
方法,注入到Spring容器中,其逻辑如下:
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;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
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));
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 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);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerOptionsBeanDefinition(registry, contextId);
上面这段代码看着很长,其实不用全部看,这段代码中我们可以看见向容器里注册的是一个FeignClientFactoryBean
,当我们从容器中获取对应对象时,会调用这个类中的getObject
方法,其逻辑如下:
public Object getObject() {
return getTarget();
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
if (url != null && LOG.isWarnEnabled()) {
LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");
else if (LOG.isDebugEnabled()) {
LOG.debug("URL not provided. Will use LoadBalancer.");
if (!name.startsWith("http")) {
url = "http://" + name;
else {
url = name;
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, 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 unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
上面的这段代码中两个分支最后都会调用到ReflectiveFeign.newInstance
方法,这个方法的逻辑如下:
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 方法和SynchronousMethodHandler对象Map
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
// 这个方法是Object中的方法,不进行处理
if (method.getDeclaringClass() == Object.class) {
continue;
// 是否为default方法
} 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对象 ReflectiveFeign.FeignInvocationHandler
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;
通过上面的代码我们看到,这里会返回一个通过动态代理创建的代理对象。这部分的调用时序图如下图所示。
通过上面的代码我们知道我们使用的FeignClient对象是一个代理对象,当我们调用相应的方法时会调用到InvocationHandler.invoke
方法中,所以会调用ReflectiveFeign.FeignInvocationHandler.invoke
方法,这个方法的逻辑如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// equals方法
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;
// hashCode方法
} else if ("hashCode".equals(method.getName())) {
return hashCode();
// toString方法
} else if ("toString".equals(method.getName())) {
return toString();
// SynchronousMethodHandler中的invoke方法
return dispatch.get(method).invoke(args);
再往下会调用到SynchronousMethodHandler.invoke
、SynchronousMethodHandler.executeAndDecode
和FeignBlockingLoadBalancerClient.execute
方法,前两个方法的代码这里就不进行粘贴了,第三个方法的逻辑如下:
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
// 获取调用的服务id
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 通过负载均衡器选择出一个服务
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
instance);
// 未找到服务节点
if (instance == null) {
String 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<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
// 真正的请求地址
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors);
再往下的逻辑就是调用Feign封装的http请求,这里就不进行粘贴了。这个方法的处理逻辑如下:
通过负载均衡器选择出一个服务节点 我使用的SpringCloud版本比较高,这里的负载均衡器不是Ribbon了
获取真正的请求地址
发起请求并返回结果
到这里我们这片关于Spring Cloud OpenFeign的文章就结束了,在这片文章中我们介绍了Spring Cloud OpenFeign的使用及原理。
通过@EnableFeignClients
注解导入FeignClientsRegistrar
对象,当Spring容器启动时会调用这个类中的registerBeanDefinitions
方法,在这里会将@FeignClient
修饰的类进行注册。
注册到Spring容器中的是一个FeignClientFactoryBean
对象
FeignClientFactoryBean
实现了FactoryBean
,当我们使用FeignClient时,会调用到这个类中的getObject
方法,在这里是通过动态代理创建一个代理对象
Spring Cloud OpenFeign集成了负载均衡器,发送请求前,会先通过负载均衡器选择出一个需要调用的实例
欢迎关注公众号【Bug搬运小能手】持续更新Java相关知识
大飞学习笔记
Spring Cloud