Feign 的实现原理
Feign 实现原理
Feign是申明式的 HTTP 客户端。代码中创建一个接口并加上@FeingClient 注解即可使用。其底层封装了 HTTP 客户端构建并发送的复杂逻辑。同时也可以整合注册中心及 Ribbon 为其提供负载均衡能力;通过整合 Histrix/sentinal 实现熔断限流功能。本期主要分享下 Feign 与 SpringCloud 的整合过程,及其底层 HTTP 调用的实现细节。
https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_openfeign
使用
pom.xml 中添加
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
启动类添加
@EnableFeignClients
注解@SpringBootApplication(scanBasePackages = {"com.xxx"}) // 指定 FeiginClient 扫描路径 @EnableFeignClients(basePackages = {"com.xxx.biz.feign"}) public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} }
申明 FeignClient
@FeignClient(name = "inverter-data-service", url = "${feign.property.inverter-data-service.url}", configuration = {xxx}) public interface InverterDataFeign {@PostMapping(value = "/api/inverter/get_first_date")FirstDataResponse firstData(@RequestBody String sn, @RequestHeader("token") String token); }
服务调用
@Service public class DemoService {@Autowiredprivate InverterDataFeign inverterDataFeign;public void remoteCall(String sn, String token) {// ...FirstDataResponse firstDataResponse = inverterDataFeign.firstData(sn, token);// ...} }
思考
@FeignClient
注解是如何使普通的 Java 接口具有 HttpClient 能力的?@FeignClient
并非@Component
注解,为什么可以像其他Spring 组件一样注入到其他组件中(如DemoService
)?
实现流程图
实现细节
扫描 & 注册
启动类 Application 申明了注解 @EnableFeignClients
,而@EnableFeignClients
又申明了@Import(FeignClientsRegistrar.class)
, FeignClientsRegistrar
实现了 ImportBeanDefinitionRegistrar
public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}/*** 子类实现*/default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}
当 Spring IOC 容器启动时,会去扫描 @ComponentScan 指定包下所有 @Import 的注解,如果 @Import 的 value 参数是 ImportBeanDefinitionRegistrar 的实现类,就会调用 ImportBeanDefinitionRegistrar#registerBeanDefinitions() 方法,而 FeignClientsRegistrar 就是 ImportBeanDefinitionRegistrar 的实现类;故容器初始化过程中 FeignClientsRegistrar#registerBeanDefinitions() 会被执行,这个方法会做两件事情,1. 注册 @EnableFeignClients 中 configuration 参数指定的默认配置类;2. 扫描并注册子包下被 @FeignClient 注解的 feign 客户端
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 注册默认配置类registerDefaultConfiguration(metadata, registry);// 扫描注册 feign 客户端至 spring 容器registerFeignClients(metadata, registry);}
}
下面主要看registerFeignClients(metadata, registry)
的实现
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();// 获取 @EnableFeignClients 中所有的参数Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// 拿到 clients 参数final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {/* 如果未设置 clients 参数,则去子包下扫描所有注解类型为 @FeignClient 的类 */// 获取扫描器ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);// 设置扫描器的过滤条件,只扫描 @FeignClient 类型scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));// 拿到 @FeignClient 的 basePackages 参数Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {// 执行扫描,构建并添加 BeanDefinition 实体candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}}else {// 如果 clients 参数不为空,则只去注册指定的 FeignClientfor (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));}}/* 遍历 BeanDefinitions(申明 @FeignClient 的类定义)1. 注册 @FeignClient 中 configuration 参数指定的配置类2. 注册具体的 FeignClient */for (BeanDefinition candidateComponent : candidateComponents) {// 查看组件是否是包含注解的 BeanDefinitionif (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;// 拿到类的注解元数据(包含 class 信息)AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();// 校验,@FeignClient 只能申明在接口上Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");// 拿到 @FeignClient 的属性数据Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());// 获取客户端名称,依次查看 contextId、value、name、serviceId,只要有值就返回String name = getClientName(attributes);// 拿到 configuration 参数,注册配置类registerClientConfiguration(registry, name, attributes.get("configuration"));// 注册 FeignClientregisterFeignClient(registry, annotationMetadata, attributes);}}}
接着看registerFeignClient(registry, annotationMetadata, attributes)
的逻辑:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 拿到接口类名,构建 class 对象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);// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法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);// 设置 BD 的一些属性,是否 primary,别名等...AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);// has a default, won't be nullboolean 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);// 注册 FeignClient 对应的 BeanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);// 使用 RefreshScope 创建 Request.Options(请求超时时间等配置) BeanDefinitionregisterOptionsBeanDefinition(registry, contextId);}
至此,Spring 容器启动过程中,扫描 FeignClient 并注册到容器的流程已经结束
初始化代理类
注册到 Spring 容器中的 BeanDefinition 会存入一个 Map 中(BeanDefinitionMap);所有 BeanDefinition 注册完毕后,Spring 会对其进行初始化,即实例化并存入 Spring 容器;注册 FeignClient 时,FeignClient 被包装成 FeignClientFactoryBean(FactoryBean 实现类)存入 BeanDefinitionMap;故初始化 FeignClientFactoryBean 时,会回调其 getObject() 方法
再来看刚刚注册 FeignClient 的逻辑:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 省略前面逻辑...// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法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();});// 省略后面逻辑...}
创建 FeignClient 对象的逻辑均在 FeignClientFactoryBean#getObject() 中:
@Overridepublic Object getObject() {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(FeinContext 是在 FeignAutoConfiguration 中通过 @Bean 注入的)// 属于 ApplicationContext 的子容器FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);// 从容器中获取 feign builder 对象(该对象也是在 FeignAutoConfiguration 中通过 @Bean 注入的)Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) {if (url != null && LOG.isWarnEnabled()) {LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");}else if (LOG.isDebugEnabled()) {LOG.debug("URL not provided. Will use LoadBalancer.");}if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();// 负载均衡,需结合 ribbon 使用return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, 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 FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 容器中获取 Targeter 实例(DefaultTargeter)Targeter targeter = get(context, Targeter.class);// 获取代理对象return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));}
DefaultTargeter#target 会去实例化 ReflectiveFeign, 并调用其 newInstance() 方法实例化代理对象,ReflectiveFeign#newInstance() 逻辑:
public <T> T newInstance(Target<T> target) {// feign 中调用的方法名 -> SynchronousMethodHandler映射;(SynchronousMethodHandler 是 InvocationHandler 的封装,包含 Feign 请求的配置信息) 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()) {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 对象InvocationHandler handler = factory.create(target, methodToHandler);// JDK 动态代理,创建代理对象,T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}
至此,代理对象创建结束.
发送请求
当 FeignClient 发起 http 请求时,会从容器中获取对应的代理类,并调用 FeignInvocationHandler#invoke() 方法,其最终实现在 SynchronousMethodHandler#invoke() 中:
public Object invoke(Object[] argv) throws Throwable {// 解析请求参数,封装 RequestTemplateRequestTemplate 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;}}}
执行请求的逻辑主要在 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 {// 执行请求,拿到响应response = client.execute(request, options);response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {// ...}// ...
}
如果未设置 http 客户端,则使用默认的客户端 Client.Default,即构建 java.net.HttpURLConnection 并发送请求:
public Response execute(Request request, Options options) throws IOException {// 构建 java.net.HttpURLConnection 并发送请求HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);}
问题
实例化 FeignClient 时,Feign 为什么要使用 FeignContext 来给每个 FeignClient 创建一个子IOC容器,如果直接使用父容器会有什么问题?
Feign 的实现原理相关推荐
- Feign的调用原理及其源码分析
Feign的调用原理及其源码分析 目录 概述 架构特性 设计思路 实现思路分析 Feign是如何进行服务调用的 拓展实现 相关工具如下: 实验效果:(解决思路) 分析: 小结: 参考资料和推荐阅读 L ...
- SpringCloud——Feign实例及原理
一.实例 1.配置feign 添加依赖 在maven的pom中添加feign <dependency><groupId>org.springframework.cloud< ...
- feign扫描_feign原理+源码解析
1 架构图. 2 feign的初始化扫包原理. (1)feign使用,是main上的@EnableFeignClients 和 api的@FeignClient(value = "servi ...
- springCloud Feign的实现原理
一.Feign介绍 Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求. Spring Cloud Feign是基于Netflix feign实现,整合了Spr ...
- Feign的使用及原理剖析
feign使用及原理剖析 一.简介 Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求.Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参 ...
- Feign底层原理分析-自动装载动态代理
本篇文章仅介绍Feign的核心机制,包括如何交由Spring容器托管.动态代理机制等内容,不会过分深入细节. 1.什么是Feign? 这里套用Feign官方Github上的介绍:"Feign ...
- Hystrix、Feign技术底层实现原理
一. Feign的设计原理 1.1 Feign是什么 Feign 的英文表意为"假装,伪装,变形", 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请 ...
- Feign原理及其使用
场景 当使用多服务时,经常会遇到服务之间的相互调用. 一个服务如果要调用另一个服务的接口,需要: ① 定义一个请求,并设置目标地址. ② 为这个请求设置参数. ③ 为这个请求设置请求头等属性. ④ 发 ...
- 关于Feign的几个问题
本文来说下关于Feign 的几个问题 文章目录 概述 什么是 Feign 什么是 Open Feign Feign 和 Openfeign 的区别 Starter Openfeign 环境准备 生产者 ...
最新文章
- Keil MDK 中利用串口及c标准库函数printf为cortex-m3做调试输出(lpc1788)
- 自学计算机科学CS总结-by 要有光LTBL
- apache+mod_wsgi+django的环境配置
- Google和IMAX放弃VR相机
- Hibernate查询之Criteria查询
- Python学习16 正则表达式3 练习题
- Transposed Convolution 反卷积
- 架构,改善程序复用性的设计~第五讲 复用离不开反射和IOC
- 华为厉害了:已启动6G网络技术研究
- 零基础、非计算机相关专业的如何转型程序员
- mysql中如何迁移数据文件,迁移mysql数据文件存放位置
- Java基础编程题(API阶段测试)
- ubuntu 20.10 安装万能五笔(ibus模式)
- 创业19年的湖南竞网如何拥抱数字化转型,按下成长加速键?
- 我们雇佣了一只大猴子
- Global Round 16D2. Seating Arrangements (hard version)(模拟,贪心)
- Va02 修改数量和价格条件时报错
- 【译】eBPF 概述:第 4 部分:在嵌入式系统运行
- DDSM多区域标注之处理overlay文件框出病灶区域
- 大数据华而不实么?大数据的本质是什么?