前言
由于公司正在高sky迁移,我们部门的老应用SpringBoot 1.x升级2.x,其中老的Neflix Feign也升级成了Spring Cloud OpenFeign,由于业务条线的各种高低版本以及服务之间调用等存在一些兼容性问题,于是看了下OpenFeign的源码,本文采用UML时序图+文字描述+代码描述+代码注释的形式来对OpenFeign的完整生命周期做剖析,希望能对大家老应用升级中遇到的类似服务调用问题提供一些帮助。
流程说明
-
服务启动类上添加@EnableFeignClients注解;
-
当程序启动时,会进行包扫描,扫描所有@FeignClients的注解的类,并且将这些信息注入Spring IOC容器中;
-
当定义的的Feign接口中的方法被实例化时,通过JDK动态代理方式从容器中获取到bean;
-
当服务被调用时,FeignClient 接口类的实例生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装可HTTP请求需要的全部信息,如请求参数名,请求方法等信息都是在这个过程中确定的,然后RequestTemplate生成Request,然后把Request交给Client去处理,完成服务的调用。
源码分析
一、动态注册Bean
1、入口从启动类开始,引入了@EnableFeignClients注解,如下:
@Slf4j
@EnableFeignClients({"com.xxxx.xxxx.feign"})
@SpringBootApplication
@ImportResource(locations={"classpath:beanRefContext.xml"})
public class xxxxApplication {
/**
* 项目启动类
*
* @param args 启动参数
*/
public static void main(String[] args) {
BootApplication.run(log, false, xxxxApplication.class, args);
String deployEnv = DeployEnvEnum.getDeployEnv();
log.info("xxxxApplication start completed... ");
}
}
2、点进去,发现通过import注解导入了FeignClientsRegistrar
配置类,代码如下:
@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 {};
}
3、FeignClientsRegistrar 点进去,
这个类实现了ImportBeanDefinitionRegistrar
接口,重写registerBeanDefinitions
方法,代码如下:
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
//省略代码...
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册全局配置,解析EnableFeignClients注解上配置的defaultConfiguration属性
registerDefaultConfiguration(metadata, registry);
//扫描指定的所有包名下的被@FeignClient注解注释的接口,将扫描出来的接口调用registerFeignClient方法注册到spring容器
registerFeignClients(metadata, registry);
}
//省略代码...
4、先执行registerDefaultConfiguration方法,如下:
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"));
}
}
5、然后再执行registerFeignClients方法,如下:
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 扫描带有FeignClient注解的类
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//获取@EnableFeignClients 中clients的值
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));
// 如果没有设置,则扫描的包路径为 @EnableFeignClients 注解所在的包
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();
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"));
//注册 FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
6、registerFeignClients方法到最后调用了registerFeignClient方法,registerFeignClient方法代码如下:
//注册 FeignClient,组装BeanDefinition,实质是一个FeignClientFactoryBean,然后注册到Spring IOC容器
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
构建FeignClientFactoryBean类型的BeanDefinitionBuilder
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//将属性设置到 FeignClientFactoryBean中
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
//将BeanDefinition包装成BeanDefinitionHolder,用于注册
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//注册BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
可以看到,这个方法就是将FeignClient注解上的属性信息,封装到BeanDefinition中,并注册到Spring容器中。但是在这个方法中,有一个关键信息,就是真实注册的FeignClientFactoryBean
,它实现了FactoryBean
接口,表明这是一个工厂bean,用于创建代理Bean,真正执行的逻辑是FactoryBean
的getObject
方法。至此,FeignClient的动态注册Bean就完成了。
二、实例初始化
FeignClientFactoryBean 是工厂类, Spring 容器通过调用它的getObject 方法来获取对应的Bean 实例。被@FeignClient 修饰的接口类都是通过FeignClientFactoryBean 的getObject方法来进行实例化的。
1、先看方法getObject,如下:
@Override
public Object getObject() throws Exception {
return getTarget();
}
2、然后调用getTarget方法,代码如下:
<T> T getTarget() {
//实例化Feign上下文对象FeignContext
FeignContext context = applicationContext.getBean(FeignContext.class);
//生成Builder对象,用来生成Feign
Feign.Builder builder = feign(context);
//如果url为空,则走负载均衡,生成有负载均衡功能的代理类
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//@FeignClient没有配置url属性,返回有负载均衡功能的代理对象
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 LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
//生成默认代理类
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
3、getTarget方法最后调用targeter.target方法,Targeter.target方法代码如下:
interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target);
}
其中target接口有两个实现类,如下截图:
4、我们没有使用Hystrix,走DefaultTargeter方法,代码如下:
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
5、然后feign.target点进去,代码如下:
public <T> T target(Target<T> target) {
return this.build().newInstance(target);
}
6、然后newInstance方法点进去,如下:
public abstract <T> T newInstance(Target<T> var1);
7、然后调用ReflectiveFeign.newInstance,代码如下:
public <T> T newInstance(Target<T> target) {
// 解析接口注解信息,根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
Method[] var5 = target.type().getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method method = var5[var7];
if (method.getDeclaringClass() != Object.class) {
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 handler = this.factory.create(target, methodToHandler);
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理
T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
Iterator var12 = defaultMethodHandlers.iterator();
while(var12.hasNext()) {
DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
最后到这里生成了动态代理,把接口中的方法和默认实现放到Map<Method, MethodHandler>中,然后使用InvocationHandlerFactory.Default()创建InvocationHandler,最后使用jdk动态代理生成接口的代理并返回;
到这里二方服务定义的接口就被@EnableFeignClient通过动态代理的方式注入到了服务中;
三、服务调用
1、前面通过@Autowired或者@Resource注入的时候,注入的是被封装之后的代理类实现,jdk动态代理持有的是ReflectiveFeign.FeignInvocationHandler类型的InvocationHandler,那么具体调用的时候,会调用FeignInvocationHandler#invoke,代码如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
// 利用分发器筛选方法,找到对应的handler 进行处理
return "toString".equals(method.getName()) ? this.toString() : ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
2、然后在invoke
方法中,会调用 this.dispatch.get(method)).invoke(args)
;this.dispatch.get(method)
会返回一个SynchronousMethodHandler,
进行拦截处理。这个方法会根据参数生成完成的RequestTemplate
对象,这个对象是Http请求的模版,代码如下。 看SynchronousMethodHandler
中的invoke
类
public Object invoke(Object[] argv) throws Throwable {
//根据参数生成RequestTemplate对象
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Request.Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
//通过RequestTemplate生成Request请求对象
return this.executeAndDecode(template, options);
} catch (RetryableException var9) {
RetryableException e = var9;
try {
retryer.continueOrPropagate(e);
} catch (RetryableException var8) {
Throwable cause = var8.getCause();
if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
throw cause;
}
throw var8;
}
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
3、然后再看executeAndDecode()
方法,该方法通过RequestTemplate
生成Request
请求对象,然后利用Http Client(默认)
获取response
,来获取响应信息,代码如下:
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
//转化为Http请求报文
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
//发起远程通信
response = this.client.execute(request, options);
//获取返回结果
response = response.toBuilder().request(request).requestTemplate(template).build();
} catch (IOException var13) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel, var13, this.elapsedTime(start));
}
throw FeignException.errorExecuting(request, var13);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (this.decoder != null) {
return this.decoder.decode(response, this.metadata.returnType());
} else {
CompletableFuture<Object> resultFuture = new CompletableFuture();
this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);
try {
if (!resultFuture.isDone()) {
throw new IllegalStateException("Response handling not done");
} else {
return resultFuture.join();
}
} catch (CompletionException var12) {
Throwable cause = var12.getCause();
if (cause != null) {
throw cause;
} else {
throw var12;
}
}
}
}
到此,整个流程就结束了...
总结
- FeignClientsRegistrar:扫描@FeignClient注解的接口并注册成BeanDefinition到应用容器
- FeignClientFactoryBean:注入远程服务接口代理实现
- FeignContext:维护服务维度上下文,存储服务调用相关工具
- Feign.Builder:用于构建服务调用的Client,维护服务调用相关组件(编解码器、重试组件、代理创建工厂等)
- Targeter:用于组装服务接口代理
- Feign:用于创建服务接口代理
- ReflectiveFeign:接口代理实现
- Client:封装网络调用工具,并提供请求调用能力(Default、ApacheHttpClient和OkHttpClient等)
引用
一文看懂Openfeign服务调用原理_叔牙的博客-CSDN博客_openfeign调用原理
Spring Cloud OpenFeign源码解析 - 知乎
Spring Cloud——OpenFeign源码解析 - 简书
Spring Cloud OpenFeign源码分析_51CTO博客_spring-cloud-starter-openfeign