當柳上原的風吹向天際的時候...

          真正的快樂來源于創造

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            368 Posts :: 1 Stories :: 201 Comments :: 0 Trackbacks
          一.C/S兩端的任務分離
             考慮到便于信息接收傳遞顯示的因素,交易系統和QQ類似,采用了傳統的C/S模式而不是B/S模式。Client端主要負責取得用戶輸入和數據顯示,而Server端分為DBServer和MsgServer兩個,前者負責數據的持久化,后者負責消息的傳遞。撇開消息服務器MsgServer不談的話,數據傳遞主要發生在Client和DBServer之間。
             
          二.C/S兩端的交互方式
             由于C端只負責數據的輸入和顯示,它必然需要向DBServer端存取數據,這就有一個信息載體和交互方式的問題。C端需要向DbServer(以下簡稱DS)傳遞的信息是多種多樣的,簡單命令行形式的數據肯定不行,類似JSON的線性形式不夠表現樹狀數據,只有XML才有豐富的表現能力,它無論是簡單的線性數據還是復雜的樹狀數據都能容納,有了dom4j或是jdom的幫助,解析起來也很方便。交互方式上,由于C可能在廣域網中,還可能有防火墻的阻擋,這樣Socket長連接就受到一定程度的限制,要是采用WebService問題就解決了,因為WebService的底層協議還是http,也走80端口,不會被防火墻阻擋,這樣,DBServer就成了一臺放置在公網上的WebService服務器,為各個Client提供Webservice服務。

          三.實現WebService的軟件選擇
             備選有Axis1/2和XFire兩種方案,選擇的依據主要是效率。通過一段時間的使用,發現XFire的效率確實比Axis1/2高,估測同等調用只占后者的一半左右。其它的易用性,穩定性等沒有成為選擇依據,因為如果XFire還不行再換其它的軟件也來得及,下面的設計保證了系統不會依賴于特定的WebService端軟件。

          四.WebSevice端的對外接口設計

          WebService的對外端口一般是由一個接口和一個實現類組成,實現類中的函數是具體實現,接口是調用者和實現者共同遵守的規約;一般來說如果客戶端需要一個函數的話,那么服務器端的接口類要定義這個函數,實現類實現這個函數。這樣的方式在交互簡單,數據量小的時侯沒有問題,且使用很方便,但量變引起質變,如果交互復雜,需要的函數眾多,數據量與日俱增的話,問題就來了。其一,這回導致接口類和實現類函數越來越多,體積越來越大,對定位維護修改帶來很大的不變;其二,接口類和實現類常會被修改,而開發人員之間的協同等待甚至沖突就日益增多起來,阻滯了開發效率;其三,也是最重要的,系統的可擴展性缺乏,難以動態維護,即使增加多個服務器分擔負載,也需要手動修改大量的代碼。因此,這種傳統的方式在Demo版過后就被放棄了。
          新的方式采用的單接口設計,即接口類中只定義一個函數,實現類實現這一個函數,其內部采用反射的方式具體調用在Spring上下文中定義好的Service類來取得結果,輸入的參數和返回值都是String,其實質是XML形式的字符串。這樣做的好處是:其一,接口類和實現類從設計開始代碼就處于穩定狀態,以后極少維護,不會越來越大;其二,自然消除了多個開發人員需要修改同一文件的沖突問題;其三,如果服務器負載過重,可以在實現類中根據輸入參數的內容做一個分流,把一些任務分配到其它服務器上去,甚至可以采用前端一個分流服務器,后面一堆負責具體業務的服務器的形式。由于只有一個函數,這樣修改起來也容易得多。事實上,采用了這種方式后,完成各個流程的程序員只負責前端表現輸入,后端的Service類等三個位置的代碼,相互間處于平行狀態,基本沒有交叉,減少了沖突,提高了開發效率。下面是實現類的具體代碼。

          五.WebService端實現類的具體代碼
          import java.lang.reflect.InvocationTargetException;
          import java.lang.reflect.Method;

          import org.apache.log4j.Logger;
          import org.dom4j.DocumentException;
          import org.springframework.beans.factory.NoSuchBeanDefinitionException;

          /**
           * 此類中共有方法為WebService對外方法,其它方法為輔助此方法而使用
           *
           * 創建日期:2010-2-9 上午09:19:31
           * 修改時間:2010-2-9 上午09:19:31
           
          */
          public class ServiceImpl implements IService{
              
          // 日志記錄器
              private    static Logger logger = Logger.getLogger(ServiceImpl.class);
              
              
          /**
               * 此函數將逐步進行以下任務
               * 1.在log文件中記錄請求的XML文本
               * 2.解析文本,得到要訪問的類名,方法名,參數
               * 3.使用反射調用類的方法
               * 4.返回結果
               * 
          @throws InstantiationException
               
          */
              
          public String getResponseXML(String requestXML){
                  logger.info(
          "接收到客戶端的請求XML文本:"+requestXML);
                  
          // 新建一個包裝器
                  ResponseXMLPackager packager=new ResponseXMLPackager();
                  
                  
          try {
                      
          // 使用解析器解析請求XML文本
                      RequestXMLParser parser=new RequestXMLParser(requestXML);
                      
                      
          // 從解析器中獲取Service服務類
                      packager.setServiceName(parser.getServiceName());
                      
                      
          // 從解析器中獲取方法名
                      packager.setMethodName(parser.getMethodName());
                      
                      
          // 從解析器中獲取方法參數
                      packager.setArgs(parser.getArgs());
                      
                      
          // 通過Spring得到實例
                      Object obj=SpringUtil.getBean(packager.getServiceName());
                      logger.info(
          "在Spring上下文配置文件中找到了'"+packager.getMethodName()+"'對應的bean.");
                      
                      
          // 得到實例對應的類
                      Class<?> cls=obj.getClass();
                      
                      
          // 通過反射得到方法
                      Method method = cls.getMethod(packager.getMethodName(), new Class[] {String[].class});
                      logger.info(
          "通過反射獲得了'"+packager.getMethodName()+"'對應的方法.");
                      
                      
          // 通過反射調用對象的方法
                      String methodResonseXML=(String)method.invoke(obj,new Object[] {packager.getArgs()});
                      logger.info(
          "通過反射調用方法'"+packager.getMethodName()+"'成功.");
                      
                      
          /**************************
                       * 設置狀態,備注及方法反饋結果
                       *************************
          */
                      String remark
          ="成功執行類'"+packager.getServiceName()+"'的方法'"+packager.getMethodName()+"'";
                      packager.setStatus(ResponseXMLPackager.Status_Success);
                      packager.setRemark(remark);
                      packager.setMethodResonseXML(methodResonseXML);     
                      
                      logger.info(remark);
                  }
          catch (DocumentException e) {
                      
          // 解析不了從客戶端傳過來的XML文本時
                      
                      String remark
          ="無法解析客戶端的請求XML文本:"+requestXML+".";
                      
                      packager.setRemark(remark);
                      packager.setStatus(ResponseXMLPackager.Status_CanNotParseRequestXML);
                      
                      logger.error(remark);
                  }
          catch (NoSuchBeanDefinitionException e) {
                      
          // Spring找不到bean時            
                      String remark="無法在Spring上下文定義文件appCtx.xml中找到id'"+packager.getServiceName()+"'對應的bean.";    
                                  
                      packager.setRemark(remark);
                      packager.setStatus(ResponseXMLPackager.Status_CanNotFoundServiceName);
                      
                      logger.error(remark);
                  }        
                  
          catch (NoSuchMethodException e) {
                      
          // 找不到方法時
                      
                      String remark
          =("類'"+packager.getServiceName()+"'中沒有名為 ‘"+packager.getMethodName()+"’的方法,或是此方法非公有函數,或是參數不是字符串數組形式.");            
                      
                      packager.setRemark(remark);
                      packager.setStatus(ResponseXMLPackager.Status_NotFoundSuchMethod);
                      
                      logger.error(remark);
                  }
          catch (IllegalAccessException e) {
                      
          // 當訪問權限不夠時
                      
                      String remark
          =("訪問類'"+packager.getServiceName()+"'中名為 ‘"+packager.getMethodName()+"’的方法非法,可能原因是當前方法(getResponseXML)對該方法的訪問權限不夠.");            
                      
                      packager.setRemark(remark);
                      packager.setStatus(ResponseXMLPackager.Status_CanNotAccessMethod);
                      
                      logger.error(remark);
                  }
          catch (InvocationTargetException e) {
                      
          // 當調用的函數拋出異常時
                      
                      Exception tragetException
          =(Exception)e.getTargetException();
                      
                      
          if(tragetException instanceof BreakException){
                          
          // 程序中斷,不能繼續進行的情況.比如說用戶沒有操作權限,要找的目標不存在等.
                          packager.setRemark(tragetException.getMessage());
                          packager.setStatus(ResponseXMLPackager.Status_Ng);
                          
                          String remark
          =("執行類'"+packager.getServiceName()+"'中名為 ‘"+packager.getMethodName()+"’的方法時被中斷,原因是:"+tragetException.getMessage()+".");    
                          logger.warn(remark);
                      }
                      
          else{        
                          
          // 程序運行過程中拋出異常,如空指針異常,除零異常,主鍵約束異常等.
                          String remark=("執行類'"+packager.getServiceName()+"'中名為 ‘"+packager.getMethodName()+"’的方法時,該方法拋出了異常,異常類型為:"+tragetException.getClass().getName()+",異常信息是"+tragetException.getMessage()+".");            
                          
                          packager.setRemark(remark);
                          packager.setStatus(ResponseXMLPackager.Status_MethodThrowException);
                          
                          logger.error(remark);
                      }
                  }
                          
                  
          // 向客戶端返回響應XML文本
                  return packager.toXML();
              }
          }


          六.Service類中函數的輸入和輸出
             從上面的代碼可見,客戶端傳過來是一個XML形式的文本,RequestXMLParser類負責從這段文本中解析出具體想調用的配置在Spring上下文中Service類的beanName,類中的具體函數名和函數的參數,然后再用反射的方式調用之。為了調用方便,讓每個Service類的具體參數都是String[] 形式的(現在看如果采用類似JSON的形式更好一點),在內部再獲得其實際數據,這樣,來自客戶端的調用就能順利的到達目的函數中。函數運行完畢后,傳出的也是一個XML形式的字符串,這是為了返回數據的方便,到了客戶端后,再進行解析變成領域對象類示例。下面代碼是一個Service類中函數的例子:
          /**
           * 添加一個Tmp對象到數據庫
           * 
          @param args
           * 
          @return
           * 
          @throws Exception
           
          */
          public String add(String[] args) throws Exception{
              String name
          =args[0];
              
              
          // 同名檢測
              if(hasSameName(name)){
                  
          throw new BreakException("已經有和"+name+"同名的對象存在了.");
              }
              
              
          int age=Integer.parseInt(args[1]);
              
          float salary=Float.parseFloat(args[2]);
              String picture
          =args[3];
              
              Tmp tmp
          =new Tmp(name,age,salary,picture);
              dao.create(tmp);
              
              
          return tmp.toXML();



          七.領域對象與XML之間的相互轉化

          由于DB服務器和Client之間傳遞的是XML形式的文本,但內部使用的都是領域對象,那么,中間需要兩次轉化過程。以取得一個Tmp對象為例,在服務器端,dao從數據庫取得記錄后會形成Tmp領域對象的實例,這個實例會轉化成XML傳到客戶端;客戶端得到這段XML文本會把它還原成領域對象。以下代碼闡述了這兩個過程:
          // 服務器端領域對象的基類,它的toXML()函數使得實例轉化為XML,它的子類只要實現changePropertytoXML()這個抽象接口就能得到此項功能。
          public abstract class BaseDomainObj{
              
          // 領域對象的唯一識別標志
              protected long id;
              
              
          // 名稱
              protected String name;
              
              
          // 對象對應的記錄被添加到數據庫的時間(入庫時間)
              protected String addTime;
              
              
          // 對象對應的記錄最近被更新的時間(更新時間)
              protected String refreshTime;
              
              
          // 備注
              protected String remark;
              
              
          // 節點名
              protected String nodeName;
              
              
          // 記錄是否有效,若為false則說明無效,常改變此值來隱藏或是顯示一個對象
              protected boolean valid;
              
              
          /**
               * 無參構造函數
               
          */
              
          public BaseDomainObj(){
                  
          this(0);
              }
              
              
          /**
               * 指定id的構造函數
               * 
          @param id
               
          */
              
          public BaseDomainObj(long id){
                  
          this.id=id;
                  String currTime
          =getCurrTime();
                  addTime
          =currTime;
                  refreshTime
          =currTime;
                  valid
          =true;
                  remark
          ="";
              }
              
              
          /**
               * 將對象轉化為XML形式
               * 
          @return
               
          */
              
          public String toXML() {
                  StringBuilder sb
          =new StringBuilder();
                  
                  sb.append(
          "<"+nodeName+">");
                  sb.append(
          "<id>"+id+"</id>");
                  sb.append(
          "<name>"+name+"</name>");
                  sb.append(
          "<addTime>"+addTime+"</addTime>");
                  sb.append(
          "<refreshTime>"+refreshTime+"</refreshTime>");
                  sb.append(
          "<remark>"+remark+"</remark>");
                  sb.append(
          "<valid>"+valid+"</valid>");
                  sb.append(changePropertytoXML());        
                  sb.append(
          "</"+nodeName+">");
                  
                  
          return sb.toString();
              }
              
              
          /**
               * 將屬性轉化為XML,強制子類實現
               * 
          @return
               
          */
              
          protected abstract String changePropertytoXML();
              
              
          /**
               * 取得當前時間
               
          */
              
          private static String getCurrTime() {
                  Date date 
          = new Date();
                  Format formatter 
          = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                  
          return formatter.format(date);
              }

              
          /*************************
               * 以下為setter/getter
               ************************
          */
              
              ..
          }

          // 具體的Tmp對象,重點是changePropertytoXML()這個函數。
          public class Tmp extends BaseDomainObj{
              
          // 年齡
              private int age;
              
              
          // 薪水
              private float salary;
              
              
          /**
               * 無參構造函數
               
          */
              
          public Tmp(){
                  
          this("",0,0.0f);
              }
              
              
          /**
               * 三參數構造函數
               * 
          @param name
               * 
          @param age
               * 
          @param salary
               
          */
              
          public Tmp(String name,int age,float salary){
                  nodeName
          ="Tmp";
                  
                  
          this.name=name;
                  
          this.age=age;
                  
          this.salary=salary;
              }
              

              @Override
              
          protected String changePropertytoXML() {
                  StringBuilder sb
          =new StringBuilder();
                  
                  sb.append(
          "<age>"+age+"</age>");
                  sb.append(
          "<salary>"+salary+"</salary>");
                  
                  
          return sb.toString();
              }

              
          /***************************
               * 以下為setter/getter部分
               **************************
          */
              
              
          public int getAge() {
                  
          return age;
              }


              
          public void setAge(int age) {
                  
          this.age = age;
              }


              
          public float getSalary() {
                  
          return salary;
              }


              
          public void setSalary(float salary) {
                  
          this.salary = salary;
              }
          }

          這樣,在得到一個Tmp對象的實例后,調用其toXML函數就能得到這個實例的XML形式表現文本。“六”中的函數就是這樣做的。

          傳出的XML文本實例:
          <Tmp>
              
          <id>1</id>
              
          <name>0</name>
              
          <addTime>2010-02-15 23:39:06</addTime>
              
          <refreshTime>2010-02-15 23:39:06</refreshTime>
              
          <remark></remark>
              
          <valid>true</valid>
              
          <age>30</age>
              
          <salary>15000.0</salary>
          </Tmp>


          上面這段文本傳回到客戶端后怎么再把它變成實例呢,有了Apache的BeanUtils包任務就簡單多了。下面請看客戶端的Tmp類及其基類:
          // 客戶端Tmp類:
          public class Tmp extends BaseDomainObj{
              
              
          // 年齡
              private String age;
              
              
          // 薪水
              private String salary;
              
              
              @Override
              
          public Object[] toArray() {
                  
          return new Object[]{id,name,age,salary,addTime,refreshTime,valid,remark};
              }


              
          public String getAge() {
                  
          return age;
              }


              
          public void setAge(String age) {
                  
          this.age = age;
              }


              
          public String getSalary() {
                  
          return salary;
              }


              
          public void setSalary(String salary) {
                  
          this.salary = salary;
              }
          }

          // Tmp類的基類:
          public abstract class BaseDomainObj{
              
          // 領域對象的唯一識別標志
              protected String id;
              
              
          // 名稱
              protected String name;
              
              
          // 對象對應的記錄被添加到數據庫的時間(入庫時間)
              protected String addTime;
              
              
          // 對象對應的記錄最近被更新的時間(更新時間)
              protected String refreshTime;
              
              
          // 備注
              protected String remark;
              
              
          // 記錄是否有效,若為false則不該進入
              protected String valid;
              
              
          /**
               * ?無參構造函數
               
          */
              
          public BaseDomainObj(){
                  
              }
              
              
          /**
               * 有參構造函數,使用此函數傳入一個XML,得到相應對象
               * 
          @param xml
               * 
          @throws DocumentException
               
          */
              
          public BaseDomainObj(String xml) throws DocumentException{
                  fromXML(xml);
              }
              
              
          /**
               * 將對象轉化為數組形式,便于在表格中顯示
               * 
          @return
               
          */
              
          public abstract Object[] toArray();
              
              
          /**
               * 使用BeanUtils將XML的節點轉化到屬性中
               * 
          @param xml
               * 
          @throws DocumentException
               
          */
              @SuppressWarnings(
          "unchecked")
              
          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.setProperty(
          this,elm.getName(),elm.getText());
                      } 
          catch (IllegalAccessException e) {
                          e.printStackTrace();
                      } 
          catch (InvocationTargetException e) {
                          e.printStackTrace();
                      }
                  }
              }

              
          public String getId() {
                  
          return id;
              }

              
          public void setId(String id) {
                  
          this.id = id;
              }

              
          public String getName() {
                  
          return name;
              }

              
          public void setName(String name) {
                  
          this.name = name;
              }

              
          public String getAddTime() {
                  
          return addTime;
              }

              
          public void setAddTime(String addTime) {
                  
          this.addTime = addTime;
              }

              
          public String getRefreshTime() {
                  
          return refreshTime;
              }

              
          public void setRefreshTime(String refreshTime) {
                  
          this.refreshTime = refreshTime;
              }

              
          public String getRemark() {
                  
          return remark;
              }

              
          public void setRemark(String remark) {
                  
          this.remark = remark;
              }

              
          public String getValid() {
                  
          return valid;
              }

              
          public void setValid(String valid) {
                  
          this.valid = valid;
              }
          }

             
          重要的是上面的黑體部分,只要我們保證XML的字段和Tmp對象中的字段是一一對應的,fromXML函數就能保證完成XML到對象的轉換,對于負責具體業務的程序員,在代碼里如下做就可以了:
          String objXML=”;// 從WebService端取出的Tmp對象XML文本
          Tmp tmp=new Tmp(objXML);// 這樣,對象就出來了.
           

          小結:
          一.框架設計者一定要定義好框架的任務,限制具體程序員的行為,否則項目的可讀性可維護性就是一句空話。
          二.框架一定要完成主干的任務的流程,而具體程序員只負責枝節,換言之,具體程序員只該負責簡單的規定好了的任務,如某函數的具體實現。
          三.好的框架完成后,其他人應該能像填空一樣完成任務,要讓他們在完成任務時不需要思考具體的來龍去脈。
          四.好的框架能讓完成任務的程序員盡量平行,減少相互間的交流成本。實際上,框架和工廠流水線的設計某種程度上是相通的。
          五.隨著數據量和規模的增大,一些問題會逐漸顯山露水,這就需要框架設計者有前瞻性的眼光。
          六.如果框架已經不能滿足需求,帶來很多問題時,設計者需要有把前設計推到重來重新組建新框架的勇氣和毅力,當斷不斷,修修補補,蹣跚前行,反受其害。
          posted on 2010-05-19 14:22 何楊 閱讀(1905) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 维西| 宁明县| 涿州市| 慈溪市| 曲靖市| 鹤壁市| 苗栗市| 潼南县| 称多县| 长春市| 广德县| 邓州市| 六安市| 浦北县| 保山市| 开远市| 荥经县| 彭泽县| 抚顺县| 阿拉善右旗| 平舆县| 安陆市| 邹平县| 鄂托克旗| 永清县| 岳普湖县| 贵德县| 龙口市| 张家口市| 天等县| 昔阳县| 荣昌县| 巴楚县| 保德县| 那坡县| 甘孜县| 香河县| 杭锦旗| 松原市| 永定县| 古蔺县|