在交易系统的C/S体系中,C只负责数据的输入和显示,相当于MVC中的View部分,S负责数据的操作和持久化,两者是通过WebService进行联系的,具体来说联系的方式是这样:C端将指定S端负责处理的Service类名,具体负责处理的函数名和函数的参数打包成一个XML传送到S端,S端解析后通过反射找到具体的函数进行处理,处理的结果会转化成XML形式的字符串传回。这就是设计梗概一中提到的内容。
如果这个过程交给负责具体业务的程序员自行完成的话,那无疑会给系统带来许多混乱和无序,程序员也无法将主要精力集中在业务上;另外,他们也无需了解每个细节是怎么完成的,他们真正需要的是框架提供好的接口,知道怎么调用取得结果就可以了。他们希望最好能像调用普通函数一样调用S中的方法并取得想要的结果,举例来说,如果客户端需要查询姓名以H开头的所有雇员,他们只需调用一个search函数就能得到查询出来的雇员集合,中间发生的组装请求XML,WebService调用,业务处理,从数据库查询数据,将数据转为XML传回和在客户端解析传回的XML再变成领域对象集合等都应该由框架来完成。这并不过分,而是很合理的需求,就像RMI曾经就是这样做的。
那么,客户端与服务器端的交互会有几种形式呢,从业务上来说无外乎下面五种形式:
1.调用S端的一个函数,只想知道这个函数是否正确运行了。典型例子如对象的删除操作。
2.调用S端的一个函数,想得到函数执行后返回的一个对象。典型例子如对象的添加操作,用户需要取回添加好的对象的ID。
3.调用S端的一个函数,想得到返回对象的集合列表。典型例子如对象的查询。
4.调用S端的一个函数,想得到分页后的某一页对象集合。典型例子如分页查询。
5.调用S端的一个函数,只想得到一个字符串。典型例子如改变一种商品的目录,得到某种商品的介绍文字等。
框架需要做的,就是把这五种形式做成通用的函数提供给负责业务的程序员,让他们仅需要这五个函数就能完成与WebService服务器的交互。这五种形式以第二种最为典型,也最为基础,完成了它其它的就可以依样画葫芦,下面请看具体过程:
首先,规定具体函数的形制为
public static BaseDomainObj fetchObject(String url,String serviceName,String mothodName,String[] args,Class<?> cls);
公有静态自不必说,BaseDomainObj是客户端领域对象的基类,fetchObject是函数名,接下来是四个参数,前三个分别是WebService所在URL,服务器上服务类注册在Spring上下文中的beanName,服务类具体的方法名,最后一个是取得对象的类型,在函数体中,会根据类型用反射生成一个实例,再通过实例的FromXML方法给实例的属性赋值,完成后就得到了负责业务的程序员想要的结果。
其次,fetchObject内部需要做的事情有:
1.将serviceName,mothodName,args三项组合成一段XML文本。此项工作由WSRequest类完成。
2.向位于url上的WebService服务器端发起请求,获得返回的文本。此项工作由WSInvoker类来完成。
3.将返回的文本转化出来,这一步是要检测服务器端函数执行是否顺畅,有无抛出异常等。因为服务器端如果发生异常是无法通过WebService传回的,只能变成文本后回传,那么客户端就需要解析一次,有问题就报出来,没问题再往下走。这一步是坚决不能忽视的。
4.通过反射得到对象,此时对象的属性还是原始状态,需要再通过反射注入相应的值,最后,客户端需要的对象就产生了。
具体的过程请参考下面的代码:
/**
* 调用远程WebService端,将返回的XML转化为一个对象,最终返回.这种调用的典型例子是getById,add等
* @param url WebService所在URL
* @param serviceName 服务名,此名在appCtx.xml中定义
* @param mothodName 方法名
* @param args 方法的参数
* @param cls 要返回的对象类型
* @return
*/
public static BaseDomainObj fetchObject(String url,String serviceName,String mothodName,String[] args,Class<?> cls){
// 得到客户端请求XML文本
WSRequest request=new WSRequest(serviceName,mothodName,args);
String requestXML=request.toXML();
logger.info("准备向位于'"+url+"'发送的请求XML为'"+requestXML+"'.");
try{
// 调用远端WebService上的方法,得到返回的XML文本
WSInvoker invoker=new WSInvoker(url);
String responseXML=invoker.getResponseXML(requestXML);
logger.info("得到位于'"+url+"'响应XML为'"+responseXML+"'.");
// 转化响应
WSResponse response=new WSResponse(responseXML);
logger.info(response);
// 如果在调用过程中如通不过检测而被中断的话
if(response.isBreaked()){
String errTxt="远程方法被中断,具体原因是"+response.getRemark();
logger.error(errTxt);
throw new WSBreakException(errTxt+".(WSE05)");
}
// 如果在调用过程中出现异常的话
if(response.hasException()){
String errTxt="调用远程方法返回了异常,具体信息是"+response.getRemark();
logger.error(errTxt);
throw new WSException(errTxt+".(WSE04)");
}
try{
// 通过反射得到对象
BaseDomainObj obj= (BaseDomainObj)cls.newInstance();
// 通过反射得到方法
Method method = cls.getMethod("fromXML", new Class[] {String.class});
// 通过反射调用对象的方法
method.invoke(obj, new Object[] {response.getMethodResonseXML()});
return obj;
}
catch(Exception ex){
String errTxt="无法将"+response.getMethodResonseXML()+"转化为"+cls.getName()+"对象.(WSE06)";
logger.error(errTxt);
throw new WSException(errTxt);
}
}
catch(MalformedURLException e){
String errTxt="无法调用'"+url+"'上的服务,因为它是畸形的.(WSE01)";
logger.error(errTxt);
throw new WSException(errTxt);
}
catch(XFireRuntimeException e){
String errTxt="无法调用'"+url+"'上的服务.(WSE02)";
logger.error(errTxt);
throw new WSException(errTxt);
}
catch(DocumentException e){
String errTxt="无法解析从服务器端'"+url+"'返回的XML文本.(WSE03)";
logger.error(errTxt);
throw new WSException(errTxt);
}
}
我们再看看通过关键的注入属性值的fromXML函数做了些什么:
public void fromXML(String xml) throws DocumentException{
Document doc=DocumentHelper.parseText(xml);
Element root=doc.getRootElement();
List<Element> elms=root.elements();
for(Element elm:elms){
try {
// 借助于BeanUtils,给对象的属性赋值
BeanUtils.setProperty(this,elm.getName(),elm.getText());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
最后,我们可以看看负责业务的程序员需要书写的代码示例:
public Tmp add(String name,String age,String salary,String picture){
String url=CommonUtil.WebService_Url;
String serviceName="TmpService";
String methodName="add";
String[] args=new String[]{name,age,salary,picture};
try{
return (Tmp)WSUtil.fetchObject(url, serviceName, methodName, args, Tmp.class);
}
catch(WSBreakException ex){
DlgUtil.popupWarningDialog(ex.getMessage());
}
catch(WSException ex){
DlgUtil.popupErrorDialog(ex.getMessage());
}
return null;
}
小结:
一.负责业务程序员不需要了解的细节,框架应该将它们隐藏起来。
二.负责业务程序员需要了解的接口,框架应该使它们尽量简单。
三.框架能做到的,就不该再让负责业务的程序员再重复的发明车轮。