天天看點

手寫RPC架構-注解解析、接口代理生成遠端服務的核心實作邏輯項目位址

手寫RPC架構-遠端服務調用

  • 遠端服務的核心
    • 使用方法
    • 核心點
  • 實作邏輯
    • 1.解析添加指定注解的接口
      • 自定義解析器
      • 拿到要解析的路徑
      • 生成代理類 并注入到spring工廠中
        • 主要元件
        • 生成代理類邏輯
          • 主要點
          • 生成代理源碼
  • 項目位址

遠端服務的核心

使用方法

點選這裡檢視詳細使用方法

// provider為遠端服務的服務名稱
@WbClient("provider")
public interface TestService {
	// value為想要調用的路徑 method為請求的方法,POST GET
    @WbRequestMapping(value = "/test", method = WbRequestMethod.GET)
    void test(@WbRequestParam(value = "asd") String str, String str2);

    @WbRequestMapping(value = "/testbody", method = WbRequestMethod.POST)
    TestUser testpost(@WbRequestBody TestUser testUser);
}
           

核心點

掃描标注注解的接口,生成實際的代理類,在代理類中實作遠端調用(由于在注冊中心中已經儲存了遠端服務的IP和端口号,隻需要拿到調用即可)。把生成的代理類注入到spring工廠中,在調用端使用Autowired注解即可自動注入代理類。

實作邏輯

1.解析添加指定注解的接口

自定義解析器

自定義解析類實作ClassPathScanningCandidateComponentProvider接口,可以解析接口。

public class InterfacePathScanningCandidateComponentProvider extends ClassPathScanningCandidateComponentProvider {
    public InterfacePathScanningCandidateComponentProvider(boolean useDefaultFilters) {
        super(useDefaultFilters);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isIndependent() && metadata.isInterface();
    }
}
           

拿到要解析的路徑

@Bean
    public MyBeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor(Environment environment) {
        // 通過配置的wb.packageScan拿到需要解析的路徑
        String packagesScan = environment.getProperty("wb.packageScan", String.class, null);
        return new MyBeanDefinitionRegistryPostProcessor(packagesScan);
    }
           

生成代理類 并注入到spring工廠中

主要元件

通過BeanDefinitionRegistryPostProcessor修改BeanDefinitionRegistry,把代理類注入到spring中。

public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    private String packagesScan;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        // 解析
        Set<BeanDefinition> defs = parseInterfaces();
        for (BeanDefinition def : defs) {
            try {
                Class<?> clazz = forName(def.getBeanClassName());
                // 生成代理類
                Class<?> proxyClass = WbClientProxy.buildProxy(clazz);
                // 代理類注冊為RootBeanDefinition後放入Spring中
                RootBeanDefinition beanDefinition = new RootBeanDefinition();
                beanDefinition.setBeanClass(proxyClass);
                beanDefinitionRegistry.registerBeanDefinition(proxyClass.getSimpleName().toLowerCase(), beanDefinition);
            } catch (ClassNotFoundException | NotFoundException | CannotCompileException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 解析
     *
     * @return
     */
    private Set<BeanDefinition> parseInterfaces() {
        // 解析
        InterfacePathScanningCandidateComponentProvider scanner = new InterfacePathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(WbClient.class));
        return new HashSet<>(scanner.findCandidateComponents(packagesScan));
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    public MyBeanDefinitionRegistryPostProcessor(String packagesScan) {
        this.packagesScan = packagesScan;
    }
}
           

生成代理類邏輯

主要點
  1. 解析接口注解@WbClient拿到要調用的服務端
  2. 解析接口中的方法注解,拿到請求路徑和請求方法
  3. 在代理類中注入注冊工廠(IRegister實作類)和WbClientCall調用
  4. 在代理類中方法中擷取服務,得到路徑,處理響應資訊
生成代理源碼
public class WbClientProxy {

    /**
     * 生成的代理對象名稱字首
     */
    private static final String PROXY_PREFIX = "WbRpcProxy";

    /**
     * 生成的代理對象名稱字尾
     */
    private static final String PROXY_SUFFIX = "Impl";

    /**
     * 參數為@WbRequestBody的類型
     */
    private static final String REQUEST_BODY_PARAMETER = "REQUEST_BODY_PARAMETER";

    public static <T> Class<T> buildProxy(Class<T> t) throws NotFoundException, CannotCompileException, ClassNotFoundException {
        // 擷取調用的服務名稱
        String clientServerName = t.getAnnotation(WbClient.class).value();
        ClassPool pool = ClassPool.getDefault();
        //建立代理類對象
        CtClass ctClass = pool.makeClass(getImplName(t));
        //設定代理類的接口
        CtClass interj = pool.getCtClass(t.getName());
        // 設定方法的屬性
        initClassField(ctClass, interj, pool);
        //代理類的所有方法
        CtMethod[] methods = interj.getDeclaredMethods();
        // 擷取所有方法的參數資訊
        Map<String, List<String>> parameterMap = buildParametersMap(t);

        for(CtMethod method : methods) {
            // 建立代理類的方法
            CtMethod ctMethod = initClassMethod(method, ctClass, parameterMap, t, clientServerName);
            ctClass.addMethod(ctMethod);
        }
        return (Class) ctClass.toClass();
    }

    private static CtMethod initClassMethod(CtMethod method, CtClass ctClass, Map<String,
            List<String>> parameterMap, Class<?> t, String clientServerName) throws NotFoundException, ClassNotFoundException, CannotCompileException {
        String methodName = method.getName();
        CtMethod cm = new CtMethod(method.getReturnType(), methodName, method.getParameterTypes(), ctClass);
        // 擷取參數清單名稱
        String methodFullName = buildMethodFullName(method);
        // 該方法參數的映射值
        List<String> parameterNames = parameterMap.get(methodFullName);
        // 解析通路的路徑
        WbRequestMapping requestMapping = (WbRequestMapping) method.getAnnotation(WbRequestMapping.class);
        StringBuilder url = new StringBuilder(requestMapping.value());
        boolean containSymbol = false;
        String requestBody = "null";
        if (!CollectionUtils.isEmpty(parameterNames)) {
            String connect = "?";
            for (int i = 0; i < parameterNames.size(); i++) {
                String parameterName = parameterNames.get(i);
                if (StringUtils.EMPTY.equals(parameterName)) {
                    System.err.println(t.getName() + "類中," + method.getName() + "方法的第" + (i + 1) + "參數沒有加WbRequestParam注解,無法比對,請添加WbRequestParam,或在打包時,使用-parameters,可根據參數名自動比對。");
                    continue;
                }
                if (REQUEST_BODY_PARAMETER.equals(parameterName)) {
                    requestBody = "$" + (i + 1);
                    continue;
                }
                containSymbol = true;
                if ("?".equals(connect)) {
                    url = new StringBuilder(url + connect + parameterName + "=\"+" + "$" + (i + 1));
                } else {
                    url.append("+\"").append(connect).append(parameterName).append("=\"+").append("$").append(i + 1);
                }
                connect = "&";
            }
        }

        if (!containSymbol) {
            url = new StringBuilder(url + "\"");
        }
        String returnType = "void".equals(cm.getReturnType().getName()) ? "com.wb.spring.boot.autoconfigure.proxy.Void" : cm.getReturnType().getName();
        String isReturn = "com.wb.spring.boot.autoconfigure.proxy.Void".equals(returnType) ? "" : "return (" + returnType + ")";
        cm.setBody("{" + isReturn + " clientCall.call(\"http://\" + register.getServer(\"" + clientServerName + "\") + \"" + url + ",\"" + requestMapping.method() + "\", " + requestBody + ",  Class.forName(\"" + returnType+ "\"));}");

        return cm;
    }

    /**
     * 建立類的屬性
     * @param ctClass
     *  生成的代理類
     * @param interj
     *  代理類的接口
     * @param pool
     * @return
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private static CtClass initClassField(CtClass ctClass, CtClass interj, ClassPool pool) throws CannotCompileException, NotFoundException {
        CtClass[] interfaces = new CtClass[]{interj};
        ctClass.setInterfaces(interfaces);
        // 擷取常量池
        ConstPool constPool = ctClass.getClassFile().getConstPool();
        // 建立屬性,調用遠端服務使用
        CtField ctField  = new CtField(pool.get("com.wb.spring.boot.autoconfigure.proxy.WbClientCall"), "clientCall", ctClass);
        ctField.setModifiers(AccessFlag.PUBLIC);
        FieldInfo fieldInfo = ctField.getFieldInfo();
        // 添加Autowired注解,後續注冊到spring中會自動裝配
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation filedAnnotation = new Annotation(Autowired.class.getName(), constPool);
        annotationsAttribute.addAnnotation(filedAnnotation);
        fieldInfo.addAttribute(annotationsAttribute);

        CtField registerField  = new CtField(pool.get("com.wb.spring.boot.autoconfigure.register.IRegister"), "register", ctClass);
        ctField.setModifiers(AccessFlag.PUBLIC);
        FieldInfo registerFieldInfo = registerField.getFieldInfo();
        registerFieldInfo.addAttribute(annotationsAttribute);
        // 加入到類檔案中
        ctClass.addField(ctField);
        ctClass.addField(registerField);
        return ctClass;
    }

    /**
     * 擷取全限定名稱
     * @param method
     * @return
     * @throws NotFoundException
     */
    private static String buildMethodFullName(CtMethod method) throws NotFoundException {
        StringBuilder methodFullName = new StringBuilder(method.getName());
        if (method.getParameterTypes() != null) {
            for (CtClass parameterType : method.getParameterTypes()) {
                methodFullName.append(parameterType.getName());
            }
        }

        return methodFullName.toString();
    }

    /**
     * key-方法名稱+方法參數類型
     * value-方法參數名稱集合
     * @param clazz
     *  要解析的類
     *
     * @return
     *  方法的類對應參數名稱
     */
    private static Map<String, List<String>> buildParametersMap(Class<?> clazz) {
        // 同名參數映射使用 此處使用1.8之上的--parameters才能支援,否則隻能使用注解映射。
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        Map<String, List<String>> parameterMap = new HashMap<>(8);
        for (Method method : clazz.getMethods()) {
            StringBuilder name = new StringBuilder(method.getName());
            List<String> parameterRequestNames = new ArrayList<>();
            if (method.getParameters() != null) {
                for (Parameter parameter : method.getParameters()) {
                    name.append(parameter.getType().getName());
                    WbRequestParam annotation = parameter.getAnnotation(WbRequestParam.class);
                    String parameterRequestName = StringUtils.EMPTY;
                    if (annotation != null) {
                        parameterRequestName = annotation.value();
                    }
                    WbRequestBody wbRequestBody = parameter.getAnnotation(WbRequestBody.class);
                    if (wbRequestBody != null) {
                        parameterRequestName = REQUEST_BODY_PARAMETER;
                    }
                    parameterRequestNames.add(parameterRequestName);
                }
            }

            String[] parameterNames = discoverer.getParameterNames(method);
            if (parameterNames == null) {
                parameterMap.put(name.toString(), parameterRequestNames);
                continue;
            }
            List<String> correctParameterRequestNames = new ArrayList<>();
            for (int i = 0; i < parameterRequestNames.size(); i++) {
                String parameterRequestName = parameterRequestNames.get(i);
                if (StringUtils.EMPTY.equals(parameterRequestName)) {
                    correctParameterRequestNames.add(parameterNames[i]);
                    continue;
                }
                correctParameterRequestNames.add(parameterRequestName);
            }

            parameterMap.put(name.toString(), correctParameterRequestNames);
        }

        return parameterMap;
    }
    /**
     * 擷取代理類的名稱
     * @param t
     *  實作類類型
     * @return
     *  實作類名稱
     */
    private static <T> String getImplName(Class<T> t) {
        return t.getPackage() + "." + PROXY_PREFIX  + t.getSimpleName() + PROXY_SUFFIX;
    }
}

           

項目位址

https://gitee.com/xu–wenbin/wb-spring-boot-project