3.1 代理模式与RPC客户端实现类
3.1.1 客户端RPC远程调用实现类的职责
- 拼装 REST 请求
- 发送请求和获取结果
- 结果解码

模拟一个Feign远程调用java接口:
@RestController(value = TestConstants.DEMO_CLIENT_PATH)public interface MockDemoClient{/*** 远程调用接口的方法:* 调用 demo-provider 的 REST 接口 api/demo/hello/v1* REST 接口 功能:返回 hello world** @return JSON 响应实例*/@GetMapping(name = "api/demo/hello/v1")RestOut<JSONObject> hello();/*** 远程调用接口的方法:* 调用 demo-provider 的 REST 接口 api/demo/echo/{0}/v1* REST 接口 功能: 回显输入的信息** @return echo 回显消息 JSON 响应实例*/@GetMapping(name = "api/demo/echo/{0}/v1")RestOut<JSONObject> echo(String word);}
3.1.2 简单的RPC客户端实现类
- 组装 REST 接口 URL。
- 通过 HttpClient 组件调用 REST 接口并获得响应结果
解析 REST 接口的响应结果,封装成 JSON 对象,并且返回给调用者。 ```java @AllArgsConstructor @Slf4j class RealRpcDemoClientImpl implements MockDemoClient { final String contextPath = TestConstants.DEMO_CLIENT_PATH;
//hello简单实现 public RestOut
hello() { /*** 远程调用接口的方法,完成 demo-provider 的 REST API 远程调用* REST API 功能:返回 hello world*/String uri = "api/demo/hello/v1";/*** 组装 REST 接口 URL*/String restUrl = contextPath + uri;log.info("restUrl={}", restUrl);
/*** 通过 HttpClient 组件调用 REST 接口*/String responseData = null;try{responseData = HttpRequestUtil.simpleGet(restUrl);} catch (IOException e){e.printStackTrace();}/*** 解析 REST 接口的响应结果,解析成 JSON 对象,并且返回*/RestOut<JSONObject> result = JsonUtil.jsonToPojo(responseData, new TypeReference<RestOut<JSONObject>>(){});return result;}//echo简单实现public RestOut<JSONObject> echo(String word){/*** 远程调用接口的方法, 完成 demo-provider 的 REST API 远程调用* REST API 功能: 回显输入的信息*/String uri = "api/demo/echo/{0}/v1";/*** 组装 REST 接口 URL*/String restUrl = contextPath + MessageFormat.format(uri, word);log.info("restUrl={}", restUrl);/*** 通过 HttpClient 组件调用 REST 接口*/String responseData = null;try{responseData = HttpRequestUtil.simpleGet(restUrl);} catch (IOException e){e.printStackTrace();}/*** 解析 REST 接口的响应结果,解析成 JSON 对象,并且返回*/RestOut<JSONObject> result = JsonUtil.jsonToPojo(responseData, new TypeReference<RestOut<JSONObject>>(){});return result;}
}
<a name="mGh87"></a>## 3.1.3 基础原理:代理模式与RPC客户端实现类<br />代理模式分为静态代理和动态代理1. 静态代理: 在代码编写阶段由工程师提供代理类的源码,再编译成代理类。所谓静态,就是在程序运行前就已经存在代理类的字节码文件,代理类和被委托类的关系在运行前就确定了。1. 动态代理: 在代码编写阶段不用关心具体的代理实现类,而是在运行阶段直接获取具体的代理对象,代理实现类由 JDK 负责生成。通过静态代理模式实现 MockDemoClient 接口的 RPC 调用实现类,类之间的关系如图 <br /> ```java@AllArgsConstructor@Slf4jclass DemoClientStaticProxy implements DemoClient{/*** 被代理的真正实例*/private MockDemoClient realClient;@Overridepublic RestOut<JSONObject> hello(){log.info("hello 方法被调用");return realClient.hello();}@Overridepublic RestOut<JSONObject> echo(String word){log.info("echo 方法被调用");return realClient.echo(word);}}
静态代理的 RPC 实现类看上去是一堆冗余代码,发挥不了什么作用。 为什么在这里一定要先介绍静态代理模式的 RPC 实现类呢?原因有以下两点:
- 上面的 RPC 实现类是出于演示目的而做了简化,对委托类并没有做任何扩展。 而实际的远程调用代理类会对委托类进行很多扩展,比如远程调用时的负载均衡、熔断、重试等。
上面的 RPC 实现类是动态代理实现类的学习铺垫。 Feign 的 RPC 客户端实现类是一个JDK 动态代理类,是在运行过程中动态生成的。
3.1.4 使用动态代理模式实现RPC客户端类
静态代理的缺陷
手工编写代理实现类会占用时间, 如果需要实现代理的类很多, 那么代理类一个一个地手工编码根本写不过来。
- 如果更改了抽象接口, 那么还得去维护这些代理类,维护上容易出纰漏。
动态代理是由JDK通过反射技术在执行阶段动态生成代理类,所以叫动态代理,获取动态代理实例的大致3个步骤:
- 明确代理类和被委托类共同的抽象接口,JDK生成的动态代理类会实现该接口
- 构造调用处理器对象,该对象要实现InvocationHandler接口,实现其唯一的抽象方法invoke
- 通过Proxy.newProxyInstance()方法在运行阶段获取JDK生成的动态代理类的实例,该方法需要三个参数
- 类装载器
- 抽象接口的class对象
- 调用处理器对象
例子:抽象接口MockDemoClient的一个动态代理实例:
//参数1:类装载器ClassLoader classLoader = ProxyTester.class.getClassLoader();//参数2:代理类和委托类共同的抽象接口Class[] clazz = new Class[]{MockDemoClient.class};//参数3:动态代理的调用处理器InvocationHandler invocationHandler = new DemoClientInocationHandler(realObject);/*** 使用以上三个参数,创建 JDK 动态代理类*/MockDemoClient proxy = (MockDemoClient) Proxy.newProxyInstance(classLoader, clazz, invocationHandler);

创建动态代理实例的核心是创建一个JDK调用处理器InvocationHandler的实现类,调用处理器DemoClientInvocationHandler的代码如下:
/*** 动态代理的调用处理器*/@Slf4jpublic class DemoClientInocationHandler implements InvocationHandler{/*** 被代理的委托类实例*/private MockDemoClient realClient;public DemoClientInocationHandler(MockDemoClient realClient){this.realClient = realClient;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{String name = method.getName();log.info("{} 方法被调用", method.getName());/*** 直接调用 hello 方法*/if (name.equals("hello")){return realClient.hello();}/*** 通过 Java 反射 调用 echo 方法*/if (name.equals("echo")){return method.invoke(realClient, args);}/*** 通过 Java 反射 调用其他的方法*/Object result = method.invoke(realClient, args);return result;}}
测试代码
@Testpublic void dynamicProxyTest() throws IOException {/*** 被代理的真实 PRC 调用类*/MockDemoClient realObject = new RealRpcDemoClientImpl();//参数1:类装载器ClassLoader classLoader = ProxyTester.class.getClassLoader();//参数2:代理类和委托类共同的抽象接口Class[] clazz = new Class[]{MockDemoClient.class};//参数3:动态代理的调用处理器InvocationHandler invocationHandler = new DemoClientInocationHandler(realObject);/*** 使用以上三个参数,创建 JDK 动态代理类*/MockDemoClient proxy = (MockDemoClient)Proxy.newProxyInstance(classLoader, clazz, invocationHandler);RestOut<JSONObject> result1 = proxy.hello();log.info("result1={}", result1.toString());RestOut<JSONObject> result2 = proxy.echo("回显内容");log.info("result2={}", result2.toString());/*** 将动态代理类的class字节码,保存在当前的工程目录下*/byte[] classFile = ProxyGenerator.generateProxyClass("Proxy0",RealRpcDemoClientImpl.class.getInterfaces());/*** 输出到文件*/FileOutputStream fos = new FileOutputStream(new File("Proxy0.class"));fos.write(classFile);fos.flush();fos.close();}
3.1.5 JDK动态代理机制的原理
动态代理的实质是通过Proxy.newProxyInstance()方法生成一个动态代理的实例
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{...}
三个参数的介绍:
- ClassLoader类加载器,此处的类加载器和被委托的类加载器相同即可
- Class[]类型,代表动态代理类将会实现的抽象接口,也是被委托类所实现的接口
InvocationHandler类型,它的调用处理器实例将作为JDK生成的动态代理对象的内部成员,在对动态代理对象进行方法调用时,invoke方法会被执行
3.2 模拟Feign RPC动态代理的实现
3.2.1 模拟Feign的方法处理器MethodHandler
由于每个 RPC 客户端类一般会包含多个远程调用方法, 因此 Feign 为远程调用方法封装了一个专门的接口—MethodHandler(方法处理器),此接口很简单,仅仅包含一个 invoke(…)抽象方法
/*** RPC 方法处理器*/interface RpcMethodHandler{/*** 功能:组装 url,完成 REST RPC 远程调用,并且返回 JSON结果** @param argv RPC 方法的参数* @return REST 接口的响应结果* @throws Throwable 异常*/Object invoke(Object[] argv) throws Throwable;}
模拟的 RPC 方法处理器只有一个抽象方法 invoke(Object[]),该方法在进行 RPC 调用时需要完成 URL 的组装、执行 RPC 请求并且将响应封装成 Java POJO 实例, 然后返回。 ```java @Slf4j public class MockRpcMethodHandler implements RpcMethodHandler {
/**
- REST URL 的前面部分,来自于远程调用 Feign 接口的类级别注解
如 “http://crazydemo.com:7700/demo-provider/“; */ final String contextPath;
/**
- REST URL 的前面部分,来自于远程调用 Feign 接口的方法级别的注解
如 “api/demo/hello/v1”; */ final String url;
public MockRpcMethodHandler(String contextPath, String url) { this.contextPath = contextPath; this.url = url; }
/**
- 功能:组装 url,完成 REST RPC 远程调用,并且返回 JSON结果 *
- @param argv RPC 方法的参数
- @return REST 接口的响应结果
- @throws Throwable 异常
/
@Override
public Object invoke(Object[] argv) throws Throwable
{
/*
- 组装 REST 接口 URL */ String restUrl = contextPath + MessageFormat.format(url, argv); log.info(“restUrl={}”, restUrl);
/*** 通过 HttpClient 组件调用 REST 接口*/String responseData = HttpRequestUtil.simpleGet(restUrl);/*** 解析 REST 接口的响应结果,解析成 JSON 对象,并且返回*/RestOut<JSONObject> result = JsonUtil.jsonToPojo(responseData, new TypeReference<RestOut<JSONObject>>(){});return result;}
}
<a name="OkFmZ"></a>## 3.2.2 模拟Feign的调用处理器InvocationHandler调用处理器 FeignInvocationHandler 是一个相对简单的类,拥有一个非常重要的 Map 类型的成员 dispatch,保存着 RPC 方法反射实例到其 MethodHandler 方法处理器的映射。 <br /> ```java@Slf4jclass MockInvocationHandler implements InvocationHandler{/*** 远程调用的分发映射:根据方法名称,分发方法处理器* key: 远程调用 Feign 接口的方法名称* value: 方法处理器 handler*/private Map<Method, RpcMethodHandler> dispatch;/*** 代理对象的创建** @param clazz 被代理的接口类型* @return 代理对象*/public static <T> T newInstance(Class<T> clazz){/*** 从远程调用接口的类级别注解中,获取 REST 地址的 contextPath 部分*/Annotation controllerAnno = clazz.getAnnotation(RestController.class);if (controllerAnno == null){return null;}String contextPath = ((RestController) controllerAnno).value();MockInvocationHandler invokeHandler = new MockInvocationHandler();invokeHandler.dispatch = new LinkedHashMap<>();/*** 通过反射,迭代远程调用接口中的每一个方法,组装对应的 MockRpcMethodHandler 模拟方法处理器*/for (Method method : clazz.getMethods()){Annotation methodAnnotation = method.getAnnotation(GetMapping.class);if (methodAnnotation == null){continue;}/*** 从远程调用接口的方法级别注解中,获取 REST 地址的 uri 部分*/String uri = ((GetMapping) methodAnnotation).name();/*** 组装 MockRpcMethodHandler 模拟方法处理器* 注入 REST 地址的 contextPath 部分和uri部分*/MockRpcMethodHandler handler = new MockRpcMethodHandler(contextPath, uri);/***将模拟方法处理器handler 实例缓存到 dispatch 映射中*/invokeHandler.dispatch.put(method, handler);}T proxy = (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz},invokeHandler);return proxy;}/*** 动态代理的方法调用** @param proxy 动态代理实例* @param method 待调用的方法* @param args 方法实参* @return 返回值* @throws Throwable 抛出的异常*/@Overridepublic Object invoke(Object proxy,Method method, Object[] args) throws Throwable{if ("equals".equals(method.getName())){Object other = args.length > 0 && args[0] != null ? args[0] : null;return equals(other);} else if ("hashCode".equals(method.getName())){return hashCode();} else if ("toString".equals(method.getName())){return toString();}log.info("远程方法 {} 被调用", method.getName());/*** 从 dispatch 映射中,根据方法名称,获取方法处理器*/RpcMethodHandler rpcMethodHandler = dispatch.get(method);/*** 方法处理器组装 url,完成 REST RPC 远程调用,并且返回 JSON结果*/return rpcMethodHandler.invoke(args);}}
3.2.3 模拟Feign的动态代理RPC的执行流程
3.2.4 模拟动态代理RPC远程调用的测试
@Slf4jpublic class FeignProxyMockTester{/*** 测试用例*/@Testpublic void test() throws IOException{/*** 创建远程调用接口的本地JDK Proxy代理实例*/MockDemoClient proxy =MockInvocationHandler.newInstance(MockDemoClient.class);/*** 通过模拟接口,完成远程调用*/RestOut<JSONObject> responseData = proxy.hello();log.info(responseData.toString());/*** 通过模拟接口,完成远程调用*/RestOut<JSONObject> echo = proxy.echo("proxyTest");log.info(echo.toString());}}
3.2.5 Feign弹性RPC客户端实现类
Feign 通过调用处理器、方法处理器完成了对 RPC 被委托类的增强,其调用处理器 InvocationHandler 通过对第三方组件如 Ribbon、Hystrix 的使用, 使 Feign 动态代理 RPC 客户端类具备了客户端负载均衡、失败回退、 熔断器、舱壁隔离等一系列的 RPC 保护能力。

- 失败回退: 当 RPC 远程调用失败时将执行回退代码,尝试通过其他方式来规避处理,而不是产生一个异常。
- 熔断器熔断: 当 RPC 远程服务被调用时, 熔断器将监视这个调用。如果调用的时间太长, 那么熔断器将介入并中断调用。如果 RPC 调用失败的次数达到某个阈值, 那么将会采取快速失败策略终止持续的调用失败
- 舱壁隔离: 如果所有 RPC 调用都使用同一个线程池,那么很有可能一个缓慢的远程服务将拖垮整个应用程序。弹性客户端应该能够隔离每个远程资源,并分配各自的舱壁线程池,使之相互隔离, 互不影响。
客户端负载均衡: RPC 客户端可以在服务提供者的多个实例之间实现多种方式的负载均衡,比如轮询、随机、权重等。
3.3 Feign弹性RPC客户端的重要组件
在微服务启动时, Feign 会进行包扫描,对加@FeignClient 注解的 RPC 接口创建远程接口的本地 JDK 动态代理实例。之后这些本地 Proxy 动态代理实例会注入 Spring IOC 容器中。当远程接口的方法被调用时,由 Proxy 动态代理实例负责完成真正的远程访问并返回结果。
3.3.2 Feign的动态代理RPC客户端实例
```java @FeignClient(
value = "demo-provider", path = "/demo-provider/api/demo/",fallback = DemoDefaultFallback.class
) public interface DemoClient { /**
- 远程调用接口的方法:
- 调用 demo-provider 的 REST 接口 api/demo/hello/v1
- REST 接口 功能:返回 hello world
@return JSON 响应实例 */ @GetMapping(“/hello/v1”) RestOut
hello(); /**
- 远程调用接口的方法:
- 调用 demo-provider 的 REST 接口 api/demo/echo/{0}/v1
- REST 接口 功能: 回显输入的信息
- @return echo 回显消息 JSON 响应实例
*/
@RequestMapping(value = “/echo/{word}/v1”,
RestOutmethod = RequestMethod.GET)
echo( @PathVariable(value = "word") String word);
3.3.3 Feign的调用处理器InvocationHandler
Feign 提供了一个默认的调用处理器, 名为 FeignInvocationHandler 类,该类完成基本的调用处理逻辑,处于 feign-core 核心 JAR 包中。
Feign 的调用处理器可以进行替换,如果 Feign是与 Hystrix 结合使用的, 就会被替换成 HystrixInvocationHandler 调用处理器类,而该类处于 feignhystrix 的 JAR 包中。
3.3.4 Feign的方法处理器MethodHandler

3.3.5 Feign的客户端组件
不同的 feign.Client 客户端实现类其内部提交 HTTP 请求的技术是不同的。常用的 Feign 客户端实现类如下:
- Client.Default 类:默认的实现类,使用 JDK 的 HttpURLConnnection 类提交 HTTP 请求。
- ApacheHttpClient 类:该客户端类在内部使用 Apache HttpClient 开源组件提交 HTTP 请求
- OkHttpClient 类:该客户端类在内部使用 OkHttp3 开源组件提交 HTTP 请求。
- LoadBalancerFeignClient 类:内部使用 Ribbon 负载均衡技术完成 HTTP 请求处理。

