前言
微服务调用中我们最常用的就是使用feign,使用feign往往离不开@EnableFeignClients和@FeignClient, 在如此简单的使用下,我们或多或少的要去关心一下他的底层实现。
Feign的注册
@EnableFeignClients
@EnableFeignClients中导入了FeignClientsRegistrar
这里实现了ImportBeanDefinitionRegistrar并重写了Bean注册方法
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册默认的配置
this.registerDefaultConfiguration(metadata, registry);
//注册FeignClients
this.registerFeignClients(metadata, registry);
}
这里注册了两类东西,我们一个一个来去看
注册默认配置
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//获取注解上的属性
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
//这里的逻辑就是判断一下是不是内部类,然后组装name
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
//创建一个FeignClientSpecification的Bean定义并且设置构造方法
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//注册
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
这里可以看到注册的配置信息是从EnableFeignClients中的defaultConfiguration取出的,一般用作自定义 Feign 的 Encoder、Decoder、LogLevel、Contract。
注册FeignClients
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//获取EnableFeignClients注解上的属性信息
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// 如果clients属性为null,则获取basePackages属性,扫描其中的所有client
if (clients == null || clients.length == 0) {
//获取扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
//扫描带有FeignClient注解的类
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
//获取@FeignClients注解中的扫描包路径
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
//扫描获取Beand定义
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
//如果clients属性不为null,则直接注入
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");
//获取@FeignClient的属性信息
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//这和上面注册默认配置一样
registerClientConfiguration(registry, name, attributes.get("configuration"));
//核心方法 真正的开始注册加了@FeignClients注解的类
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
上面的逻辑是在扫描@FeignClients获取Bean定义,然后注册一下@FeignClients中属性configuration的配置类。
真正的注册加了@FeignClients类的核心代码在下面,他的传参并不是直接传的刚刚扫描的Bean定义,而是Bean定义的元数据。这么做是因为他要构建一个新的Bean定义而不是去使用直接扫描出来的,具体看下面源码。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
//类全路径名称
String className = annotationMetadata.getClassName();
//加载类
Class clazz = ClassUtils.resolveClassName(className, null);
//获取Bean工厂
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
//这里其实是把加了刚刚加载的类封装成FeignClientFactoryBean
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();
});
//下面就是设置一些属性然后注册Bean了
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实现了FactoryBean
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));
}
//最终生成的Bean定义是这里获取的返回值
return factoryBean.getObject();
});
@Override
public Object getObject() {
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)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
}
//如果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;
}
//如果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));
}
创建负载均衡的代理对象
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?");
}
这里的主要逻辑:
- 如果没有指定url则创建负载均衡的代理对象。先是拿出client,然后获取client的代理对象。我们最终使用的FeignClient是Client的动态代理对象,而Client对象是真正执行http请求的对象。
- 如果没有指定url则不必要进行负载均衡,这里是做了两个if判断,client属于FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient会代理他们中的delegate。
- 最终都要去执行targeter.target()方法
调用链Targeter.target()→DefaultTargeter.target()→Feign.Builder.target()→Feign.newInstance()→ReflectiveFeign.newInstance()。
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>();
//遍历类中方法
for (Method method : target.type().getMethods()) {
//Obbject的方法
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
//default方法创建DefaultMethodHandler处理
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
//这一步method就都是用户创建的,从nameToHandler取出
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//动态代理逻辑
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的调用
刚刚只是讲了加了@FeignClients注解的类如何生成代理对象并注册到容器中,接下来就要看在使用的时候他是如何帮我们去进行的远程调用。
既然是代理对象就会调用代理逻辑,我们看刚刚创建动态代理逻辑的方法
调用链路:factory.create -> Default#create -> FeignInvocationHandler
我们看到他实现了InvocationHandler,代理逻辑就会走invoke方法
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
//存放着刚刚的methodToHandler
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@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);
}
}
public Object invoke(Object[] argv) throws Throwable {
// 解析请求参数,封装 RequestTemplate\
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 获取 Request.Options 对象,封装了请求的 connectTimeout/readTimeout 等信息
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;
}
}
}