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

源码地址: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> 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, 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 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 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 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 entry : RemoteTransferUtil.extractParams(method, args).entrySet()) {
                builder.queryParam(entry.getKey(), entry.getValue());
            }
            return builder;
        }
    
  • 配置类

    用于获取注解的host地址

    @Component
    public class RemoteTransferConfig {
    
        public static Map 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 extractPath(Method method, Object[] args) {
            Map 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 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 extractParams(Method method, Object[] args) {
            LinkedHashMap 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远程调用)