Dubbo无论是生产端的暴露服务创建的invoker代理还是消费端创建的代理调用,都会需要经过代理。而Dubbo中默认采用javassit代理,动态的在内存当中生成所代理类的字节码,来完成代理的功能。
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper类不能正确处理带$的类名
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
可以看到,javassit的代理工厂类只实现了两个方法,恰恰是上文所说的消费端和生产端两个流程中的代理实现。以getProxy()为例子,也就是生成消费端的代理为例子。
getProxy()需要两个参数,一个是所需要被代理的类invoker,一个是代理类所实现的接口,而这些接口中所定义的方法,就是需要通过被代理达到远程调用生产端的作用。
首先,Dubbo通过getProxy()方法传入所要代理的接口所组成的数组。
public static Proxy getProxy(Class<?>... ics)
{
return getProxy(ClassHelper.getCallerClassLoader(Proxy.class), ics);
}
但是这里可以看到,在生成代理类的字节码之前,仍需要通过得到当前Proxy的类加载器,防止类加载器之间的隔离。
for(int i=0;i<ics.length;i++)
{
String itf = ics[i].getName();
if( !ics[i].isInterface() )
throw new RuntimeException(itf + " is not a interface.");
Class<?> tmp = null;
try
{
tmp = Class.forName(itf, false, cl);
}
catch(ClassNotFoundException e)
{}
if( tmp != ics[i] )
throw new IllegalArgumentException(ics[i] + " is not visible from class loader");
sb.append(itf).append(';');
}
// use interface class name list as key.
String key = sb.toString();
首先,会遍历传入的接口名,并确保传入的都确定是接口,将所有接口名拼接,用来获得所有接口名所组成的字符串,用来保证相同顺序的接口传入以生成字节码之后可以减轻生成代理的压力。
synchronized( ProxyCacheMap )
{
cache = ProxyCacheMap.get(cl);
if( cache == null )
{
cache = new HashMap<String, Object>();
ProxyCacheMap.put(cl, cache);
}
}
Proxy proxy = null;
synchronized( cache )
{
do
{
Object value = cache.get(key);
if( value instanceof Reference<?> )
{
proxy = (Proxy)((Reference<?>)value).get();
if( proxy != null )
return proxy;
}
if( value == PendingGenerationMarker )
{
try{ cache.wait(); }catch(InterruptedException e){}
}
else
{
cache.put(key, PendingGenerationMarker);
break;
}
}
while( true );
}
在Proxy中保存着static类型的map存放着每个类加载器所对应的加载了过的代理类。如果刚才所得到的key在这里能够相应的得到已经生成了的代理类,就不用再在下面生成代理,可以直接在map中取得并返回。
ccp = ClassGenerator.newInstance(cl);
Set<String> worked = new HashSet<String>();
List<Method> methods = new ArrayList<Method>();
接下来,通过类加载器获得新的ClassGenerator,主要目的还是获得类加载器的对象池。
之后分别创建set来保存方法的描述符和list来保存所需要被代理的方法。
for(int i=0;i<ics.length;i++)
{
if( !Modifier.isPublic(ics[i].getModifiers()) )
{
String npkg = ics[i].getPackage().getName();
if( pkg == null )
{
pkg = npkg;
}
else
{
if( !pkg.equals(npkg) )
throw new IllegalArgumentException("non-public interfaces from different packages");
}
}
ccp.addInterface(ics[i]);
for( Method method : ics[i].getMethods() )
{
String desc = ReflectUtils.getDesc(method);
if( worked.contains(desc) )
continue;
worked.add(desc);
int ix = methods.size();
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes();
StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
for(int j=0;j<pts.length;j++)
code.append(" args[").append(j).append("] = ($w)$").append(j+1).append(";");
code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
if( !Void.TYPE.equals(rt) )
code.append(" return ").append(asArgument(rt, "ret")).append(";");
methods.add(method);
ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
}
}
接下来遍历接口,通过获取接口的访问标志符,来判断是不是public方法,否则,将会判断该接口的包名是否与其他接口处于同一个包名下,如果不是,则会抛出异常。
public static boolean isPublic(int mod) {
return (mod & PUBLIC) != 0;
}
接口访问标志符的判断通过位运算得到。
接下来,遍历接口下面的方法。,动态获得方法的描述符。
public static String getDesc(final Method m)
{
StringBuilder ret = new StringBuilder(m.getName()).append('(');
Class<?>[] parameterTypes = m.getParameterTypes();
for(int i=0;i<parameterTypes.length;i++)
ret.append(getDesc(parameterTypes[i]));
ret.append(')').append(getDesc(m.getReturnType()));
return ret.toString();
}
在这里,方法的描述符在这里解析。
例如 void do(int i)的方法,将在这里被解析为do(I)V。
在获得了方法描述符之后保存在set里面。
之后根据方法的参数返回值动态生成被代理的字节码,这里只生成方法体的字节码。
生成如下,假设被代理的方法返回值为boolean类型,参数数量为1,由于在java方法的调用中,会给第一个参数增加this,所以参数的配置从第二个开始:
Object[]args = new Object[1];
args[0]= 下标为1的参数;
Objectret = handler.invoke(this, methods[该方法在代理类中的下标], args);
returnret == null ? false : ((Boolean)ret).booleanValue();
之后会在ClassGenerator中的addMethod()方法加入代理类的字节码。
public ClassGenerator addMethod(String name, int mod, Class<?> rt, Class<?>[] pts, Class<?>[] ets, String body)
{
StringBuilder sb = new StringBuilder();
sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(rt)).append(' ').append(name);
sb.append('(');
for(int i=0;i<pts.length;i++)
{
if( i > 0 )
sb.append(',');
sb.append(ReflectUtils.getName(pts[i]));
sb.append(" arg").append(i);
}
sb.append(')');
if( ets != null && ets.length > 0 )
{
sb.append(" throws ");
for(int i=0;i<ets.length;i++)
{
if( i > 0 )
sb.append(',');
sb.append(ReflectUtils.getName(ets[i]));
}
}
sb.append('{').append(body).append('}');
return addMethod(sb.toString());
}
这里会给方法加入方法的声明与结尾。这里就相对简单,新生成的方法的访问标志符,参数返回值都会直接写入,也会把方法所声明的异常抛出在这里增加在方法的声明上。在实现了方法的声明和方法体的拼接之后,将会把这字符串保存在ClassGenerator的方法数组里。
if( pkg == null )
pkg = PACKAGE_NAME;
// create ProxyInstance class.
String pcn = pkg + ".proxy" + id;
ccp.setClassName(pcn);
ccp.addField("public static java.lang.reflect.Method[] methods;");
ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{ InvocationHandler.class }, new Class<?>[0], "handler=$1;");
ccp.addDefaultConstructor();
Class<?> clazz = ccp.toClass();
clazz.getField("methods").set(null, methods.toArray(new Method[0]));
// create Proxy class.
String fcn = Proxy.class.getName() + id;
ccm = ClassGenerator.newInstance(cl);
ccm.setClassName(fcn);
ccm.addDefaultConstructor();
ccm.setSuperClass(Proxy.class);
ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
Class<?> pc = ccm.toClass();
proxy = (Proxy)pc.newInstance();
在依次对所有的方法完成方法代理之后。将会给新的生成的类命名为报名.proxy + 生成的代理数量的id,这个类也是具体的代理的instance类。之后增加字段属性。
例如用来存放被调用的方法的methods,存放被代理对象的handler。
public static java.lang.reflect.Methods[]methods;
private InvocationHandler handler;
并且创建只有handler的构造方法,之后通过toClass()方法动态得到字节码编译后的class类。
在最后将之前的所有Method通过set保存在methods字段上。
之前的到的类作为Proxy的instance类,而还需要生成Proxy类来在之后的操作中可以通过不同参数来重复获得新的ProxyInstance。
关键要实现newInstance()方法,而这个方法在JavassistProxyFactory中马上将取得到,这个方法会根据传入的参数,通过新的类名的构造方法得到新的proxyInstance实例。
通过ClassGenerator的toClass()方法动态解析成可以使用的classe类,并最后通过newInstance()获得代理的实例。
CtClass ctcs = mSuperClass == null ? null : mPool.get(mSuperClass);
if( mClassName == null )
mClassName = ( mSuperClass == null || javassist.Modifier.isPublic(ctcs.getModifiers())
? ClassGenerator.class.getName() : mSuperClass + "$sc" ) + id;
mCtc = mPool.makeClass(mClassName);
if( mSuperClass != null )
mCtc.setSuperclass(ctcs);
mCtc.addInterface(mPool.get(DC.class.getName())); // add dynamic class tag.
if( mInterfaces != null )
for( String cl : mInterfaces ) mCtc.addInterface(mPool.get(cl));
if( mFields != null )
for( String code : mFields ) mCtc.addField(CtField.make(code, mCtc));
if( mMethods != null )
{
for( String code : mMethods )
{
if( code.charAt(0) == ':' )
mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(1))), code.substring(1, code.indexOf('(')), mCtc, null));
else
mCtc.addMethod(CtNewMethod.make(code, mCtc));
}
}
if( mDefaultConstructor )
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
if( mConstructors != null )
{
for( String code : mConstructors )
{
if( code.charAt(0) == ':' )
{
mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(1))), mCtc, null));
}
else
{
String[] sn = mCtc.getSimpleName().split("\\$+"); // inner class name include $.
mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length-1]), mCtc));
}
}
}
return mCtc.toClass(ClassHelper.getCallerClassLoader(getClass()), null);
在toClass()方法中,将之前得到的所有类配置得到,按照javassist的要求配置在javassist的api ctclass类当中,依次配置超类,接口,字段,方法,构造方法在ctClass当中 ,最后通过toClass()方法动态获得动态通过字节码生成的代理类。
把目光回到javassistProxyFactory,在的得到了新的Proxy之后,通过newInstance()方法得到了新的proxyInstance,而这个方法,也是在刚才动态生成的。这样既可通过javassist得到了新的代理对象。在消费者方法调用,实则会调用代理的相应的方法,而直接调用handler的方法,达到代理的目的。