揭开sofa-boot关于sofa-rpc proxy的秘密

442 阅读6分钟

“我正在参加「掘金·启航计划」”

前言


我们在前面几篇文章已经介绍了,sofa-rpc、sofa-bolt、sofa-boot相关知识,接下来我们再深入sofa-boot框架里面一览rpc proxy的面纱,看看框架里面具体是怎么实现代理类的。

image.png

在官网有个基础的架构图,这个跟dubbo、其他rpc框架很相似,就是分为4步,第一步服务端注册到注册中心。然后客户端进行订阅,然后接收注册中心关于服务端相关数据,最后客户端在执行远程调用的时候通过proxy 进行invoke调用,在sofa里面是refer指向也是类似的。

why proxy是写在boot框架,而不是rpc框架的


如果按框架的划分,是可以这样设计的,rpc负责跟底层bolt框架的交互,而boot框架作为再高一层对rpc框架的配置控制,比如说我这个模块依赖了rpc jar,是不是对应的类需要扫描进去,还是说我通过配置开关来控制。其次对于底层能力的封装,sofa-boot有个@SofaReference 就是对底层rpc调用进行处理,简化了我们直接client、server方式的调用。

但是

作为一个开源框架来看,这无疑也带来新的问题,就是如果我作为一个开发者,本身每家公司都有自己的契约,一般就是依赖springboot体系,所以说sofa-boot很多东西用不上,这对于开源框架来讲是设计不太妥当的,按我想法应该是将这个注解也放在sofa-rpc框架,而sofa-boot只是将这些类进行代理即可,即使你不依赖sofa-boot,那我也有快捷的方式进行初始化,this is my idea~

proxy 的面纱


在面试中经常被问到java的代理有哪些方式,基本就是jdk代理、cglib代理,下面给个demo

cglib代理

image.png

相当于我们把一个类创建为代理类,然后加上对应的拦截器,当我们在执行的时候进行intercept,有些是打印日志,然后执行回之前的父类方法。

如果让我来设计rpc代理是怎样的呢?其实就是我们注入spring类的时候,一般有个注解对吧,@Resource这些,aop底层通过beanpostprocess这个类,也就是类加载的时候进行改写,我们可以改写之后,参照上面的拦截器方式,我们通过调用netty的通讯,将类+方法+version转换成对应的ip:port,然后请求拿到响应之后回填到这个代理类中,到此我们就完成了这个proxy,下面具体看下源码。

sofa-boot proxy


BindingAdapter

为什么从这个类开始呢?因为我在看源码的时候也在里面绕晕了,如果我们从这个类开始会容易入手,现在开始讲解。

Object inBinding(Object contract, T binding, SofaRuntimeContext sofaRuntimeContext);

在绑定中,在绑定中意味着引用服务

原文注释是这样的,就是我们将注解跟底层的通讯进行绑定,再看下具体的实现类。

image.png

JvmBindingAdapter按我理解应该是我们自己实现了一个代理类,就是一个项目里面,我们自己给他实现了代理类,就跟上面cglib代理同理,另一种是RpcBindingAdapter,为了实现sofa-rpc组件用的。

JvmBindingAdapter

image.png

image.png

看到没有,这就是典型的代理类,关键就是看拦截器内容JvmServiceInvoker。

JvmServiceInvoker

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    if (!SofaRuntimeProperties.isJvmFilterEnable()) {
        // Jvm filtering is not enabled
        return super.invoke(invocation);
    }

    ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
    JvmFilterContext context = new JvmFilterContext(invocation);
    Object rtn;

    if (getTarget() == null) {
        ServiceComponent serviceComponent = DynamicJvmServiceProxyFinder
            .getDynamicJvmServiceProxyFinder().findServiceComponent(
                sofaRuntimeContext.getAppClassLoader(), contract);
        if (serviceComponent == null) {
            // Jvm service is not found in normal or Ark environment
            // We're actually invoking an RPC service, skip Jvm filtering
            return super.invoke(invocation);
        }
        context.setSofaRuntimeContext(serviceComponent.getContext());
    } else {
        context.setSofaRuntimeContext(sofaRuntimeContext);
    }

    long startTime = System.currentTimeMillis();
    try {
        Thread.currentThread().setContextClassLoader(serviceClassLoader);
        // Do Jvm filter <code>before</code> invoking
        // if some filter returns false, skip remaining filters and actual Jvm invoking
        if (JvmFilterHolder.beforeInvoking(context)) {
            rtn = doInvoke(invocation);
            context.setInvokeResult(rtn);
        }
    } catch (Throwable e) {
        // Exception occurs, set <code>e</code> in Jvm context
        context.setException(e);
        doCatch(invocation, e, startTime);
        throw e;
    } finally {
        // Do Jvm Filter <code>after</code> invoking regardless of the fact whether exception happens or not
        JvmFilterHolder.afterInvoking(context);
        rtn = context.getInvokeResult();
        doFinally(invocation, startTime);
        Thread.currentThread().setContextClassLoader(oldClassLoader);
    }
    return rtn;
}

最重要的是doInvoke(invocation),invocation.getMethod().invoke(targetObj, invocation.getArguments())不外乎就是直接代理类执行方法并且附上对应的参数。

RpcBindingAdapter

image.png

image.png

com.alipay.sofa.rpc.bootstrap.DefaultConsumerBootstrap#refer

@Override
public T refer() {
    if (proxyIns != null) {
        return proxyIns;
    }
    synchronized (this) {
        if (proxyIns != null) {
            return proxyIns;
        }
        String key = consumerConfig.buildKey();
        String appName = consumerConfig.getAppName();
        // 检查参数
        checkParameters();
        // 提前检查接口类
        if (LOGGER.isInfoEnabled(appName)) {
            LOGGER.infoWithApp(appName, "Refer consumer config : {} with bean id {}", key, consumerConfig.getId());
        }

        // 注意同一interface,同一tags,同一protocol情况
        AtomicInteger cnt = REFERRED_KEYS.get(key); // 计数器
        if (cnt == null) { // 没有发布过
            cnt = CommonUtils.putToConcurrentMap(REFERRED_KEYS, key, new AtomicInteger(0));
        }
        int c = cnt.incrementAndGet();
        int maxProxyCount = consumerConfig.getRepeatedReferLimit();
        if (maxProxyCount > 0) {
            if (c > maxProxyCount) {
                cnt.decrementAndGet();
                // 超过最大数量,直接抛出异常
                throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_DUPLICATE_CONSUMER_CONFIG, key,
                    maxProxyCount));
            } else if (c > 1) {
                if (LOGGER.isInfoEnabled(appName)) {
                    LOGGER.infoWithApp(appName, "Duplicate consumer config with key {} has been referred!"
                        + " Maybe it's wrong config, please check it."
                        + " Ignore this if you did that on purpose!", key);
                }
            }
        }

        try {
            // build cluster
            cluster = ClusterFactory.getCluster(this);
            // build listeners
            consumerConfig.setConfigListener(buildConfigListener(this));
            consumerConfig.setProviderInfoListener(buildProviderInfoListener(this));
            // init cluster
            cluster.init();
            // 构造Invoker对象(执行链)
            proxyInvoker = buildClientProxyInvoker(this);
            // 创建代理类
            proxyIns = (T) ProxyFactory.buildProxy(consumerConfig.getProxy(), consumerConfig.getProxyClass(),
                proxyInvoker);

            //动态配置
            final String dynamicAlias = consumerConfig.getParameter(DynamicConfigKeys.DYNAMIC_ALIAS);
            if (StringUtils.isNotBlank(dynamicAlias)) {
                final DynamicConfigManager dynamicManager = DynamicConfigManagerFactory.getDynamicManager(
                    consumerConfig.getAppName(), dynamicAlias);
                dynamicManager.initServiceConfiguration(consumerConfig.getInterfaceId());
            }
        } catch (Exception e) {
            if (cluster != null) {
                cluster.destroy();
                cluster = null;
            }
            consumerConfig.setConfigListener(null);
            consumerConfig.setProviderInfoListener(null);
            cnt.decrementAndGet(); // 发布失败不计数
            if (e instanceof SofaRpcRuntimeException) {
                throw (SofaRpcRuntimeException) e;
            } else {
                throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_BUILD_CONSUMER_PROXY), e);
            }
        }
        if (consumerConfig.getOnAvailable() != null && cluster != null) {
            cluster.checkStateChange(false); // 状态变化通知监听器
        }
        RpcRuntimeContext.cacheConsumerConfig(this);
        return proxyIns;
    }
}

这就是底层跟netty交互,什么检查链接,然后计数器,里面比较重要的地方就是创建代理类

// 创建代理类
proxyIns = (T) ProxyFactory.buildProxy(consumerConfig.getProxy(), consumerConfig.getProxyClass(),
    proxyInvoker);

image.png

这里面跟上面的动态代理不太一样了,通过字节码的方式去改写类,好家伙~ 默认通过JavassistProxy框架改写代码字节码。

com.alipay.sofa.rpc.proxy.javassist.JavassistProxy#getProxy

@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> interfaceClass, Invoker proxyInvoker) {
    StringBuilder debug = null;
    if (LOGGER.isDebugEnabled()) {
        debug = new StringBuilder();
    }
    try {
        Class clazz = PROXY_CLASS_MAP.get(interfaceClass);
        if (clazz == null) {
            //生成代理类
            String interfaceName = ClassTypeUtils.getTypeStr(interfaceClass);
            ClassPool mPool = ClassPool.getDefault();
            mPool.appendClassPath(new LoaderClassPath(ClassLoaderUtils.getClassLoader(JavassistProxy.class)));
            CtClass mCtc = mPool.makeClass(interfaceName + "_proxy_" + counter.getAndIncrement());
            if (interfaceClass.isInterface()) {
                mCtc.addInterface(mPool.get(interfaceName));
            } else {
                throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
            }

            // 继承 java.lang.reflect.Proxy
            mCtc.setSuperclass(mPool.get(java.lang.reflect.Proxy.class.getName()));
            CtConstructor constructor = new CtConstructor(null, mCtc);
            constructor.setModifiers(Modifier.PUBLIC);
            constructor.setBody("{super(new " + UselessInvocationHandler.class.getName() + "());}");
            mCtc.addConstructor(constructor);

            List<String> fieldList = new ArrayList<String>();
            List<String> methodList = new ArrayList<String>();

            fieldList.add("public " + Invoker.class.getCanonicalName() + " proxyInvoker = null;");
            createMethod(interfaceClass, fieldList, methodList);

            for (String fieldStr : fieldList) {
                if (LOGGER.isDebugEnabled()) {
                    debug.append(fieldStr).append("\n");
                }
                mCtc.addField(CtField.make(fieldStr, mCtc));
            }
            for (String methodStr : methodList) {
                if (LOGGER.isDebugEnabled()) {
                    debug.append(methodStr).append("\n");
                }
                mCtc.addMethod(CtMethod.make(methodStr, mCtc));
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("javassist proxy of interface: {} \r\n{}", interfaceClass,
                    debug != null ? debug.toString() : "");
            }
            clazz = mCtc.toClass();
            PROXY_CLASS_MAP.put(interfaceClass, clazz);
        }
        Object instance = clazz.newInstance();
        clazz.getField("proxyInvoker").set(instance, proxyInvoker);
        return (T) instance;
    } catch (Exception e) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("javassist proxy of interface: {} \r\n{}", interfaceClass,
                debug != null ? debug.toString() : "");
        }
        throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_PROXY_CONSTRUCT, "javassist"), e);
    }
}

创建方法

private void createMethod(Class<?> interfaceClass, List<String> fieldList, List<String> resultList) {
    Method[] methodAry = interfaceClass.getMethods();
    StringBuilder sb = new StringBuilder(512);
    int mi = 0;
    for (Method m : methodAry) {
        mi++;
        if (Modifier.isNative(m.getModifiers()) || Modifier.isFinal(m.getModifiers()) ||
            Modifier.isStatic(m.getModifiers())) {
            continue;
        }
        Class<?>[] mType = m.getParameterTypes();
        Class<?> returnType = m.getReturnType();

        sb.append(Modifier.toString(m.getModifiers()).replace("abstract", ""))
            .append(" ").append(ClassTypeUtils.getTypeStr(returnType)).append(" ").append(m.getName())
            .append("( ");
        int c = 0;

        for (Class<?> mp : mType) {
            sb.append(" ").append(mp.getCanonicalName()).append(" arg").append(c).append(" ,");
            c++;
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append(")");
        Class<?>[] exceptions = m.getExceptionTypes();
        if (exceptions.length > 0) {
            sb.append(" throws ");
            for (Class<?> exception : exceptions) {
                sb.append(exception.getCanonicalName()).append(" ,");
            }
            sb = sb.deleteCharAt(sb.length() - 1);
        }
        sb.append("{");

        sb.append(" Class clazz = ").append(interfaceClass.getCanonicalName()).append(".class;");
        sb.append(" ").append(Method.class.getCanonicalName()).append(" method =  method_").append(mi).append(";");
        sb.append(" Class[] paramTypes = new Class[").append(c).append("];");
        sb.append(" Object[] paramValues = new Object[").append(c).append("];");
        StringBuilder methodSig = new StringBuilder();
        for (int i = 0; i < c; i++) {
            sb.append("paramValues[").append(i).append("] = ($w)$").append(i + 1).append(";");
            sb.append("paramTypes[").append(i).append("] = ").append(mType[i].getCanonicalName()).append(".class;");
            methodSig.append("," + mType[i].getCanonicalName() + ".class");
        }

        fieldList.add("private " + Method.class.getCanonicalName() + " method_" + mi + " = "
            + ReflectUtils.class.getCanonicalName() + ".getMethod("
            + interfaceClass.getCanonicalName() + ".class, "" + m.getName() + "", "
            + (c > 0 ? "new Class[]{" + methodSig.toString().substring(1) + "}" : "new Class[0]") + ");"
            );

        sb.append(SofaRequest.class.getCanonicalName()).append(" request = ")
            .append(MessageBuilder.class.getCanonicalName())
            .append(".buildSofaRequest(clazz, method, paramTypes, paramValues);");
        sb.append(SofaResponse.class.getCanonicalName()).append(" response = ")
            .append("proxyInvoker.invoke(request);");
        sb.append("if(response.isError()){");
        sb.append("  throw new ").append(SofaRpcException.class.getName()).append("(")
            .append(RpcErrorType.class.getName())
            .append(".SERVER_UNDECLARED_ERROR,").append(" response.getErrorMsg());");
        sb.append("}");

        sb.append("Object ret = response.getAppResponse();");
        sb.append("if (ret instanceof Throwable) {");
        sb.append("    throw (Throwable) ret;");
        sb.append("} else {");
        if (returnType.equals(void.class)) {
            sb.append(" return;");
        } else {
            sb.append(" return ").append(asArgument(returnType, "ret")).append(";");
        }
        sb.append("}");

        sb.append("}");

        resultList.add(sb.toString());
        sb.delete(0, sb.length());
    }

    // toString()
    sb.append("public String toString() {");
    sb.append("  return proxyInvoker.toString();");
    sb.append("}");
    resultList.add(sb.toString());
    // hashCode()
    sb.delete(0, sb.length());
    sb.append("public int hashCode() {");
    sb.append("  return proxyInvoker.hashCode();");
    sb.append("}");
    resultList.add(sb.toString());
    // equals()
    sb.delete(0, sb.length());
    sb.append("public boolean equals(Object obj) {");
    sb.append("  return this == obj || (getClass().isInstance($1) && proxyInvoker.equals(")
        .append(JavassistProxy.class.getName()).append(".parseInvoker($1)));");
    sb.append("}");
    resultList.add(sb.toString());
}

看懂了吧,跟我们原先的思路是一样的,就是通过这个底层netty交互完拿到结果返回回去。就是改写字节码看起来有点生硬,哈哈,全是字符串代码。

往上追寻proxy 什么时候产生的

com.alipay.sofa.runtime.service.component.ReferenceComponent#createProxy

image.png

com.alipay.sofa.runtime.service.component.ReferenceComponent#activate

image.png

com.alipay.sofa.runtime.component.impl.ComponentManagerImpl#doRegister

com.alipay.sofa.runtime.spring.ServiceBeanFactoryPostProcessor#postProcessBeanFactory

真正改写类是在最后一个类,它继承了BeanFactoryPostProcessor,对所有带上@SofaService、@ SofaReference进行重写,具体里面我没有去细看啦~

总结


到这里对sofa-boot proxy讲解结束了,里面给我们展示了两种方式,一种是直接动态代理,一种是改写字节码,直接将类属性塞成proxy,好家伙,然后还有一个proxyInstance,执行方法的时候通过拦截器走这个代理。