在交易系統的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;
}
小結:
一.負責業務程序員不需要了解的細節,框架應該將它們隱藏起來。
二.負責業務程序員需要了解的接口,框架應該使它們盡量簡單。
三.框架能做到的,就不該再讓負責業務的程序員再重復的發明車輪。