前面的小节里我们学习了Feign的架构,其中提到了一个关键步骤叫做“动态代理”,相信大家对这个词并不陌生。今天我们就来深入了解下,Feign中的动态代理机制。

大话Feign之动态代理

“动态代理”是面试场景里的高频问题,从Spring中AOP的实现方式,到让自己手写一个动态代理实例,这个话题仿佛成了面试中很有仪式感的一个问题。面试官开口问到“请说出你对aop的理解”,感觉就像汪峰导师在问“你的梦想是什么”。
可是代理就代理好了,为什么要加个“动态”二字呢,难道还有静态代理一说?简单的说,所谓动态是相对于“静态编译”来说的。在java中,假如我们在编译期不知道这个对象是何方神圣,只能等待程序执行的时候,也就在是运行期才能知道,那么我们就称之为“动态”获取对象(比如通过类名+反射创建一个实例)。而所谓“动态代理”,就是指在运行期指定一个代理对象,以接管的方式执行后续的任务。就是这么简单。
大话Feign之动态代理 - 图1
而Feign的“动态代理”是个偷天换日的过程。我们把目标服务看做一个新娘子,当服务调用请求发出后,一伙迎亲车队浩浩荡荡地出发去迎亲。这时候,一伙打着Feign名号的抢亲小队出现了,他们利用“动态代理”的方式,截胡了迎亲车队,自个儿当起了新郎官去接新娘。我们这就来看看这伙抢亲小队是怎么工作的。

抢亲小队 - 截胡方法调用

问:截胡迎亲小队总共分几步?总共分四步:
大话Feign之动态代理 - 图2
是时候锻炼一下大家阅读开源项目源码的能力了,这部分并没有安排相应的源码讲解视频,老师只给开一个头,让大家知道Feign中动态代理的方法的起点。希望大家跟着老师的提示,顺着抢亲小队的足迹,自己去翻看源码,弄清楚Feign组件最核心的动态代理机制。如果自己反反复复没看明白的(对开源项目源码接触不多的同学,Feign的代码确实稍微难懂了一些),可以用各种方法联系老师,我们也可以考虑来一场直播,带大家手把手看Feign源码。

  1. GetObject:原配的迎亲小队出发了,一路喊着“接对象咯”(getObject),成功吸引到了抢亲小队的注意力。
    • 这一步是FeignClientFactoryBean的getObject方法发起的,为了获取一个可以发起远程调用的实体方法,只是这时它还不知道,getObject方法获取到的其实是一个代理对象。
    • 我们知道Feign实际上是调用了@FeignClient注解所修饰的接口,FeignClientFactoryBean封装了这个接口中所包含的配置信息,比如Eureka服务名称,服务调用的路径,降级逻辑的处理类,等等。
  2. 创建代理对象:一伙抢亲小队听到风声,立马开始着手准备埋伏。在上一步的getObject方法的最后做好了埋伏,开始了偷天换日的过程。
    • 上一步中getObject最后一行,经由Targeter类的转发,抢亲小队登场了。
    • 下面就是创建代理对象的时候了,Feign的所有代理实例均通过ReflectiveFeign.newInstance创建,他的底层是采用Builder模式,将@FeignClient接口的特征,方法名,参数等等一系列信息提取出来,拼装成Java反射机制中通用的Method类。
    • 偷天换日:这一步是整个动态代理机制中的核心操作。在newInstance的创建过程中,Feign通过实现JDK的InvocationHandler接口(所有动态代理方案几乎都和它有关联),将自己的Handler和上一步组装的Method进行了关联,这样一来,所有对这个接口方法的调用,都将被Feign自定义的InvocationHandler给接管。这种动态代理的方式,我们叫做JDK动态代理
    • 所有一切就绪,就等截胡方法调用了
  3. 拦截请求:这时迎亲车队经过了,因为我们在前一步已经做了埋伏,这个方法调用立马被我们自己人,也就是上一步中自定义的InvocationHandler截胡了。
    • SynchronousMethodHandler这时接管了invoke方法。构造Request请求,装模作样当起了新郎官。(在构造Request请求的同时还会涉及一系列的参数拼装和加密等步骤)
  4. 发起调用:最后一步,借助LoadBalancerFeignClient发起了真正的HTTP请求。从这个类的名字大家可以看到,似乎和负载均衡有点关系?没错,这个就是Feign和Ribbon组合而成的一个Client类,它会利用Ribbon实现超时重试等操作。

前面讲到过,Feign是武装到牙齿的组件,每一步的背后都有非常复杂的处理流程。我们学习Feign不求泛而求精,把其中每一步都研究透并不现实,但是一定要把最核心的部分吃透,比如“动态代理”。

Spring的动态代理

Spring的AOP有两种动态代理方式,其中一种就是前面讲到的Feign采用的方式:JDK动态代理。在Spring中通过JdkDynamicAopProxy实现。它有两个特点

  • 实现InvocationHandler接口,接管invoke方法实现自己的业务逻辑,所有调用都会被传递到InvocationHandler的invoke方法,通过Proxy.newProxyInstance获取动态代理对象
  • 被代理的对象必须实现了某个接口,不能代理无接口的类。

Spring还有一种动态代理的方式,那就是CGLIB,它并不强制代理类实现某个接口。在实际使用中,CGLIB在代理对象的性能方面比JDKDynamic要快很多,但是在创建代理对象上的时间花费也相当长。所以,如果你的类并没有实现接口,或者是单例模式的类不需要重复创建,建议使用CGLIB的方式。

小结

这一节带大家了解的Feign的动态代理,它是基于JDK代理的方案。下一节我带大家去看一下@EnableFeignClient注解的底层机制。
学习Tips:还记得上一节提到的学习方法吗?就是尝试去了解同一个功能在不同开源项目中的应用。动态代理就是一个很好的例子,在阅读并理解了Feign的动态代理机制后,大家可以自行去下载Spring的源码,看看Spring中动态代理的实现方式。如果感觉太复杂摸不清前后关系的话,可以自己画一些类图或流程图,方便理解。正所谓纸上得来终觉浅,绝知此事要躬行。
相信通过自己一点点摸索出来的经验,一定比直接从书本上学到的更加深刻。