Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign 灵感来源于 Retrofit , JAXRS-2.0 和 WebSocket,Feign 最初是为了降低统一绑定 Denominator 到 HTTP APIs 的复杂度,不管是否是 Restful。
Feign旨在使编写Java Http客户端变得更容易,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring Cloud时,自动封装服务调用客户端的开发量。
| Feign | OpenFeign |
|---|---|
| Feign是spring cloud组件中的一个轻量级Restful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 | OpenFeign是Spring Cloud在Feign的基础上支持了Spring MVC的注解,如@RequestMapping等等。OpenFeign的@FiegnClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务 |
- 在Spring Cloud应用中使用Feign组件,首先需要在依赖包中加入以下依赖:
org.springframework.cloud spring-cloud-starter-openfeign
- 再定义一个可供调用的Feign组件示例如下:
@FeignClient(value = "servicename")
public interface IUserService {
@RequestMapping(value = "getUserDetailInfo", method = RequestMethod.GET)
ApiResult getUserDetailInfo(@RequestParam("username") String username);
}
上述代码中,每一个方法都代表通过 Feign 请求的一个接口, @RequestMapping 指定请求地址和请求方法。 @FeignClient 则用于指定调用的微服务,结合负载均衡框架在注册中心注册的服务列表中选择一个合适的服务地址。 目前对FeignClient的应用主要有2种方式:
- 服务提供方定义好 Feign 组件,自己的 Controller 实现定义好的Feign组件接口,然后把Feign组件打包成SDK提供给调用方,这样的好处是便于后期服务提供方统一升级组件,比如更换调用路径和参数等;
- 服务调用方自己封装 Feign 组件,这样的好处是调用方可以灵活的自定义Feign组件;
注意:目前Feign调用对基本类型参数传值支持不是很好,所以参数最好封装成一个 DTO对象 进行传输。如果是JSON参数,最好再定义Feign组件时加上 @RequestBody 注解。
- 启用Feign组件是使用Feign的第一步,使用如下注解开启:
//启用Feign组件并配置扫描包路径
@EnableFeignClients(basePackages = {"com.xxx.service.api"})
- 接下来就是在需要调用接口的地方用Spring Bean一样调用其他服务了,示例如下:
@Autowired private IUserService userservice;3. OpenFeign源码解析 3.1. Feign Bean创建
Feign组件初始化是从 @EnableFeignClients 注解开始的,注解源码截图如下:
@EnableFeignClients 核心有2个方法, basePackages 与 defaultConfiguration ,前者用于定义扫描包路径,后者用于定义@FeignClient组件的配置类,在配置类中可以自己定义Feign请求的 Decoder 解码器、 Encoder 编码器、 Contract 组件扫描构造器。 在注解上有一个关键注解 @Import(FeignClientsRegistrar.class) ,导入了Feign组件的注册器,用于扫描Feign组件与初始化Feign组件的Bean定义信息,各阶段作用建下述源码注释。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
FeignClientsRegistrar() {
}
static void validateFallback(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient");
}
static void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "
+ "of fallback classes that implement the interface annotated by @FeignClient");
}
static String getName(String name) {
if (!StringUtils.hasText(name)) {
return "";
}
String host = null;
try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
}
else {
url = name;
}
host = new URI(url).getHost();
}
catch (URISyntaxException e) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
}
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
static String getPath(String path) {
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map 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) {
LinkedHashSet candidateComponents = new LinkedHashSet<>();
Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class>[] clients = attrs == null ? null : (Class>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set 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();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map 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);
}
}
核心代码实际可以只关注以下 registerFeignClient 方法片段,定义好了创建Feign组件Bean的FactoryBean:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Mapattributes) { 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 对象, BeanDefinitionBuilder.genericBeanDefinition 设置Url,Path,decode404,以及降级fallback等属性,返回 BeanDefinitionBuilder 的对象,可以看到, BeanDefinitionBuilder 的构造返回的是 FeignClientFactoryBean 的 getObject() 方法。
接下来可以查看Feign代理Bean实例是如何创建的的,参见 FeignClientFactoryBean 源码:
public class FeignClientFactoryBean implements FactoryBean
在上述源码中,最核心的是 getObject 方法,方法中定义好了如何去初始化一个FeignClient组件,在代理Bean中织入了哪些方法,具体可以参见代码中文注释。 我们先暂时略过上述工厂Bean创建代理对象时,使用到Feign的其他组件,如:Encoder、Decoder、Contract等,后面再详细阐述。 当调用target方法时,会触发实际会触发FeignBuiler组件的newInstance源码如下:
class DefaultTargeter implements Targeter {
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget target) {
return feign.target(target);
}
}
Feign.Builder.target 会触发 Feign.Builer 组件的 newInstance
Feign.Builer 组件的 newInstance 源码如下:
@Override publicT newInstance(Target target) { //核心方法,解析定义的@FeignClient组件中的方法和请求路径 Map nameToHandler = targetToHandlersByName.apply(target); Map methodToHandler = new LinkedHashMap (); List defaultMethodHandlers = new LinkedList (); 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))); } } //调用工厂Bean,创建执行Handler MethodHandler是feign的一个接口,这个接口的invoke方法,是动态代理调用者InvocationHandler的invoke()方法最终调用的方法。 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; } //请求方法和路径解析器 static final class ParseHandlersByName { private final Contract contract; private final Options options; private final Encoder encoder; private final Decoder decoder; private final ErrorDecoder errorDecoder; private final QueryMapEncoder queryMapEncoder; private final SynchronousMethodHandler.Factory factory; ParseHandlersByName( Contract contract, Options options, Encoder encoder, Decoder decoder, QueryMapEncoder queryMapEncoder, ErrorDecoder errorDecoder, SynchronousMethodHandler.Factory factory) { this.contract = contract; this.options = options; this.factory = factory; this.errorDecoder = errorDecoder; this.queryMapEncoder = queryMapEncoder; this.encoder = checkNotNull(encoder, "encoder"); this.decoder = checkNotNull(decoder, "decoder"); } public Map apply(Target target) { //核心方法:解析@FeignClient组件,Feign自带的则解析自己的注解格式,Spring提供了解析MVC注解的SpringMvcContract List metadata = contract.parseAndValidateMetadata(target.type()); Map result = new LinkedHashMap (); for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else if (md.bodyIndex() != null || md.alwaysEncodeBody()) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else { buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target); } if (md.isIgnored()) { result.put(md.configKey(), args -> { throw new IllegalStateException(md.configKey() + " is not a method handled by feign"); }); } else { result.put(md.configKey(), factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } } return result; } }
解析@FeignClient注解,生成MethodHandler,具体的解析类是 ParseHandlerByName 。这个类是 ReflectiveFeign 的内部类。
ParseHandlerByName.apply 解析@FeignClient组件.
Feign自带的则解析自己的注解格式,Spring提供了解析MVC注解的SpringMvcContract
Listmetadata = contract.parseAndValidateMetadata(target.type());
newInstance 拿到注解元数据以后,循环处理注解元数据,创建每个方法对应的 MethodHandler ,这个 MethodHandler 最终会被代理对象调用。最终 MethodHandler 都会保存到下面这个集合中,然后返回。
MethodHandler 是feign的一个接口,这个接口的 invoke 方法,是动态代理调用者 InvocationHandler的invoke() 方法最终调用的方法。
MapnameToHandler = targetToHandlersByName.apply(target); Map methodToHandler = new LinkedHashMap (); List defaultMethodHandlers = new LinkedList (); 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 的 invoke() 方法最终回调 MethodHandler 的 invoke() 来发送http请求。这就是Feign动态代理的具体实现。
// 创建动态代理调用者
InvocationHandler handler = factory.create(target, methodToHandler);
// 反射生成feign接口代理
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class>[] {target.type()}, handler);
InvocationHandler.invoke() 的具体实现在 FeignInvocationHandler.invoke() , FeignInvocationHandler 也是 ReflectiveFeign 的一个内部类。里面有很多细节处理这里不再赘述,我们直接进入核心那一行代码,以免影响思路,我们是理Feign的实现原理的!不要在意这些细节!
// InvocationHandler 的 invoke() 方法最终回调 MethodHandler 的 invoke() 来发送http请求
ReflectiveFeign类的invoke()方法,第100行,代码的后半段,如下:
(MethodHandler)this.dispatch.get(method). invoke(args);
this.dispatch:这是一个map,就是保存所有的MethodHandler的集合。参考创建InvocationHandler的位置:ReflectiveFeign类的newInstance()方法的第64行。
this.dispatch.get(method):这里的method就是我们开发者写的feign接口中定义的方法的方法名!这段代码的意思就是从MethodHandler集合中拿到我们需要调用的那个方法。
this.dispatch.get(method).invoke(args):这里的invoke就是调用的MethodHandler.invoke(),动态代理回调代理类,就这样完成了,oh my god,多么伟大的创举!
MethodHandler.invoke() 的具体实现: SynchronousMethodHandler.invoke()
到了这里,就是发送请求的逻辑了。发送请求前,首先要创建请求模板,然后调用请求拦截器RequestInterceptor进行请求处理。
@Override
public Object invoke(Object[] argv) throws Throwable {
// 创建RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
// 创建feign重试器,进行失败重试
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;
}
}
}
RequestTemplate模板需要经过一系列拦截器的处理。我们可以自定义请求拦截器,我们自定义的拦截器,也会在此时进行调用,所有实现了RequestTemplate接口的类,都会在这里被调用。比如我们可以自定义拦截器把全局事务id放在请求头里。
到此源码解析完毕
3.2 配置与优化-
超时时间配置
在自动装配属性 FeignClientProperties.FeignClientConfiguration 类中
使用yaml
feign:
client:
config:
default:
connectTimeout: 500
readTimeout: 15000
- 请求重试
Feign的重试是通过配置Retryer来实现,在 FeignClientsConfiguration 自动配置类中,配置了一个默认的 Retryer.NEVER_RETRY ,表示用不重试。不重试和重试超过限制次数都是抛出异常来停止重试。可以通过自定义 Retryer 来覆盖默认的配置
- 编码/消息头处理
对具体调用请求可以通过3种方式来进行编解码处理:
- 定义 RequestInterceptor ,在请求前处理编码与附加消息头;
- 自定义 Encoder ,在编码阶段处理请求头;
- 在 @FeignClient 组件接口 @RequestMapping 的 headers 属性中附带消息头信息;
- 其他事项
- 在定义Feign组件时, @RequestMapping 注解只加在方法上,不要放在类上,Spring MVC自带的Dispacher请求映射会扫描所有带 @RequestMapping 类,容易导致一些不必要的问题;
Feign架构图
- 基于JDK动态代理生成代理类。
- 根据接口类的注解声明规则,解析出底层MethodHandler
- 基于RequestBean动态生成request。
- Encoder将bean包装成请求。
- 拦截器负责对请求和返回进行装饰处理。
- 日志记录。
- 基于重试器发送http请求,支持不同的http框架,默认使用的是HttpUrlConnection。



