利用反射机制实现XML-RPC
在这个部分,主要讨论在先前讨论中所处想的以下问题。我看到XML-RPC协议和这个文章所述的框架的局限性,但我也考虑这些方式的一定先进性。
局限性
XML-RPC是一个简单协议,很明显它不能为代表面向对象系统特色的远程过程调用实现可编程API。特别地,这样一个API不支持以下的一些实现:
继承:XML-RPC没能携带充足的信息决定那种类型可以沿着继承的层级结构传递。在远程过程调用和对象传递参数中都存在这种情况。因此,申明所有的类为final类型是一个好的编程习惯
重载:XML-RPC不允许方法重载。依据这条规则,可以重载那些有原始类型声明的方法,但实际这个选择是不能满足的。当我们需要从方法的声明去推断结构类型是,我们不允许重载。我仅仅允许同一个方法有不同参数个数这种情况除向,因为所有的方法在远程过程调用期间是可用的。我么有以这个方式实现,而是使用了不同的方法名。注意:Web服务在这方面也不提供更多的灵活性。即使灵活性个那个靠的框架Axis也对重载有限制。
集合:XML-RPC不允许结合类型出现。和重载相同的原因,我们必须从被给定的集合类型推断集合中项目的类型,这是不可能的。(JDK1.5之前版本)。取而代之,我们使用数组,可以查询组件类型。虽然,Web服务在远程方法调用方面比XML-RPC更强大,但更多的意见是反对使用集合类型。参看“Web Services Programming Tips and Tricks: Use Collection Types with SOAP and JAX-RPC” Russell Butek and Richard Scheuerle, Jr(2002.4)
Null值:XML-RPC不支持空值。这可能是这个协议中最令人尴尬的瑕疵,因为这个意味着在数组里不能保存空值。在XML-RPC有过关于Null值得提议,但大多数的实现不支持Null值。无需去说,如果通信连接的两边均是与Java程序交互,通过手动方式在消息里加入一些元数据可以克服这个缺点。而且,这意味着滥用协议,不是一个好的建议。
序列化控制
序列化在下面的场景中出现。特别地,文中所提议的框架在发现属性去自动序列化。有时,你可以在序列化中阻止一些属性值得传递。
设想一个Person对象引用多个不同类型的Address对象。特别地,这些Address对象之一是邮件地址,而其他对象在其他的上下文环境中有意义。你可能希望通过Person类的Person.getMailingAddress()方法可以获得邮件地址,这样增强你的Person类。标准的自举机制看到的是一个新的属性-mailingAddress,这个属性在序列化的时候可使用众多的地址列表中初始化。在这种情况下,一个对应的Person.setMailingAddress()方法将执行这样的操作,不管地址序列化的顺序如何,反序列化将返回一个对应的Address类。当然,你的方法应该如何序列化是无关竟要的,但即使你写的方法是正确的,在其程序接口编程的有些人可能不清楚你想的是什么,增加了发生问题的可能性。在任何情况下,你应该容忍两次序列化邮件地址。
但是,这里有个帮助,Introspector可能告诉你,要查找一个类的属性时要去使用反射,而要使用给定的信息。这些信息可以在BeanInfo类里找到,如果你的类名是MyClass,那么你的BeanInfo类应该叫做MyClassBeanInfo。BeanInfo类因该在MyClass类的相同包里,或者在BeanInfo的搜索路径里。搜索路径可以在Introspector里设置。作为一个BeanInfo类,应该提供如下的属性:
代码10 BeanInfo 例子 1
public class MyClassBeanInfo extends SimpleBeanInfo ...{
public PropertyDescriptor[] getPropertyDescriptors() ...{
try ...{
BeanInfo superInfo = Introspector.getBeanInfo(MyClass.class.getSuperclass());
List list = new ArrayList();
for (int i = 0;
i < superInfo.getPropertyDescriptors().length;
i++) ...{
list.add(superInfo.getPropertyDescriptors()[i]);
}
//
list.add(new PropertyDescriptor("myProperty", MyClass.class));
//
return (PropertyDescriptor[])list.toArray(new PropertyDescriptor[list.size()]);
}
catch (IntrospectionException e) ...{
return null;
}
}
}
getPropertyDescriptors()方法必须返回属性描述器所代表的属性。首先,在你的超类里增加这个属性,增加这个你希望发布的这个属性到你的类里,如粗体部分显示。
这是一个严重的缺陷:上面的提议包含了很多固定代码,而这些是编程的时候应尽量避免的。正好,增加的这些被序列化的属性是比罗列显示这些属性能更好的运作。当然,一个方式是使用Introspector通过反射机制调用Introspector.getBeanInfo(MyClass.class, Introspector.IGNORE_ALL_BEANINFO)获得所有的属性。你能增加一个过滤器在你的返回结果时。这种方式看起来象如下展示:
代码11 BeanInfo例子 2
public class MyClassBeanInfo extends SimpleBeanInfo ...{
public PropertyDescriptor[] getPropertyDescriptors() ...{
try ...{
BeanInfo infoByReflection = Introspector.getBeanInfo(MyClass.class, Introspector.IGNORE_ALL_BEANINFO);
PropetyDescriptor allProperies = infoByReflection.getPropertyDescriptors();
return filter(allProperies);
}
catch (IntrospectionException e) ...{
return null;
}
}
protected PropertyDescriptor[] filter(PropertyDescriptor[] props)...{
// Remove properties which must not be exposed }
}
一个好的方法是使用接口定义语言(IDL)构造一个框架,这样允许你手动去产生Bean和扩阿占属性和方法。这个产生器负责提供通过IDL过滤属性的BeanInfo类。继续看一个这样实现的例子。
增加值
当我们隐藏了实际的传送机制,很容易在收到和发送的消息中增加信息。假若我们需要在每个远程方法调用中传送Session信息。这个信息就可以在调用者那里增加上,处理者把它作为第一个参数(包装所有必需的信息成一个适当德Bean)。在其他的调用里,这些信息能从参数Vector里删除,在方法调用里分别处理。在文后的资源引用中找到更多的可用代码,可以更好的使用这个框架。
其他语言
如果你正视弱点,它可能变成支点。XML-RPC的简易导致了上面所描述的限制。然而,XML-RPC已有了多种语言的实现,例如Ruby,Python,或函数性怨言Haskell。不是所有的语言支持面向对象系统中所支持的继承,不是所有的语言支持重载。有些语言,例如Haskell,有灵活的列表类型,从Java语言的角度看,它的这种类型介于数组和列表之间。因此,XML-RPC内在的限制使它适宜于跨语言通信。
当选择XML-RPC作为跨越Java和其他语言的桥梁时,你仍可以使用这个框架,但你仅仅能在与Java通信的一侧使用。而且,可以扩展这个框架去覆盖其他语言。例如,你可以用其他语言重写这个框架,增加对Java接口和数据对象与其他语言对应对象之间转换的支持。另外的方式,我已在上面暗示过,就是写一个编译器把IDL的适当形式转换到其他多种语言的形式,Java是其中之一。在下面,我给出一个例子。
无需任何诉说,这种方式扩展文中所提的框架将是比框架更棘手的事,但它们将协同运行。
删除或替换XML-RPC实现
一个有效率的系统更愿意避免使用XML-RPC中间框架,反而通过把XML-RPC的XML数据直接转换成适当的对象。你可能认为,在潜藏在后面的接口里的抽象方法调用能用多种XML-RPC实现。当我认为不需去做什么事时,那就不能实现这些功能。再者,你是被吸引,而去适应这个框架,以满足你的需要。
远程方法调用
伴随着J2SE1.5,RMI将使用代理机制。将不再需要使用RMI编译器产生桩类(除非你要与一些旧的系统协作)。因此,如果不能夹在一个桩类,那么远程对象的桩就被认为是一个java.lang.reflect.Proxy实例。
接口定义语言
去处要查看大两Bean实现规约和XML-RPC限制的麻烦,正如上面所述,就是避免写接口和Bean。取而代之,适用适当的IDL去创建它们。这样的语言看起来就像下面的代码:
代码12 IDL
module partner;
exception NoPartnerException < 123 : "No partner found" >;
struct Partner ...{
int id;
string name;
int age;
date birthday;
};
interface PartnerHome ...{
Partner getPartner(int id) throws NoPartnerException;
Partner[] findPartner(string name, date bday) throws NoPartnerException;
};
基于IDL编写一个解析器和代码产生器,使得交叉语言通信更加简易。
总结
在这片文章里,展示了如何使用Java反射机制透明地包装经由XML-RPC实现远程方法调用。已经重点展示了已经整合在Proxy类,Array类和Introspector类中的实现机制。基于这些工具类,一个可适用于多种用途的远程方法调用的中间件框架已经实构造出来。
