JGroups(2.6.9-GA)使用RpcDispatcher来调用callRemoteMethods等类似的一系列函数来实现远程调用,并且推荐使用MethodCall作为远程调用的函数信息,MethodCall类包装了包括函数名,函数参数及其类型等信息。MethodCall类部分代码如下:public class MethodCall implements Externalizable { private static final long serialVersionUID=7873471327078957662L; /** The name of the method, case sensitive. */ protected String method_name; /** The ID of a method, maps to a java.lang.reflect.Method */ protected short method_id=-1; /** The arguments of the method. */ protected Object[] args; /** The class types, e.g., new Class[]{String.class, int.class}. */ protected Class[] types; /** The signature, e.g., new String[]{String.class.getName(), int.class.getName()}. */ protected String[] signature; /** The Method of the call. */ protected Method method; /** To carry arbitrary data with a method call, data needs to be serializable if sent across the wire */ protected Map payload; protected static final Log log=LogFactory.getLog(MethodCall.class); /** Which mode to use. */ protected short mode=OLD; /** Infer the method from the arguments. */ protected static final short OLD=1; /** Explicitly ship the method, caller has to determine method himself. */ protected static final short METHOD=2; /** Use class information. */ protected static final short TYPES=3; /** Provide a signature, similar to JMX. */ protected static final short SIGNATURE=4; /** Use an ID to map to a method */ protected static final short ID=5; //other ops.... }
RPC调用端通过序列化MethodCall来传输MethodCall实例,远程的服务器端通过反序列化得到MethodCall实例,之后利用反射技术在预先设定的ServerObject类上查找匹配的函数并利用反射技术调用该方法得到返回结果。
JGroups查找匹配的函数时提供了多种选择,并且通过MethodCall的属性mode设定。这些模式包括: switch(mode) { case OLD: meth=findMethod(cl); break; case METHOD: if(this.method != null) meth=this.method; break; case TYPES: //meth=cl.getDeclaredMethod(method_name, types); meth = getMethod(cl, method_name, types); break; case SIGNATURE: Class[] mytypes=null; if(signature != null) mytypes=getTypesFromString(cl, signature); //meth=cl.getDeclaredMethod(method_name, mytypes); meth = getMethod(cl, method_name, mytypes); break; case ID: break; default: if(log.isErrorEnabled()) log.error("mode " + mode + " is invalid"); break; }
说明:1.OLD模式:检查函数名与函数参数个数;
2.METHOD模式:此模式同ID模式,先通过ID模式找到Method,并设置MethodCall的method属性;
3.TYPES模式:检查函数名,函数参数个数,并且通过Class.isAssignableFrom(Class clazz)判断是否是匹配类型。
4.ID模式:通过函数ID来查找匹配的函数。
默认的查找方式是OLD,其实现方式是: Method findMethod(Class target_class) throws Exception { int len=args != null? args.length : 0; Method m; Method[] methods=getAllMethods(target_class); for(int i=0; i < methods.length; i++) { m=methods[i]; if(m.getName().equals(method_name)) { if(m.getParameterTypes().length == len) return m; } } return null; } ,也就是说只检查函数名以及参数个数。这就会导致匹配出现问题。
函数查找模式的设定是通过不同的构造函数类确定的。如: public MethodCall(String method_name, Object[] args) { this.method_name=method_name; this.mode=OLD;//默认模式 this.args=args; } public MethodCall(short method_id, Object[] args) { this.method_id=method_id; this.mode=ID;//ID模式,通过函数ID查找? this.args=args; } public MethodCall(String method_name, Object[] args, Class[] types) { this.method_name=method_name; this.args=args; this.types=types; this.mode=TYPES;//参数类型 } public MethodCall(String method_name, Object[] args, String[] signature) { this.method_name=method_name; this.args=args; this.signature=signature; this.mode=SIGNATURE;//函数签名 }
如果我们在使用MethodCall时并没有设定函数查找模式而使用默认的模式时,那么我们的远程的ServerObject类就不能有参数个数相同而参数类型不同的重载函数。如果出现这种情况的话,MethodCall会找到最先“匹配”到的函数,而未必是我们想调用的函数。如下面两个函数:
public boolean remove(Object id){//body} public boolean remove(List ids){//body} ,调用Object[] args = new Object[]{256L}; Class[] types = new Class[]{Long.class}; MethodCall methodCall = new MethodCall("remove",args,types); RpcDispatcher.callRemoteMethod(dest, methodCall, GroupRequest.GET_FIRST, 20000); 并不能确定能调用ServerObject的public boolean remove(Object id){//body} 方法。因为它使用的TYPES的查找模式。这种模式检查函数名,参数个数之后用如下的语句判断函数参数类型是否匹配。那么任何Object的子类都是可以复制给万能的Object类型的。 if(!parameters[j].isAssignableFrom(types[j])) { // if (!types[j].equals(parameters[j])) { continue methods; }
解决办法:1.设定函数匹配模式。
2.对与OLD模式,ServerObject中避免出现函数参数个数相同而类型不同的函数重载。
3.对于TYPES模式,ServerObject中避免出现函数的参数个数相同,类型不同但是有“父子”关系的函数重载。
4.对于SIGNATURE模式,显示通过signature获取参数类型,进而又用TYPES模式去匹配,此模式下同TYPES模式。
5.对于ID模式,需要同步两边的函数ID?。。。
代价最小的修改应该就是通过不同的构造函数去设定函数模式。经上面的讨论在JGroups-2.6.9-GA中就没有精确匹配的办法,除非自己改造ServerObject的函数,不能出现1,2,3,4中的情况。但是此方法代价太大,涉及到应用逻辑部分代码都需要修改。还有就是降低版本了,使用2.6.1版本的TYPES模式是可行的,其他的待考证。升级不靠谱,至少2.9.0以及2.10.0两个版本的存在同样的问题,其他的待考证。