是一个HTTP请求调用轻量级框架,可以以Java接口注解的方式调用HTTP请求,而不用像Java中通过封装HTTP请求报文的方式直接调用,下面详细介绍。
这里套用Feign官方Github上的介绍:“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户端,Feign的主要目标是降低大家使用Http API的复杂性”。
其实,Feign底层依赖于Java的动态代理机制,对原生Java Socket或者Apache HttpClient进行封装,实现了基于Http协议的远程过程调用
。
当然,Feign还在此基础上实现了负载均衡、熔断
等机制。
使用过Feign的同学都知道,@EnableFeignClients注解是开启Fiegn功能的关键,我们通常会在该注解中添加FeignClient的所在包,以便Spring容器能够扫描到所有的FeignClient,并进行托管。后面我们便可以使用@Autowired注解自动导入了。
@SpringBootApplication
@EnableFeignClients(basePackages = {"com.**.feign"})
public class Application {}
该注解样式也是很多第三方包集成Springboot所使用的套路:一般都是开启该注解后,Springboot便可以自动装载第三方包所指定的Class,我们便可以直接使用第三方包所提供的功能,非常方便。
接下来不会详细介绍自动装载的部分,而是直接给出自动装载的主脉络,看看Spring容器到底装载了什么bean。
首先进入@EnableFeignClients源码中,查看该注解导入了什么Registrar注册器,这个注册器便是自动装载的关键。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...}
从源码中可以看出,@EnableFeignClients 注解导入的是自定义的FeignClientsRegistrar类。
这种类型的注册器一般会继承Spring中的ImportBeanDefinitionRegistrar接口,并在registerBeanDefinitions实现方法中向Spring容器注册一些bean,以达到自动注入第三方功能的目的。
// [1] 继承ResourceLoaderAware和EnvironmentAware
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// [2] 注册默认的Feign配置
registerDefaultConfiguration(metadata, registry);
// [3] 注册所有定义的FeignClient
registerFeignClients(metadata, registry);
}
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// [1] 获取clients属性
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// [2] 如果clients属性为null,则获取basePackages属性,扫描其中的所有client
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 {
// [3] 如果clients属性不为null,则直接注入
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// [4] 遍历所有的clients,将其封装为BeanDefinition注册进Spring容器中
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);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
registerFeignClients方法的代码稍微有点多,但主要思路就是解析@EnableFeignClients注解,根据解析信息获取FeignClients,然后包装成BeanDefinition注册进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);
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);
}
registerFeignClient()是对单个FeignClient注册的方法。这个方法乍一看很长,但其实主线非常清晰。
就是将扫描到的BeanDefinition中的元信息提取出来,然后构造成一个Feign自定义的FactoryBean,即FeignClientFactoryBean,后面我们每次获取容器中的FeignClient时,就会通过该FactoryBean的getObject()方法中获取(这个涉及到了Spring容器中普通bean和FactoryBean的区别,大家可以自行去了解下)。
方法中的其它部分都是为上面所说的逻辑服务,包括FactoryBean的构造,BeanDefinition注入到容器中等过程。我们不必太过关心,只要抓住重点即可。
讲到这里,其实自动装载过程已经完成了,容器中已经包含了自定义的FeignClientFactoryBean。这里用一张流程图总结下自动装载的全过程。
但是我们暂时还不知道自动装载的FeignClientFactoryBean到底做了什么。下面我们就深入去了解。
FeignClientFactoryBean的源码如下:
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
@Override
public Object getObject() {
// [1] 真正的获取对象的方法委托给getTarget()方法了
return getTarget();
}
<T> T getTarget() {
// 获取Feign的上下文
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
// [2] 如果url没有定义,则进入到该判断中创建对象,该判断中创建的对象具有负载均衡功能
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;
}
// [3] 如果url定义了,说明用户指定了某台机器,也就没有必要进行负载均衡了,则从下面的方法创建对象
String url = this.url + cleanPath();
// 可以发现下面的执行逻辑和loadBalance()非常像,只是多了两个if判断,这两个if判断就是移除负载均衡的关键
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));
}
}
// 创建负载均衡的client
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}
getObject()会根据用户定义的FeignClient是否定义url属性决定是否返回具有负载均衡属性的对象。具体过程如下:
从上面的源码中可知,无论是否需要负载均衡,都会通过targeter.target()方法创建动态代理对象。我们这里跳过中间不重要的环节,给出targeter.target()不太重要的调用栈,大家可以自行查看:Targeter.target()→DefaultTargeter.target()→Feign.Builder.target()→Feign.newInstance()→ReflectiveFeign.newInstance()。
接下来我们来到了ReflectiveFeign.newInstance()
这个重要的方法:
@Override
public <T> T newInstance(Target<T> target) {
// [1] nameToHandler 里面基本上是SynchronousMethodHandler,主要用于处理用户自定义的方法
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
// [2] DefaultMethodHandler用于处理接口中default方法
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
// 遍历接口中的所有方法
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
// [3] 如果是Object中的方法,直接跳过
continue;
} else if (Util.isDefault(method)) {
// [4] 如果是default方法,则创建DefaultMethodHandler处理
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// [5] 其它的都是用户自定义的方法了,此时从nameToHandler中拿出SynchronousMethodHandler进行映射
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// [6] 这个就是动态代理的关键了,代理逻辑都在该handler中了
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;
}
从newInstance中我们看到了熟悉的proxy和InvocationHandler,这也就说明了Feign的底层还是依赖了JDK的动态代理:
后面就是创建代理对象并返回了。
下面我们来看核心代理逻辑究竟做了什么,InvocationHandler实现类为FeignInvocationHandler,是ReflectiveFeign的静态内部类。
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
// dispatch就是我们上文提到的methodToHandler
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这下面都是判断是否为Object中的方法
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();
}
// 如果是用户自定义的远程调用方法,则执行MethodHandler中的invoke方法
return dispatch.get(method).invoke(args);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FeignInvocationHandler) {
FeignInvocationHandler other = (FeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
}
FeignInvocationHandler 还是非常简单的,dispatch就是我们上文提到的methodToHandler(method→MethodHandler)映射,在执行invoke方法时:
这里我们仅关心远程调用的实现机制,因此下面我们将进入到SynchronousMethodHandler中,观察invoke()方法的执行逻辑:
@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;
}
}
}
invoke方法将具体的远程调用委托给executeAndDecode()执行,从方法名可知,该方法不仅执行http远程调用,同时还会对response进行节码操作,这也是Feign非常方便的一点,能够让开发者忽略http报文解析的过程。
invoke还提供了失败重试机制,主要逻辑由Retryer 这个对象实现,感兴趣的小伙伴可以自行了解。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 构造请求对象
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 执行http请求
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
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);
}
// ...忽略解码过程
}
executeAndDecode()方法首先构造请求对象,然后使用client对象发起http请求。之前说了,client可以是Apache HttpClient或者Feign封装的具有负载均衡能力的FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient,但这两个client的execute()方法底层最终会调用其中的delegate(即Apache HttpClient)执行http远程调用。
讲到这里,FeignClientFactoryBean#getObject方法的执行逻辑我们也非常清楚了,就是通过JDK的动态代理,对Apache HttpClient进行多层封装,以实现远程调用的能力。这里也用一张图梳理下整个过程:
本文主要介绍了Feign的自动装载和动态代理机制,并梳理了这两个机制的主要脉络,而忽略其它次要信息。
Feign能够被众多开发人员所使用绝不仅仅是具备以上介绍的两个功能,它还提供了诸如负载均衡、熔断等机制,这些也在文章中有少量提及,大家感兴趣的话,可以沿着本文所介绍的主脉络,一一梳理下这些功能。