在交易系統(tǒng)的C/S體系中,C只負(fù)責(zé)數(shù)據(jù)的輸入和顯示,相當(dāng)于MVC中的View部分,S負(fù)責(zé)數(shù)據(jù)的操作和持久化,兩者是通過(guò)WebService進(jìn)行聯(lián)系的,具體來(lái)說(shuō)聯(lián)系的方式是這樣:C端將指定S端負(fù)責(zé)處理的Service類(lèi)名,具體負(fù)責(zé)處理的函數(shù)名和函數(shù)的參數(shù)打包成一個(gè)XML傳送到S端,S端解析后通過(guò)反射找到具體的函數(shù)進(jìn)行處理,處理的結(jié)果會(huì)轉(zhuǎn)化成XML形式的字符串傳回。這就是設(shè)計(jì)梗概一中提到的內(nèi)容。
如果這個(gè)過(guò)程交給負(fù)責(zé)具體業(yè)務(wù)的程序員自行完成的話(huà),那無(wú)疑會(huì)給系統(tǒng)帶來(lái)許多混亂和無(wú)序,程序員也無(wú)法將主要精力集中在業(yè)務(wù)上;另外,他們也無(wú)需了解每個(gè)細(xì)節(jié)是怎么完成的,他們真正需要的是框架提供好的接口,知道怎么調(diào)用取得結(jié)果就可以了。他們希望最好能像調(diào)用普通函數(shù)一樣調(diào)用S中的方法并取得想要的結(jié)果,舉例來(lái)說(shuō),如果客戶(hù)端需要查詢(xún)姓名以H開(kāi)頭的所有雇員,他們只需調(diào)用一個(gè)search函數(shù)就能得到查詢(xún)出來(lái)的雇員集合,中間發(fā)生的組裝請(qǐng)求XML,WebService調(diào)用,業(yè)務(wù)處理,從數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)為XML傳回和在客戶(hù)端解析傳回的XML再變成領(lǐng)域?qū)ο蠹系榷紤?yīng)該由框架來(lái)完成。這并不過(guò)分,而是很合理的需求,就像RMI曾經(jīng)就是這樣做的。
那么,客戶(hù)端與服務(wù)器端的交互會(huì)有幾種形式呢,從業(yè)務(wù)上來(lái)說(shuō)無(wú)外乎下面五種形式:
1.調(diào)用S端的一個(gè)函數(shù),只想知道這個(gè)函數(shù)是否正確運(yùn)行了。典型例子如對(duì)象的刪除操作。
2.調(diào)用S端的一個(gè)函數(shù),想得到函數(shù)執(zhí)行后返回的一個(gè)對(duì)象。典型例子如對(duì)象的添加操作,用戶(hù)需要取回添加好的對(duì)象的ID。
3.調(diào)用S端的一個(gè)函數(shù),想得到返回對(duì)象的集合列表。典型例子如對(duì)象的查詢(xún)。
4.調(diào)用S端的一個(gè)函數(shù),想得到分頁(yè)后的某一頁(yè)對(duì)象集合。典型例子如分頁(yè)查詢(xún)。
5.調(diào)用S端的一個(gè)函數(shù),只想得到一個(gè)字符串。典型例子如改變一種商品的目錄,得到某種商品的介紹文字等。
框架需要做的,就是把這五種形式做成通用的函數(shù)提供給負(fù)責(zé)業(yè)務(wù)的程序員,讓他們僅需要這五個(gè)函數(shù)就能完成與WebService服務(wù)器的交互。這五種形式以第二種最為典型,也最為基礎(chǔ),完成了它其它的就可以依樣畫(huà)葫蘆,下面請(qǐng)看具體過(guò)程:
首先,規(guī)定具體函數(shù)的形制為
public static BaseDomainObj fetchObject(String url,String serviceName,String mothodName,String[] args,Class<?> cls);
公有靜態(tài)自不必說(shuō),BaseDomainObj是客戶(hù)端領(lǐng)域?qū)ο蟮幕?lèi),fetchObject是函數(shù)名,接下來(lái)是四個(gè)參數(shù),前三個(gè)分別是WebService所在URL,服務(wù)器上服務(wù)類(lèi)注冊(cè)在Spring上下文中的beanName,服務(wù)類(lèi)具體的方法名,最后一個(gè)是取得對(duì)象的類(lèi)型,在函數(shù)體中,會(huì)根據(jù)類(lèi)型用反射生成一個(gè)實(shí)例,再通過(guò)實(shí)例的FromXML方法給實(shí)例的屬性賦值,完成后就得到了負(fù)責(zé)業(yè)務(wù)的程序員想要的結(jié)果。
其次,fetchObject內(nèi)部需要做的事情有:
1.將serviceName,mothodName,args三項(xiàng)組合成一段XML文本。此項(xiàng)工作由WSRequest類(lèi)完成。
2.向位于url上的WebService服務(wù)器端發(fā)起請(qǐng)求,獲得返回的文本。此項(xiàng)工作由WSInvoker類(lèi)來(lái)完成。
3.將返回的文本轉(zhuǎn)化出來(lái),這一步是要檢測(cè)服務(wù)器端函數(shù)執(zhí)行是否順暢,有無(wú)拋出異常等。因?yàn)榉?wù)器端如果發(fā)生異常是無(wú)法通過(guò)WebService傳回的,只能變成文本后回傳,那么客戶(hù)端就需要解析一次,有問(wèn)題就報(bào)出來(lái),沒(méi)問(wèn)題再往下走。這一步是堅(jiān)決不能忽視的。
4.通過(guò)反射得到對(duì)象,此時(shí)對(duì)象的屬性還是原始狀態(tài),需要再通過(guò)反射注入相應(yīng)的值,最后,客戶(hù)端需要的對(duì)象就產(chǎn)生了。
具體的過(guò)程請(qǐng)參考下面的代碼:
我們?cè)倏纯赐ㄟ^(guò)關(guān)鍵的注入屬性值的fromXML函數(shù)做了些什么:
最后,我們可以看看負(fù)責(zé)業(yè)務(wù)的程序員需要書(shū)寫(xiě)的代碼示例:
小結(jié):
一.負(fù)責(zé)業(yè)務(wù)程序員不需要了解的細(xì)節(jié),框架應(yīng)該將它們隱藏起來(lái)。
二.負(fù)責(zé)業(yè)務(wù)程序員需要了解的接口,框架應(yīng)該使它們盡量簡(jiǎn)單。
三.框架能做到的,就不該再讓負(fù)責(zé)業(yè)務(wù)的程序員再重復(fù)的發(fā)明車(chē)輪。
如果這個(gè)過(guò)程交給負(fù)責(zé)具體業(yè)務(wù)的程序員自行完成的話(huà),那無(wú)疑會(huì)給系統(tǒng)帶來(lái)許多混亂和無(wú)序,程序員也無(wú)法將主要精力集中在業(yè)務(wù)上;另外,他們也無(wú)需了解每個(gè)細(xì)節(jié)是怎么完成的,他們真正需要的是框架提供好的接口,知道怎么調(diào)用取得結(jié)果就可以了。他們希望最好能像調(diào)用普通函數(shù)一樣調(diào)用S中的方法并取得想要的結(jié)果,舉例來(lái)說(shuō),如果客戶(hù)端需要查詢(xún)姓名以H開(kāi)頭的所有雇員,他們只需調(diào)用一個(gè)search函數(shù)就能得到查詢(xún)出來(lái)的雇員集合,中間發(fā)生的組裝請(qǐng)求XML,WebService調(diào)用,業(yè)務(wù)處理,從數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)為XML傳回和在客戶(hù)端解析傳回的XML再變成領(lǐng)域?qū)ο蠹系榷紤?yīng)該由框架來(lái)完成。這并不過(guò)分,而是很合理的需求,就像RMI曾經(jīng)就是這樣做的。
那么,客戶(hù)端與服務(wù)器端的交互會(huì)有幾種形式呢,從業(yè)務(wù)上來(lái)說(shuō)無(wú)外乎下面五種形式:
1.調(diào)用S端的一個(gè)函數(shù),只想知道這個(gè)函數(shù)是否正確運(yùn)行了。典型例子如對(duì)象的刪除操作。
2.調(diào)用S端的一個(gè)函數(shù),想得到函數(shù)執(zhí)行后返回的一個(gè)對(duì)象。典型例子如對(duì)象的添加操作,用戶(hù)需要取回添加好的對(duì)象的ID。
3.調(diào)用S端的一個(gè)函數(shù),想得到返回對(duì)象的集合列表。典型例子如對(duì)象的查詢(xún)。
4.調(diào)用S端的一個(gè)函數(shù),想得到分頁(yè)后的某一頁(yè)對(duì)象集合。典型例子如分頁(yè)查詢(xún)。
5.調(diào)用S端的一個(gè)函數(shù),只想得到一個(gè)字符串。典型例子如改變一種商品的目錄,得到某種商品的介紹文字等。
框架需要做的,就是把這五種形式做成通用的函數(shù)提供給負(fù)責(zé)業(yè)務(wù)的程序員,讓他們僅需要這五個(gè)函數(shù)就能完成與WebService服務(wù)器的交互。這五種形式以第二種最為典型,也最為基礎(chǔ),完成了它其它的就可以依樣畫(huà)葫蘆,下面請(qǐng)看具體過(guò)程:
首先,規(guī)定具體函數(shù)的形制為
public static BaseDomainObj fetchObject(String url,String serviceName,String mothodName,String[] args,Class<?> cls);
公有靜態(tài)自不必說(shuō),BaseDomainObj是客戶(hù)端領(lǐng)域?qū)ο蟮幕?lèi),fetchObject是函數(shù)名,接下來(lái)是四個(gè)參數(shù),前三個(gè)分別是WebService所在URL,服務(wù)器上服務(wù)類(lèi)注冊(cè)在Spring上下文中的beanName,服務(wù)類(lèi)具體的方法名,最后一個(gè)是取得對(duì)象的類(lèi)型,在函數(shù)體中,會(huì)根據(jù)類(lèi)型用反射生成一個(gè)實(shí)例,再通過(guò)實(shí)例的FromXML方法給實(shí)例的屬性賦值,完成后就得到了負(fù)責(zé)業(yè)務(wù)的程序員想要的結(jié)果。
其次,fetchObject內(nèi)部需要做的事情有:
1.將serviceName,mothodName,args三項(xiàng)組合成一段XML文本。此項(xiàng)工作由WSRequest類(lèi)完成。
2.向位于url上的WebService服務(wù)器端發(fā)起請(qǐng)求,獲得返回的文本。此項(xiàng)工作由WSInvoker類(lèi)來(lái)完成。
3.將返回的文本轉(zhuǎn)化出來(lái),這一步是要檢測(cè)服務(wù)器端函數(shù)執(zhí)行是否順暢,有無(wú)拋出異常等。因?yàn)榉?wù)器端如果發(fā)生異常是無(wú)法通過(guò)WebService傳回的,只能變成文本后回傳,那么客戶(hù)端就需要解析一次,有問(wèn)題就報(bào)出來(lái),沒(méi)問(wèn)題再往下走。這一步是堅(jiān)決不能忽視的。
4.通過(guò)反射得到對(duì)象,此時(shí)對(duì)象的屬性還是原始狀態(tài),需要再通過(guò)反射注入相應(yīng)的值,最后,客戶(hù)端需要的對(duì)象就產(chǎn)生了。
具體的過(guò)程請(qǐng)參考下面的代碼:
/**
* 調(diào)用遠(yuǎn)程WebService端,將返回的XML轉(zhuǎn)化為一個(gè)對(duì)象,最終返回.這種調(diào)用的典型例子是getById,add等
* @param url WebService所在URL
* @param serviceName 服務(wù)名,此名在appCtx.xml中定義
* @param mothodName 方法名
* @param args 方法的參數(shù)
* @param cls 要返回的對(duì)象類(lèi)型
* @return
*/
public static BaseDomainObj fetchObject(String url,String serviceName,String mothodName,String[] args,Class<?> cls){
// 得到客戶(hù)端請(qǐng)求XML文本
WSRequest request=new WSRequest(serviceName,mothodName,args);
String requestXML=request.toXML();
logger.info("準(zhǔn)備向位于'"+url+"'發(fā)送的請(qǐng)求XML為'"+requestXML+"'.");
try{
// 調(diào)用遠(yuǎn)端WebService上的方法,得到返回的XML文本
WSInvoker invoker=new WSInvoker(url);
String responseXML=invoker.getResponseXML(requestXML);
logger.info("得到位于'"+url+"'響應(yīng)XML為'"+responseXML+"'.");
// 轉(zhuǎn)化響應(yīng)
WSResponse response=new WSResponse(responseXML);
logger.info(response);
// 如果在調(diào)用過(guò)程中如通不過(guò)檢測(cè)而被中斷的話(huà)
if(response.isBreaked()){
String errTxt="遠(yuǎn)程方法被中斷,具體原因是"+response.getRemark();
logger.error(errTxt);
throw new WSBreakException(errTxt+".(WSE05)");
}
// 如果在調(diào)用過(guò)程中出現(xiàn)異常的話(huà)
if(response.hasException()){
String errTxt="調(diào)用遠(yuǎn)程方法返回了異常,具體信息是"+response.getRemark();
logger.error(errTxt);
throw new WSException(errTxt+".(WSE04)");
}
try{
// 通過(guò)反射得到對(duì)象
BaseDomainObj obj= (BaseDomainObj)cls.newInstance();
// 通過(guò)反射得到方法
Method method = cls.getMethod("fromXML", new Class[] {String.class});
// 通過(guò)反射調(diào)用對(duì)象的方法
method.invoke(obj, new Object[] {response.getMethodResonseXML()});
return obj;
}
catch(Exception ex){
String errTxt="無(wú)法將"+response.getMethodResonseXML()+"轉(zhuǎn)化為"+cls.getName()+"對(duì)象.(WSE06)";
logger.error(errTxt);
throw new WSException(errTxt);
}
}
catch(MalformedURLException e){
String errTxt="無(wú)法調(diào)用'"+url+"'上的服務(wù),因?yàn)樗腔蔚?(WSE01)";
logger.error(errTxt);
throw new WSException(errTxt);
}
catch(XFireRuntimeException e){
String errTxt="無(wú)法調(diào)用'"+url+"'上的服務(wù).(WSE02)";
logger.error(errTxt);
throw new WSException(errTxt);
}
catch(DocumentException e){
String errTxt="無(wú)法解析從服務(wù)器端'"+url+"'返回的XML文本.(WSE03)";
logger.error(errTxt);
throw new WSException(errTxt);
}
}
* 調(diào)用遠(yuǎn)程WebService端,將返回的XML轉(zhuǎn)化為一個(gè)對(duì)象,最終返回.這種調(diào)用的典型例子是getById,add等
* @param url WebService所在URL
* @param serviceName 服務(wù)名,此名在appCtx.xml中定義
* @param mothodName 方法名
* @param args 方法的參數(shù)
* @param cls 要返回的對(duì)象類(lèi)型
* @return
*/
public static BaseDomainObj fetchObject(String url,String serviceName,String mothodName,String[] args,Class<?> cls){
// 得到客戶(hù)端請(qǐng)求XML文本
WSRequest request=new WSRequest(serviceName,mothodName,args);
String requestXML=request.toXML();
logger.info("準(zhǔn)備向位于'"+url+"'發(fā)送的請(qǐng)求XML為'"+requestXML+"'.");
try{
// 調(diào)用遠(yuǎn)端WebService上的方法,得到返回的XML文本
WSInvoker invoker=new WSInvoker(url);
String responseXML=invoker.getResponseXML(requestXML);
logger.info("得到位于'"+url+"'響應(yīng)XML為'"+responseXML+"'.");
// 轉(zhuǎn)化響應(yīng)
WSResponse response=new WSResponse(responseXML);
logger.info(response);
// 如果在調(diào)用過(guò)程中如通不過(guò)檢測(cè)而被中斷的話(huà)
if(response.isBreaked()){
String errTxt="遠(yuǎn)程方法被中斷,具體原因是"+response.getRemark();
logger.error(errTxt);
throw new WSBreakException(errTxt+".(WSE05)");
}
// 如果在調(diào)用過(guò)程中出現(xiàn)異常的話(huà)
if(response.hasException()){
String errTxt="調(diào)用遠(yuǎn)程方法返回了異常,具體信息是"+response.getRemark();
logger.error(errTxt);
throw new WSException(errTxt+".(WSE04)");
}
try{
// 通過(guò)反射得到對(duì)象
BaseDomainObj obj= (BaseDomainObj)cls.newInstance();
// 通過(guò)反射得到方法
Method method = cls.getMethod("fromXML", new Class[] {String.class});
// 通過(guò)反射調(diào)用對(duì)象的方法
method.invoke(obj, new Object[] {response.getMethodResonseXML()});
return obj;
}
catch(Exception ex){
String errTxt="無(wú)法將"+response.getMethodResonseXML()+"轉(zhuǎn)化為"+cls.getName()+"對(duì)象.(WSE06)";
logger.error(errTxt);
throw new WSException(errTxt);
}
}
catch(MalformedURLException e){
String errTxt="無(wú)法調(diào)用'"+url+"'上的服務(wù),因?yàn)樗腔蔚?(WSE01)";
logger.error(errTxt);
throw new WSException(errTxt);
}
catch(XFireRuntimeException e){
String errTxt="無(wú)法調(diào)用'"+url+"'上的服務(wù).(WSE02)";
logger.error(errTxt);
throw new WSException(errTxt);
}
catch(DocumentException e){
String errTxt="無(wú)法解析從服務(wù)器端'"+url+"'返回的XML文本.(WSE03)";
logger.error(errTxt);
throw new WSException(errTxt);
}
}
我們?cè)倏纯赐ㄟ^(guò)關(guān)鍵的注入屬性值的fromXML函數(shù)做了些什么:
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,給對(duì)象的屬性賦值
BeanUtils.setProperty(this,elm.getName(),elm.getText());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Document doc=DocumentHelper.parseText(xml);
Element root=doc.getRootElement();
List<Element> elms=root.elements();
for(Element elm:elms){
try {
// 借助于BeanUtils,給對(duì)象的屬性賦值
BeanUtils.setProperty(this,elm.getName(),elm.getText());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
最后,我們可以看看負(fù)責(zé)業(yè)務(wù)的程序員需要書(shū)寫(xiě)的代碼示例:
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;
}
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;
}
小結(jié):
一.負(fù)責(zé)業(yè)務(wù)程序員不需要了解的細(xì)節(jié),框架應(yīng)該將它們隱藏起來(lái)。
二.負(fù)責(zé)業(yè)務(wù)程序員需要了解的接口,框架應(yīng)該使它們盡量簡(jiǎn)單。
三.框架能做到的,就不該再讓負(fù)責(zé)業(yè)務(wù)的程序員再重復(fù)的發(fā)明車(chē)輪。