手写RPC框架-注解解析、接口代理生成

远程服务的核心

使用方法

点击这里查看详细使用方法

// 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值