Spring Cloud OpenFeign
底层实现原理
先说一下写这篇文章的一个原因,就是我被面试官吊打了,我只知道OpenFeign
底层采用了RestTemplate进行调用,采用了动态代理,但是具体怎么实现的我就母鸡了。为了防止同样的地方摔倒,我决定我现在这里爬起来。
一 、简介
OpenFeign
是Spring Cloud 在Feign的基础上支持了Spring MVC
的注解,如@RequesMapping等等。
OpenFeign
的@FeignClient
可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
二、OpenFeign
的使用
1. 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 启动类添加@EnableFeignClients
注解
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients(basePackages = {"xx.xx"})
@ComponentScan(basePackages = {"xx.xx"})
public class TagApplication {public static void main(String[] args) {SpringApplication application = new SpringApplication(TagApplication.class);}
}
3. 编写业务类
@FeignClient(name = "user-service")
public interface BaseFeignApi {@GetMapping("/list")List<User> getList();
}
三、OpenFeign
实现原理
1.@EnableFeignClients(basePackages = {"xx.xx"})
注解
@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 {};
}
2.FeignClientsRegistrar
类
2.1 FeignClientsRegistrar
类
class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {....
}
2.2ImportBeanDefinitionRegistrar()
函数
public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {this.registerBeanDefinitions(importingClassMetadata, registry);}default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}
}
FeignClientsRegistrar
本质是一个ImportBeanDefinitionRegistrar
,并且持有环境变量和资源加载器的能力,FeignClientsRegistrar
重写了registerBeanDefinitions
方法,该方法在容器上下文刷新(启动时调用refresh)时被调用,调用时机此处不展开分析,我们看一下.
2.3FeignClientsRegistrar
#registerBeanDefinitions()
函数
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 处理@EnableFeignClients注解上的属性配置,将配置注册到容器仲registerDefaultConfiguration(metadata, registry);// 核心方法:注册@FeignClient对应的接口,奖@FeignClient注册到容器中registerFeignClients(metadata, registry);
}
2.4FeignClientsRegistrar
#registerFeignClients()
函数
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 创建Spring内置的扫描器ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);Set<String> basePackages;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) {// 添加过滤器:过滤器所有被 @FeignClient 标记接口scanner.addIncludeFilter(annotationTypeFilter);basePackages = getBasePackages(metadata);}else {final Set<String> clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class<?> clazz : clients) {basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {@Overrideprotected boolean match(ClassMetadata metadata) {String cleaned = metadata.getClassName().replaceAll("\\$", ".");return clientClasses.contains(cleaned);}};scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}// 处理@FeignClient类,解析for (String basePackage : basePackages) {Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition 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);}}}}
第一部分是为了找到@Feignclient标识的接口类,第二部分就是对找出的接口类进行处理了处理,主要关注registerClientConfiguration
和registerFeignClient
函数。
其中registerClientConfiguration
是为了处理@FeignClient#configuration
属性的,在这个函数会往spirng容器中添加#{serviceName}.FeignClientSpecification
作为名字的FeignClientSpecification
类对象,例如user-center.FeignClientSpecification。
而registerFeignClient
函数则是处理接口类的主要方法了。我们在之前考虑到,我们在接口上填写了@FeignClient
注解,在之后程序中我们可以直接引用这个接口对象来调用接口上的函数,理论分析一波:接口如果没有实现类,是不能直接在spring中直接进行注入并调用相应的方法的,一定需要我们去实现这个接口,那么我们可以想到,OpenFeign
中一定做了这样的操作。
接下来我们看一下registerFeignClient
函数
2.5registerFeignClient()
函数
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();// 创建一个 BeanDefinitionBuilder 对象,用于构建并注入 FeignClientFactoryBeanBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);// 参数校验validate(attributes);// 设置BeanDefinition参数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();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });// 注入接口实例BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
3. 接口代理对象的构建
3.1FeignClientFactoryBean
类
OpenFeign
接口代理对象的构建,主要是通过 Spring 的扩展接口 FactoryBean<T>
来实现的。在上面的代码中,通过解析 FeignClient
对象,构建成一个 FeignClientFactoryBean
对象,Spring 在注入对应接口是,会调用 FeignClientFactoryBean
对象中的 getObject()
方法,返回注入对应的代理对象。
class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware {@Overridepublic Object getObject() throws Exception {// 获取目标对象return getTarget();}/*** 获取目标对象* @param <T> the target type of the Feign client* @return a {@link Feign} client created with the specified data and the context* information*/<T> T getTarget() {FeignContext context = this.applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);// 判断当前 FeignClient 注解中的url是否为空,如果不为空,直接通过url的调用if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}this.url += cleanPath();// 返回目标对象return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));}if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {this.url = "http://" + this.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 unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);// 返回目标对象return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));}
}
3.2 Targeter#target()
函数
HystrixTargeter
类实现了Targeter
。
class HystrixTargeter implements Targeter {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> target) {if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {return feign.target(target);}feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName(): factory.getContextId();SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);if (setterFactory != null) {builder.setterFactory(setterFactory);}Class<?> fallback = factory.getFallback();if (fallback != void.class) {return targetWithFallback(name, context, target, builder, fallback);}Class<?> fallbackFactory = factory.getFallbackFactory();if (fallbackFactory != void.class) {return targetWithFallbackFactory(name, context, target, builder,fallbackFactory);}// 返回目标对象return feign.target(target);}
}
3.3 Feign对象
feign.target(target)
函数
public abstract class Feign {public <T> T target(Target<T> target) {// 创建代理对象return this.build().newInstance(target);}public abstract <T> T newInstance(Target<T> target);/*** 构建 Feign对象*/public Feign build() {Client client = (Client)Capability.enrich(this.client, this.capabilities);Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {return (RequestInterceptor)Capability.enrich(ri, this.capabilities);}).collect(Collectors.toList());Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);Options options = (Options)Capability.enrich(this.options, this.capabilities);Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);// 创建代理对象的 InvocationHandler 工厂实例InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}
}
点击进入newInstance()
函数
3.4ReflectiveFeign
对象
ReflectiveFeign
继承了Feign
public class ReflectiveFeign extends Feign {
/*** 创建代理对象*/@Overridepublic <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>();// 解析模板:将方法解析,封装为MethodHandlerfor (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 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;}
}
看到这里就比较清晰了,把接口中的方法和默认实现放到Map<Method, MethodHandler>
中然后使用InvocationHandlerFactory.Default()
创建InvocationHandler
,然后使用jdk动态代理生成接口的代理并返回,这里主要看一下Feign
的InvocationHandler
实现:
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");this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);}@Overridepublic 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);}}
四、服务调用
正如我们前边所说,通过@Autowired
或者@Resource
注入的时候,注入的是被封装之后的代理类实现,
jdk动态代理持有的是ReflectiveFeign.FeignInvocationHandler
类型的InvocationHandler
,那么具体调用的时候,会调用FeignInvocationHandler#invoke()
方法
4.1 FeignInvocationHandler
对象
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");this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);}@Overridepublic 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);}}
前几个判断分支都是调用了Object的基本方法,最后dispatch.get(method).invoke(args)
才是接口的业务方法调用,dispatch的类型是Map<Method, MethodHandler>
,是接口中方法与MethodHandler
的映射关系,而MethodHandler
又被SynchronousMethodHandler.Factory
封装成SynchronousMethodHandler
(实现了MethodHandler
):
4.2 SynchronousMethodHandler
类
SynchronousMethodHandler
类实现了MethodHandler
。那么dispatch.get(method).invoke(args)
最终调用的就是SynchronousMethodHandler#invoke()
方法:
final class SynchronousMethodHandler implements MethodHandler {@Overridepublic Object invoke(Object[] argv) throws Throwable {// 构建请求的一个模版RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();while (true) {try {// 可以看到调用的时候默认是带有重试能力,默认是5次,具体调用交给executeAndDecode来实现: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;}}}
}
可以看到调用的时候默认是带有重试能力,默认是5次,具体调用交给executeAndDecode
来实现:
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 {// 通过 Client执行,进行远程调用response = client.execute(request, options);} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);}long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);boolean shouldClose = true;try {if (logLevel != Logger.Level.NONE) {response =logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);}if (Response.class == metadata.returnType()) {if (response.body() == null) {return response;}if (response.body().length() == null ||response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {shouldClose = false;return response;}// Ensure the response body is disconnectedbyte[] bodyData = Util.toByteArray(response.body().asInputStream());return response.toBuilder().body(bodyData).build();}if (response.status() >= 200 && response.status() < 300) {if (void.class == metadata.returnType()) {return null;} else {Object result = decode(response);shouldClose = closeAfterDecode;return result;}} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {Object result = decode(response);shouldClose = closeAfterDecode;return result;} else {throw errorDecoder.decode(metadata.configKey(), response);}} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);}throw errorReading(request, response, e);} finally {if (shouldClose) {ensureClosed(response.body());}}}
核心是client.execute()
,我们选择性看一下Default
默认的实现:
@Override
public Response execute(Request request, Options options) throws IOException {HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);
}
很明显,最终服务调用会委托给HttpURLConnection
来执行,然后组装结果和状态码返回调用,到这里openfeign
的服务调用就分析完了,为了帮助理解和有更直观的概念,我们看一下服务调用时序图:
图是我在网上找了一张,是OkHttpClient的,原理都是一样的,大家凑活看一下。
注意:
我们知道OpenFeign
的底层默认会交给HttpURLConnection
处理,HttpURLConnection
是不支持连接池的。所以这里我们可以配置为HttpClient
或OkHttpClient
等进行一个优化。具体配置如下:
feign:client:config:default:# 日志级别,这里是我本地开发环境设置为FULL,生产环境不建议设置为FULL,可以设置为HEADloggerLevel: FULL# 使用httpclient httpclient:enabled: truemax-connections: 200max-connections-per-route: 50# OpenFeign第一次加载慢是因为底层采用了懒加载的方式,我们这里采用了饥饿加载的方式ribbon:eager-load:enabled: trueclients: easyexcelService
五、总结
- 通过
@EnableFeignCleints
触发 Spring 应用程序对 classpath 中@FeignClient
修饰类的扫描 - 解析到
@FeignClient
修饰类后, Feign 框架通过扩展 Spring BeanDeifinition
的注册逻辑, 最终注册一个FeignClientFacotoryBean
进入 Spring 容器 - Spring 容器在初始化其他用到
@FeignClient
接口的类时, 获得的是FeignClientFacotryBean
产生的一个代理对象Proxy
. - 基于 java 原生的动态代理机制, 针对
Proxy
的调用, 都会被统一转发给 Feign 框架所定义的一个InvocationHandler
, 由该Handler
完成后续的 HTTP 转换, 发送, 接收, 翻译HTTP响应的工作
感觉这块的逻辑还是有点难度的,我debug源码三四次才搞懂OpenFeign
底层是如何创建代理对象,如何实现调用的。想要了解OpenFeign
底层原理的同学可以自己手动debug一下源码看一看具体的执行流程。
纸上得来终觉浅,绝知此事要躬行。
参考:
-
https://blog.csdn.net/weixin_41821642/article/details/129874394?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171325961116800188518011%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171325961116800188518011&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-5-129874394-null-null.142v100pc_search_result_base4&utm_term=openfeign%E5%BA%95%E5%B1%82%E8%B0%83%E7%94%A8%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4187
-
https://blog.csdn.net/bz120413/article/details/122215774?ops_request_misc=&request_id=&biz_id=102&utm_term=openfeign%E5%BA%95%E5%B1%82%E8%B0%83%E7%94%A8%E5%8E%9F%E7%90%86&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-122215774.142v100pc_search_result_base4&spm=1018.2226.3001.4187