设计思路

image.png
模拟一个类似 Feign 客户端

编写 IUser 接口

  1. @Component
  2. @ApiServer("http://localhost:8080/user")
  3. public interface IUserApi {
  4. @GetMapping("/all")
  5. Flux<User> getAll();
  6. @GetMapping("/get/{id}")
  7. Mono<User> getById(@PathVariable("id") String id);
  8. @DeleteMapping("/delete/{id}")
  9. Mono<Void> deleteById(@PathVariable("id") String id);
  10. @PostMapping("/create")
  11. Mono<User> create(@RequestBody Mono<User> user);
  12. }

这里我们使用了一个自定义注解 @ApiServer 用来保存 URL 前缀

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ApiServer {
  4. String value() default "";
  5. }

对应的,还使用了一个 Domain User,用来接收和输入

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @Builder
  5. public class User {
  6. private String id;
  7. private String name;
  8. private int age;
  9. }

编写 Proxy 接口

调用接口 IUserApi 我们编写完成后,我们就需要对 IUserApi 进行代理,获取方法的参数,返回值等,并进行调用,由于代理可能会使用 JDK 动态代理,也可能使用 CGLIB 代理,所以我们先编写 Proxy 接口,接口很简单,只有一个方法,就是创建代理对象

  1. public interface ProxyCreator {
  2. Object create(Class<?> clazz);
  3. }

需要使用到的两个 POJO

ServerInfo

ServerInfo,用来记录服务器信息,目前只记录服务器前缀,配合 @ApiServer 使用

  1. @Data
  2. @Builder
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. public class MethodInfo {
  6. /** 请求 URL */
  7. private String url;
  8. /** 请求方法 */
  9. private HttpMethod method;
  10. /** 请求参数 */
  11. private Map<String, Object> params;
  12. /** 请求体 */
  13. private Mono<?> body;
  14. /** 请求体的类型 */
  15. private Class<?> bodyElementType;
  16. /** 判断是 Flux 还是 Mono */
  17. private boolean returnFlux;
  18. /** 返回对象的类型 */
  19. private Class<?> returnElementType;
  20. }

MethodInfo

MethodInfo,用来描述方法信息,正对 IUserApi 接口中的方法

  1. @Data
  2. @Builder
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. public class MethodInfo {
  6. /** 请求 URL */
  7. private String url;
  8. /** 请求方法 */
  9. private HttpMethod method;
  10. /** 请求参数 */
  11. private Map<String, Object> params;
  12. /** 请求体 */
  13. private Mono<?> body;
  14. /** 请求体的类型 */
  15. private Class<?> bodyElementType;
  16. /** 判断是 Flux 还是 Mono */
  17. private boolean returnFlux;
  18. /** 返回对象的类型 */
  19. private Class<?> returnElementType;
  20. }

编写 RestHandler 请求接口

Rest 请求可以由 WebClient 来实现,也可以有 Spring 的 RestTemplate 来实现,所以为了更好的扩展,我们也抽象成接口的形式

  1. public interface RestHandler {
  2. /**
  3. * 初始化服务器信息
  4. *
  5. * @param serverInfo 服务器信息
  6. */
  7. void init(ServerInfo serverInfo);
  8. /**
  9. * 调用 REST 请求
  10. *
  11. * @param methodInfo 方法信息
  12. * @return Object
  13. */
  14. Object invoke(MethodInfo methodInfo);
  15. }

编写 RestHandler 实现类(WebClientRestHandler)

这里我们使用 WebClient 来实现 RestHandler 接口

  1. public class WebClientRestHandler implements RestHandler {
  2. private WebClient webClient;
  3. @Override
  4. public void init(ServerInfo serverInfo) {
  5. webClient = WebClient.create(serverInfo.getUrl());
  6. }
  7. /**
  8. * 处理 Rest 请求
  9. *
  10. * @param methodInfo 方法信息
  11. */
  12. @Override
  13. public Object invoke(MethodInfo methodInfo) {
  14. Object result;
  15. WebClient.RequestBodySpec bodySpec = this.webClient
  16. // 请求方法
  17. .method(methodInfo.getMethod())
  18. // 请求 URI
  19. .uri(methodInfo.getUrl(), methodInfo.getParams())
  20. // 响应格式
  21. .accept(MediaType.APPLICATION_JSON);
  22. // 判断是否含有 body
  23. if (methodInfo.getBody() != null) {
  24. bodySpec.body(methodInfo.getBody(), methodInfo.getBodyElementType());
  25. }
  26. // 发送请求
  27. WebClient.ResponseSpec request = bodySpec.retrieve();
  28. // 异常处理
  29. request.onStatus(httpStatus -> httpStatus.value() == 404, response -> Mono.just(new RuntimeException("Not Found")));
  30. // 处理响应
  31. if (methodInfo.isReturnFlux()) {
  32. result = request.bodyToFlux(methodInfo.getReturnElementType());
  33. } else {
  34. result = request.bodyToMono(methodInfo.getReturnElementType());
  35. }
  36. return result;
  37. }
  38. }

编写 Proxy 实现类(JDKProxyCreator)

这里我们使用 JDK 动态代理来实现

  1. @Slf4j
  2. public class JDKProxyCreator implements ProxyCreator {
  3. @Override
  4. public Object create(Class<?> clazz) {
  5. log.info("createProxy: {}", clazz);
  6. // 得到接口的 API 服务器地址
  7. ServerInfo serverInfo = extractServerInfo(clazz);
  8. log.info("serverInfo: {}", serverInfo);
  9. // 给每一个代理类一个实现
  10. RestHandler restHandler = new WebClientRestHandler();
  11. // 初始化服务器信息和 WebClient
  12. restHandler.init(serverInfo);
  13. return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{clazz}, (proxy, method, args) -> {
  14. // 根据方法和参数得到调用信息
  15. MethodInfo methodInfo = extractMethodInfo(method, args);
  16. log.info("methodInfo: {}", methodInfo);
  17. // 调用 REST
  18. return restHandler.invoke(methodInfo);
  19. });
  20. }
  21. private ServerInfo extractServerInfo(Class<?> clazz) {
  22. ApiServer apiServer = clazz.getAnnotation(ApiServer.class);
  23. ServerInfo serverInfo = new ServerInfo();
  24. serverInfo.setUrl(apiServer.value());
  25. return serverInfo;
  26. }
  27. private MethodInfo extractMethodInfo(Method method, Object[] args) {
  28. MethodInfo methodInfo = new MethodInfo();
  29. Annotation[] annotations = method.getAnnotations();
  30. // 1、获取请求方法和请求URL
  31. extractRequestUrl(annotations, methodInfo);
  32. // 2、获取请求参数
  33. extractRequestParam(method, args, methodInfo);
  34. // 3、获取返回对象信息
  35. extractResponseInfo(method, methodInfo);
  36. return methodInfo;
  37. }
  38. /**
  39. * 获取返回对象信息
  40. *
  41. * @param method method
  42. * @param methodInfo methodInfo
  43. */
  44. private void extractResponseInfo(Method method, MethodInfo methodInfo) {
  45. // 返回 flux 还是 mono
  46. // isAssignableFrom 判断类型是否是某个类的子类
  47. // instanceof 判断实例是否是某个类的子类
  48. // getReturnType 不带泛型信息
  49. boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class);
  50. methodInfo.setReturnFlux(isFlux);
  51. // getGenericReturnType 带泛型信息
  52. Type actualType = getActualType(method.getGenericReturnType());
  53. methodInfo.setReturnElementType((Class<?>) actualType);
  54. }
  55. /**
  56. * 获取请求方法和请求URL
  57. *
  58. * @param method method
  59. * @param args 参数
  60. * @param methodInfo methodInfo
  61. */
  62. private void extractRequestParam(Method method, Object[] args, MethodInfo methodInfo) {
  63. Map<String, Object> params = new LinkedHashMap<>();
  64. methodInfo.setParams(params);
  65. Parameter[] parameters = method.getParameters();
  66. for (int i = 0; i < parameters.length; i++) {
  67. PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
  68. if (pathVariable != null) {
  69. params.put(pathVariable.value(), args[i]);
  70. }
  71. RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
  72. if (requestBody != null) {
  73. methodInfo.setBody((Mono<?>) args[i]);
  74. Type actualTypeArgument = getActualType(parameters[i].getParameterizedType());
  75. methodInfo.setBodyElementType((Class<?>) actualTypeArgument);
  76. }
  77. }
  78. }
  79. /**
  80. * 获取参数实际类型
  81. *
  82. * @param type 类型
  83. */
  84. private Type getActualType(Type type) {
  85. ParameterizedType parameterizedType = (ParameterizedType) type;
  86. return parameterizedType.getActualTypeArguments()[0];
  87. }
  88. /**
  89. * 获取请求参数
  90. *
  91. * @param annotations annotations
  92. * @param methodInfo methodInfo
  93. */
  94. private void extractRequestUrl(Annotation[] annotations, MethodInfo methodInfo) {
  95. for (Annotation annotation : annotations) {
  96. // GET
  97. if (annotation instanceof GetMapping) {
  98. GetMapping mapping = (GetMapping) annotation;
  99. methodInfo.setUrl(mapping.value()[0]);
  100. methodInfo.setMethod(HttpMethod.GET);
  101. }
  102. // POST
  103. else if (annotation instanceof PostMapping) {
  104. PostMapping mapping = (PostMapping) annotation;
  105. methodInfo.setUrl(mapping.value()[0]);
  106. methodInfo.setMethod(HttpMethod.POST);
  107. }
  108. // PUT
  109. else if (annotation instanceof PutMapping) {
  110. PutMapping mapping = (PutMapping) annotation;
  111. methodInfo.setUrl(mapping.value()[0]);
  112. methodInfo.setMethod(HttpMethod.PUT);
  113. }
  114. // DELETE
  115. else if (annotation instanceof DeleteMapping) {
  116. DeleteMapping mapping = (DeleteMapping) annotation;
  117. methodInfo.setUrl(mapping.value()[0]);
  118. methodInfo.setMethod(HttpMethod.DELETE);
  119. }
  120. }
  121. }
  122. }

至此,我们就完成了 IUserApi 的动态代理

让 Spring 容器管理 IUserApi 和 ProxyCreator

我们使用 FactoryBean 来产生 IUserApi 对应的代理 bean

  1. @Bean
  2. ProxyCreator jdkProxyCreator() {
  3. return new JDKProxyCreator();
  4. }
  5. @Bean
  6. FactoryBean<IUserApi> userApi(ProxyCreator proxyCreator) {
  7. return new FactoryBean<IUserApi>() {
  8. /**
  9. * 返回代理对象
  10. */
  11. @Override
  12. public IUserApi getObject() throws Exception {
  13. return (IUserApi) proxyCreator.create(getObjectType());
  14. }
  15. /**
  16. * 返回接口类型
  17. */
  18. @Override
  19. public Class<?> getObjectType() {
  20. return IUserApi.class;
  21. }
  22. };
  23. }

测试

现在所需要的 ProxyCreator 和 RestHandler 都准备好了,我们就可以开始测试了

  1. @RestController
  2. public class TestController {
  3. @Autowired
  4. private IUserApi userApi;
  5. @GetMapping("/")
  6. public Flux<User> getAll() {
  7. // 实现调用 rest 接口的效果
  8. return userApi.getAll();
  9. }
  10. @GetMapping("/get/{id}")
  11. public Mono<User> getById(@PathVariable("id") String id) {
  12. Mono<User> mono = userApi.getById(id);
  13. mono.subscribe(System.out::println, e -> System.err.println(e.getMessage()));
  14. return mono;
  15. }
  16. @DeleteMapping("/delete/{id}")
  17. public Mono<Void> deleteById(@PathVariable("id") String id) {
  18. return userApi.deleteById(id);
  19. }
  20. @PostMapping("/create")
  21. public Mono<User> create(@RequestBody User user) {
  22. return userApi.create(Mono.just(user));
  23. }
  24. }