您的位置 首页 > 数码极客

〔feign原理〕feign原理 -csdn


本篇文章仅介绍Feign的自动装载与动态代理机制等内容,不会过分深入细节。

1、什么是Feign?

这里套用Feign官方Github上的介绍:“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户端,Feign的主要目标是降低大家使用Http API的复杂性”。

其实,Feign底层依赖于Java的动态代理机制,对原生Java Socket或者Apache HttpClient进行封装,实现了基于Http协议的远程过程调用。当然,Feign还在此基础上实现了负载均衡、熔断等机制。

2、为什么要使用Feign?

  • 声明式Http Client相对于编程式Http Client代码逻辑更加简洁,不需要处理复杂的编码请求和响应,只需要像调用本地方法即可,提高编码效率
  • 集中管理Http请求方法,代码边界更加清晰
  • 更好的集成负载均衡、熔断降级等功能
  • ...

3、Feign依赖注入原理

使用过Feign的同学都知道,@EnableFeignClients注解是开启Fiegn功能的关键,我们通常会在该注解中添加FeignClient的所在包,以便Spring容器能够扫描到所有的FeignClient,并进行托管。后面我们便可以使用@Autowired注解自动导入了。

@SpringBootApplication @EnableFeignClients(basePackages = {"com.**.feign"}) public class Application {} 复制代码

该注解样式也是很多第三方包集成Springboot所使用的套路:一般都是开启该注解后,Springboot便可以自动装载第三方包所指定的Class,我们便可以直接使用第三方包所提供的功能,非常方便。

接下来不会详细介绍自动装载的部分,而是直接给出自动装载的主脉络,看看Spring容器到底装载了什么bean。

3.1、Feign自动装载

首先进入@EnableFeignClients源码中,查看该注解导入了什么Registrar注册器,这个注册器便是自动装载的关键。

@Retention) @Targe) @Documented @Impor) 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); } }
  • [1] 从类的定义中,我们可以发现还实现了ResourceLoaderAware、EnvironmentAware两个Spring钩子接口,那么该注册类必然持有资源加载器和Spring的环境变量等信息,这个不过多叙述。
  • [2] 该方法会从@EnableFeignClients注解中提取defaultConfiguration这个key和对应的value,并把它当作默认的Feign配置注册到Spring容器中。如果没有该key,则不做任何处理。
  • [3] registerFeignClients方法会扫描@EnableFeignClients注解的basePackages,注册所有的FeignClient,下面详细介绍。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>(); Map<String, Object> attrs = me()); // [1] 获取clients属性 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) a("clients"); if (clients == null || clien == 0) { // [2] 如果clients属性为null,则获取basePackages属性,扫描其中的所有client ClassPathScanningCandidateComponentProvider scanner = getScanner(); ); (new AnnotationTypeFilter)); Set<String> basePackages = getBasePackages(metadata); for (String basePackage : basePackages) { candida(basePackage)); } } else { // [3] 如果clients属性不为null,则直接注入 for (Class<?> clazz : clients) { candida(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 = beanDe(); A(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata .getAnnotationACanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, a("configuration")); registerFeignClient(registry, annotationMetadata, attributes); } } }

registerFeignClients方法的代码稍微有点多,但主要思路就是解析@EnableFeignClients注解,根据解析信息获取FeignClients,然后包装成BeanDefinition注册进Spring容器中。具体分为以下几步:

  • [1] 首先解析@EnableFeignClients注解中的clients信息,如果存在,说明开发人员直接指定了FeignClient的全路径,因此只要加载这些全路径的class即可。如果未指定,则通过扫包的方式加载。
  • [2] 如果clients属性为null,则创建一个扫描器Scanner,并指定要扫描的类必须有FeignClient注解,然后通过getBasePackages()方法从@EnableFeignClients注解中获取basePackages信息,最后遍历所有待扫描的包,将扫描到的FeignClient类加入candidateComponents中,待后续加载进容器。
  • [3] 如果clients属性不为null,上面也说了,会直接注入
  • [4] 到了这里,所有的FeignClient都被扫描到并且封装成BeanDefinition,接下来会遍历这些BeanDefinition,然后在A()方法中判断这些BeanDefinition是否为接口,因为@FeignClient注解只能使用在接口上。校验完后我们应该关注registerFeignClient()这个真正注册FeignClient的方法。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = anno(); Class clazz = Cla(className, null); ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) registry : null; String contextId = getContextId(beanFactory, attributes); String name = getName(attributes); FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); (beanFactory); (name); (contextId); (clazz); BeanDefinitionBuilder definition = BeanDe(clazz, () -> { (getUrl(beanFactory, attributes)); (getPath(beanFactory, attributes)); (a("decode404")))); Object fallback = a("fallback"); if (fallback != null) { (fallback instanceof Class ? (Class<?>) fallback : Cla(), null)); } Object fallbackFactory = a("fallbackFactory"); if (fallbackFactory != null) { Factory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory : Cla(), null)); } return (); }); de); de(true); validate(attributes); AbstractBeanDefinition beanDefinition = de(); beanDe, className); beanDe("feignClientsRegistrarFactoryBean", factoryBean); // has a default, won't be null boolean primary = (Boolean) a("primary"); beanDe(primary); String[] qualifiers = getQualifiers(attributes); if (qualifiers)) { qualifiers = new String[] { contextId + "FeignClient" }; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); BeanDe(holder, registry); }

registerFeignClient()是对单个FeignClient注册的方法。这个方法乍一看很长,但其实主线非常清晰。

就是将扫描到的BeanDefinition中的元信息提取出来,然后构造成一个Feign自定义的FactoryBean,即FeignClientFactoryBean,后面我们每次获取容器中的FeignClient时,就会通过该FactoryBean的getObject()方法中获取(这个涉及到了Spring容器中普通bean和FactoryBean的区别,大家可以自行去了解下)。

方法中的其它部分都是为上面所说的逻辑服务,包括FactoryBean的构造,BeanDefinition注入到容器中等过程。我们不必太过关心,只要抓住重点即可。

讲到这里,其实自动装载过程已经完成了,容器中已经包含了自定义的FeignClientFactoryBean。这里用一张流程图总结下自动装载的全过程。

但是我们暂时还不知道自动装载的FeignClientFactoryBean到底做了什么。下面我们就深入去了解。

3.2、FeignClientFactoryBean#getObject

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 ? beanFac) : a); Feign.Builder builder = feign(context); if (!S(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 (S(url) && !url.startsWith("http")) { url = "http://" + url; } // [3] 如果url定义了,说明用户指定了某台机器,也就没有必要进行负载均衡了,则从下面的方法创建对象 String 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, Targe); return (T) (this, builder, context, new HardCodedTarget<>(type, name, url)); } } // 创建负载均衡的client protected <T> T loadBalance builder, FeignContext context, HardCodedTarget<T> target) { Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); Targeter targeter = get(context, Targe); return (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属性决定是否返回具有负载均衡属性的对象。具体过程如下:

  • [1] 重写的方法委托给getTarget()执行了
  • [2] 如果没有定义url,则直接通过loadBalance()方法创建代理对象。此时会先从容器中拿到Client对象,然后使用()方法创建Client的代理对象。注意,我们最终使用的FeignClient是Client的动态代理对象,而Client对象是真正执行http请求的对象。Client一般是httpClient或者是Feign自定义的具有LoadBalance功能的LoadBalancerClient。前者很好理解,可以直接认为是Apache HttpClient;后者是Feign在Apache HttpClient的基础上封装了Spring Cloud Loadbalancer一系列对象,而Apache HttpClient作为被封装的delegate,在delegate真正执行http请求时同时进行Loadbalancer的负载均衡逻辑。
  • [3] 如果url已经定义了,说明用户指定了具体某台机器,此时已经没有必要进行负载均衡了(当然,如果配置的是域名,可能会由下游ng或者网关层进行负载均衡,这里说的是Feign没有必要负载均衡)。为了移除负载均衡的功能,这里比loadBalance()方法多了client的判断,如果client是FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient,会直接代理这两个对象中的delegate对象,即直接代理Apache HttpClient,这样就能移除其中的负载均衡功能了。

从上面的源码中可知,无论是否需要负载均衡,都会通过()方法创建动态代理对象。我们这里跳过中间不重要的环节,给出()不太重要的调用栈,大家可以自行查看:Targe()→DefaultTarge()→Feign.Builder.target()→Feign.newInstance()→Re()。

接下来我们来到了Re()这个重要的方法:

@Override public <T> T newInstance(Target<T> target) { // [1] nameToHandler 里面基本上是SynchronousMethodHandler,主要用于处理用户自定义的方法 Map<String, MethodHandler> nameToHandler = (target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); // [2] DefaultMethodHandler用于处理接口中default方法 List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); // 遍历接口中的所有方法 for (Method method : ().getMethods()) { if () == Object.class) { // [3] 如果是Object中的方法,直接跳过 continue; } else if (method)) { // [4] 如果是default方法,则创建DefaultMethodHandler处理 DefaultMethodHandler handler = new DefaultMethodHandler(method); de(handler); me(method, handler); } else { // [5] 其它的都是用户自定义的方法了,此时从nameToHandler中拿出SynchronousMethodHandler进行映射 me(method, nameToHandler.ge((), method))); } } // [6] 这个就是动态代理的关键了,代理逻辑都在该handler中了 InvocationHandler handler = (target, methodToHandler); // 创建代理对象 T proxy = (T) Proxy.newProxyInstance(().getClassLoader(), new Class<?>[] {()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { de(proxy); } // 返回代理对象 return proxy; }

从newInstance中我们看到了熟悉的proxy和InvocationHandler,这也就说明了Feign的底层还是依赖了JDK的动态代理:

  • [1] 这里会通过()方法创建configKey→SynchronousMethodHandler的映射
  • [2] defaultMethodHandlers用于存储处理用户定义的FeignClient接口中的default方法的handler
  • [3] 如果是Object方法,这里直接跳过了,因为会在后面的InvocationHandler中处理Object方法的代理逻辑
  • [4] 如果是接口中的default方法,则创建DefaultMethodHandler并添加进defaultMethodHandlers列表和methodToHandler 映射中
  • [5] 创建method→SynchronousMethodHandler的映射
  • [6] 创建InvocationHandler 核心代理对象,代理逻辑都封装在该对象中。注意,这里传递了methodToHandler(method→MethodHandler)这个映射,MethodHandler可能是DefaultMethodHandler(处理default方法)或者SynchronousMethodHandler(处理用户定义的远程调用方法)。代理过程中,会根据方法名称dispatch到这个映射中对应的MethodHandler进行处理。

后面就是创建代理对象并返回了。

下面我们来看核心代理逻辑究竟做了什么,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) { = checkNotNull(target, "target"); // dispatch就是我们上文提到的methodToHandler = checkNotNull(dispatch, "dispatch for %s", target); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 这下面都是判断是否为Object中的方法 if ("equals".equal())) { 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".equal())) { return hashCode(); } else if ("toString".equal())) { return toString(); } // 如果是用户自定义的远程调用方法,则执行MethodHandler中的invoke方法 return di(method).invoke(args); } @Override public boolean equals(Object obj) { if (obj instanceof FeignInvocationHandler) { FeignInvocationHandler other = (FeignInvocationHandler) obj; return ); } return false; } @Override public int hashCode() { return (); } @Override public String toString() { return (); } }

FeignInvocationHandler 还是非常简单的,dispatch就是我们上文提到的methodToHandler(method→MethodHandler)映射,在执行invoke方法时:

  • 如果是Object方法,则调用重写的方法处理
  • 如果是default方法,则从dispatch映射中获取对应的De()处理
  • 如果是用户定义的远程调用方法,则从dispatch映射中获取对应的Sync()处理

这里我们仅关心远程调用的实现机制,因此下面我们将进入到SynchronousMethodHandler中,观察invoke()方法的执行逻辑:

@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTem(argv); Options options = findOptions(argv); Retryer retryer = (); while (true) { try { // 具体执行远程调用的方法 return executeAndDecode(template, options); } catch (RetryableException e) { try { // 判断是否需要重试 re(e); } catch (RetryableException th) { Throwable cause = (); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(), 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(), logLevel, request); } Response response; long start = Sy(); try { // 执行http请求 response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 12 response = re() .request(request) .requestTemplate(template) .build(); } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(), 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进行多层封装,以实现远程调用的能力。这里也用一张图梳理下整个过程:

4、总结

本文主要介绍了Feign的自动装载和动态代理机制,并梳理了这两个机制的主要脉络,而忽略其它次要信息。Feign能够被众多开发人员所使用绝不仅仅是具备以上介绍的两个功能,它还提供了诸如负载均衡、熔断等机制,这些也在文章中有少量提及,大家感兴趣的话,可以沿着本文所介绍的主脉络,一一梳理下这些功能。

责任编辑: 鲁达

1.内容基于多重复合算法人工智能语言模型创作,旨在以深度学习研究为目的传播信息知识,内容观点与本网站无关,反馈举报请
2.仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证;
3.本站属于非营利性站点无毒无广告,请读者放心使用!

“feign原理,feign原理,csdn,openfeign原理,feign原理面试”边界阅读