美文网首页
自定义注解实现RPC远程调用

自定义注解实现RPC远程调用

作者: 秋风落叶黄 | 来源:发表于2021-02-24 20:28 被阅读0次

    源码地址:https://github.com/huangyichun/remotetransfer

    本文涉及知识点:

    • 自定义注解
    • 动态代理
    • Spring bean加载
    • Java 8 优化的策略模式

    项目背景:由于原直播系统采用SpringCloud部署在虚拟机(仅使用SpringCloud的注册中心),现打算使用k8s部署,由于k8s和SpringCloud功能很多重合了,如果在k8s系统使用SpringCloud将无法用到k8s的注册中心、负载均衡等功能,而且项目部署也过臃肿。 因此有2种方案:

    • 方案一

      采用spring-cloud-starter-kubernetes 组件替换SpringCloud,该方案存在一个问题,研发需要熟悉k8s功能,而且未来项目必须使用k8s部署。

    • 方案二

      采用Http请求替换SpringCloud的注册中心Feign调用。由于接口较多,如果每个接口都改动工作量较大,也不利于代码维护。因此使用自定义注解来替换@FeignClient注册。

    具体实现:

    • 创建自定义注解

      /**
       * 远程调用,替换SpringCloud Feign
       * @Author: huangyichun
       * @Date: 2021/2/22
       */
      @Target(ElementType.TYPE)
      @Inherited
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface RemoteTransfer {
      
          String hostName();
      }
      
    • 定义handler接口用于处理实际业务逻辑

      /**
       * @Author: huangyichun
       * @Date: 2021/2/24
       */
      public interface RemoteTransferHandler {
      
          Object handler(String host, Method method, Object[] args);
      }
      
    • 采用反射动态创建类,并注册到Spring容器中

    创建 RemoteTransferRegister类,并实现BeanFactoryPostProcessor接口,该接口的方法postProcessBeanFactory是在bean被实例化之前被调用的。这样保证了自定义注解声明的Bean能被其他模块Bean依赖。

    /**
     * @Author: huangyichun
     * @Date: 2021/2/23
     */
    @Slf4j
    @Component
    public class RemoteTransferRegister implements BeanFactoryPostProcessor {
    
        /**
         * 设置扫描的包
         */
        private static final String SCAN_PATH = "com.huang.web";
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
            RemoteTransferInvoke transferInvoke = new RemoteTransferInvoke(invokeRestTemplate());
    
            //扫描注解声明类
            Set<Class<?>> classSet = new Reflections(SCAN_PATH).getTypesAnnotatedWith(RemoteTransfer.class);
            for (Class<?> cls : classSet) {
                log.info("create proxy class name:{}", cls.getName());
    
                //动态代理的handler
                InvocationHandler handler = (proxy, method, args) -> transferInvoke.invoke(cls, method, args);
                //生成代理类
                Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{cls}, handler);
    
                //注册到Spring容器
                beanFactory.registerSingleton(cls.getName(), proxy);
            }
        }
      
      private RestTemplate invokeRestTemplate() {
           //生成restTemplate 省略代码 
      }
    }
    
    • 创建RemoteTransferInvoke类,处理动态代理类的请求

      支持解析@GetMapping、@PostMapping、@PutMapping、以及@DeleteMapping。使用策略模式+Map优化 if else 语句。具体代码如下

      @Slf4j
      @Data
      public class RemoteTransferInvoke {
      
          private final RestTemplate restTemplate;
      
          /**
           * 用于实际远程调用处理
           */
          private Map<Class<? extends Annotation>, RemoteTransferHandler> requestMethodMap = new HashMap<>();
      
          public RemoteTransferInvoke(RestTemplate restTemplate) {
              this.restTemplate = restTemplate;
              init();
          }
      
          /**
           * 使用策略模式+Map 去除
           */
          private void init() {
              requestMethodMap.put(GetMapping.class, (host, method, args) -> {
                  GetMapping methodAnnotation = method.getAnnotation(GetMapping.class);
                  String path = methodAnnotation.value()[0];
                  return getOrDeleteMapping(method, args, host, path, HttpMethod.GET);
              });
      
              requestMethodMap.put(PutMapping.class, (host, method, args) -> {
                  PutMapping methodAnnotation = method.getAnnotation(PutMapping.class);
                  String path = methodAnnotation.value()[0];
                  return putOrPostMapping(method, args, host, path, HttpMethod.PUT);
              });
      
      
              requestMethodMap.put(PostMapping.class, (host, method, args) -> {
                  PostMapping methodAnnotation = method.getAnnotation(PostMapping.class);
                  String path = methodAnnotation.value()[0];
                  return putOrPostMapping(method, args, host, path, HttpMethod.POST);
              });
              requestMethodMap.put(DeleteMapping.class, (host, method, args) -> {
                  DeleteMapping methodAnnotation = method.getAnnotation(DeleteMapping.class);
                  String path = methodAnnotation.value()[0];
                  return getOrDeleteMapping(method, args, host, path, HttpMethod.DELETE);
              });
          }
      
      
          /**
           * 动态代理调用方法
           * @param tClass 类
           * @param method 方法
           * @param args 请求参数
           * @return 返回值
           */
          public Object invoke(Class<?> tClass, Method method, Object[] args) {
              RemoteTransfer remoteAnnotation = tClass.getAnnotation(RemoteTransfer.class);
              String host = RemoteTransferConfig.map.get(remoteAnnotation.hostName());
      
              Annotation[] annotations = method.getAnnotations();
              Optional<Annotation> first = Arrays.stream(annotations).filter(annotation1 -> requestMethodMap.containsKey(annotation1.annotationType())).findFirst();
              Preconditions.checkArgument(first.isPresent(), "注解使用错误");
      
              Annotation methodAnnotation = first.get();
              
              return requestMethodMap.get(methodAnnotation.annotationType()).handler(host, method, args);
          }
      
      
          private Object putOrPostMapping(Method method, Object[] args, String host, String url, HttpMethod httpMethod) {
              url = RemoteTransferUtil.dealPathVariable(method, args, url);
              UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + url);
      
              HttpHeaders httpHeaders = new HttpHeaders();
              HttpEntity<JSONObject> entity = new HttpEntity<>(RemoteTransferUtil.extractBody(method, args), httpHeaders);
              ResponseEntity<?> exchange = restTemplate.exchange(builder.toUriString(), httpMethod, entity, method.getReturnType());
              return exchange.getBody();
          }
      
          private Object getOrDeleteMapping(Method method, Object[] args, String host, String url, HttpMethod httpMethod) {
      
              UriComponentsBuilder builder = buildGetUrl(method, args, host, url);
              HttpHeaders httpHeaders = new HttpHeaders();
              HttpEntity<JSONObject> entity = new HttpEntity<>(null, httpHeaders);
              return this.restTemplate.exchange(builder.toUriString(), httpMethod, entity, method.getReturnType()).getBody();
          }
      
      
          private UriComponentsBuilder buildGetUrl(Method method, Object[] args, String host, String url) {
              url = RemoteTransferUtil.dealPathVariable(method, args, url);
              UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + url);
      
              for (Map.Entry<String, String> entry : RemoteTransferUtil.extractParams(method, args).entrySet()) {
                  builder.queryParam(entry.getKey(), entry.getValue());
              }
              return builder;
          }
      
    • 配置类

      用于获取注解的host地址

      @Component
      public class RemoteTransferConfig {
      
          public static Map<String, String> map = new HashMap<>();
      
          @Value("${remote.transfer.host}")
          private String port;
      
          @PostConstruct
          public void init() {
              map.put("TRADE-PLAY", port);
          }
      }
      
    • 工具类

      /**
       * @Author: huangyichun
       * @Date: 2021/2/23
       */
      @Slf4j
      public class RemoteTransferUtil {
      
          /**
           * 获取Path参数
           * @param method 方法
           * @param args 值
           * @return
           */
          public static Map<String, String> extractPath(Method method, Object[] args) {
              Map<String, String> params = new HashMap<>();
              Parameter[] parameters = method.getParameters();
      
              if (parameters.length == 0) {
                  return params;
              }
      
              for (int i = 0; i < parameters.length; i++) {
                  PathVariable param = parameters[i].getAnnotation(PathVariable.class);
                  if (param != null) {
                      params.put(param.value(), String.valueOf(args[i]));
                  }
              }
              return params;
          }
      
          /**
           * 获取请求body
           * @param method
           * @param args
           * @return
           */
          public static JSONObject extractBody(Method method, Object[] args) {
              JSONObject object = new JSONObject();
              Parameter[] parameters = method.getParameters();
              if (parameters.length == 0) {
                  return null;
              }
      
              for (int i = 0; i < parameters.length; i++) {
                  RequestBody param = parameters[i].getAnnotation(RequestBody.class);
                  if (param != null) {
                      String returnStr = JSON.toJSONString(args[i], SerializerFeature.WriteMapNullValue, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat);
                      object = JSONObject.parseObject(returnStr);
                  }
              }
              return object;
          }
      
          /**
           * 处理url的Path
           * @param method 方法
           * @param args 值
           * @param url url
           * @return
           */
          public static String dealPathVariable(Method method, Object[] args, String url) {
              for (Map.Entry<String, String> entry : RemoteTransferUtil.extractPath(method, args).entrySet()) {
                  if (url.contains("{" + entry.getKey() + "}")) {
                      url = url.replace("{" + entry.getKey() + "}", entry.getValue());
                  }
              }
              return url;
          }
      
      
          /**
           * 处理请求参数
           * @param method 方法
           * @param args 值
           * @return
           */
          public static LinkedHashMap<String, String> extractParams(Method method, Object[] args) {
              LinkedHashMap<String, String> params = new LinkedHashMap<>();
              Parameter[] parameters = method.getParameters();
      
              if (parameters.length == 0) {
                  return params;
              }
      
              for (int i = 0; i < parameters.length; i++) {
                  RequestParam param = parameters[i].getAnnotation(RequestParam.class);
                  if (param != null) {
                      params.put(param.value(), String.valueOf(args[i]));
                  }
              }
              return params;
          }
      }
      

    注解的具体使用:

    @RemoteTransfer(hostName = "TRADE-PLAY")
    public interface TradePlayServiceApi {
    
         @GetMapping("/open/get")
         String test(@RequestParam("type") String type);
    
         @PostMapping("/open/post")
         ResponseResult post(@RequestBody PostRequest request);
    }
    

    测试类:

    @SpringBootTest
    class WebApplicationTests {
    
        @Autowired
        private TradePlayServiceApi tradePlayServiceApi;
    
        @Test
        public void get() {
            String type = tradePlayServiceApi.test("type");
            System.out.println(type);
        }
    
        @Test
        public void post() {
            PostRequest request = new PostRequest();
            request.setId("id");
            PostRequest.Person person = new PostRequest.Person("name", "age");
    
            request.setPerson(person);
            ResponseResult post = tradePlayServiceApi.post(request);
            System.out.println(post);
        }
    }
    

    最终测试通过,请求正常返回。

    相关文章

      网友评论

          本文标题:自定义注解实现RPC远程调用

          本文链接:https://www.haomeiwen.com/subject/lecefltx.html