“我正在参加「掘金·启航计划」”
前言
我们在前面几篇文章已经介绍了,sofa-rpc、sofa-bolt、sofa-boot相关知识,接下来我们再深入sofa-boot框架里面一览rpc proxy的面纱,看看框架里面具体是怎么实现代理类的。
在官网有个基础的架构图,这个跟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代理
相当于我们把一个类创建为代理类,然后加上对应的拦截器,当我们在执行的时候进行intercept,有些是打印日志,然后执行回之前的父类方法。
如果让我来设计rpc代理是怎样的呢?其实就是我们注入spring类的时候,一般有个注解对吧,@Resource这些,aop底层通过beanpostprocess这个类,也就是类加载的时候进行改写,我们可以改写之后,参照上面的拦截器方式,我们通过调用netty的通讯,将类+方法+version转换成对应的ip:port,然后请求拿到响应之后回填到这个代理类中,到此我们就完成了这个proxy,下面具体看下源码。
sofa-boot proxy
BindingAdapter
为什么从这个类开始呢?因为我在看源码的时候也在里面绕晕了,如果我们从这个类开始会容易入手,现在开始讲解。
Object inBinding(Object contract, T binding, SofaRuntimeContext sofaRuntimeContext);
在绑定中,在绑定中意味着引用服务
原文注释是这样的,就是我们将注解跟底层的通讯进行绑定,再看下具体的实现类。
JvmBindingAdapter按我理解应该是我们自己实现了一个代理类,就是一个项目里面,我们自己给他实现了代理类,就跟上面cglib代理同理,另一种是RpcBindingAdapter,为了实现sofa-rpc组件用的。
JvmBindingAdapter
看到没有,这就是典型的代理类,关键就是看拦截器内容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
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);
这里面跟上面的动态代理不太一样了,通过字节码的方式去改写类,好家伙~ 默认通过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
com.alipay.sofa.runtime.service.component.ReferenceComponent#activate
com.alipay.sofa.runtime.component.impl.ComponentManagerImpl#doRegister
com.alipay.sofa.runtime.spring.ServiceBeanFactoryPostProcessor#postProcessBeanFactory
真正改写类是在最后一个类,它继承了BeanFactoryPostProcessor,对所有带上@SofaService、@ SofaReference进行重写,具体里面我没有去细看啦~
总结
到这里对sofa-boot proxy讲解结束了,里面给我们展示了两种方式,一种是直接动态代理,一种是改写字节码,直接将类属性塞成proxy,好家伙,然后还有一个proxyInstance,执行方法的时候通过拦截器走这个代理。