Chasing an mobile web vision

          闖蕩在移動互聯網的世界中

          OSGi介紹(五)兩個bundle

          (四)中提到的直接型改造法實際上和一個傳統的java應用程序沒有區別。因此客戶的需求發生變化,通常是牽一發而動全身。
          那么我們現在就看看如果在osgi framework中,用多個bundle來實現的效果吧。
          我的想法是用兩個bundle來配合實現“扶貧助手”的功能。一個bundle專門負責錄入和顯示紀錄,一個bundle專門負責紀錄的數據結構和對數據的處理,用時下時髦的說法就是使用了mvc,只是我的m和c放到了一起。
          先看看mc的bundle實現代碼片斷:

          package com.wukong.test.family.control;
          import java.util.*;
          import com.wukong.test.family.control.impl.*;
          public interface FamilyInfoDatabase {
              
          public static class FamilyFactory {
                  
          private static FamilyInfoDatabase database;
                  
          public static FamilyInfoDatabase getDatabaseInstance(){
                      
          if(database == null){
                          database 
          = new FamilyDatabase();
                      }

                      
          return database;
                  }

              }

              
          public String[] getColumns();
              
          public Object getValueAt(int row, int column);
              
          public String[] getSortingFields();
              
          public int getRowCount();
              
              
          public void sort(String sortField) throws IllegalArgumentException;
              
              
          public void addEntry(List columns, List values) throws IllegalArgumentException;
              
          public void deleteEntry(String familyName);
              
          public void update(String familyName,List columns, List values)throws IllegalArgumentException;
          }



          這個interface是用來描述一個數據庫所具備的基本功能。注意,這個interface被放到了package com.wukong.test.family.control中。
          另外,這個interface還提供了一個內嵌類,用來專門提供一個工廠方法來產生唯一的一個數據庫實例。
          對于這個數據庫的實現,代碼片斷如下(增加,刪除和修改的代碼都被省略了,只給出排序的代碼):
          package com.wukong.test.family.model.impl;
          public class FamilyInfoEntry {
              
          private String familyName;
              
          private int population;
              
          private int incomePerYear;
              
              FamilyInfoEntry(String familyName,
          int population,int income){
                  
          this.familyName = familyName;
                  
          this.population = population;
                  
          this.incomePerYear = income;
              }

              
              
              String getFamilyName() 
          {
                  
          return familyName;
              }

              
          int getIncomePerYear() {
                  
          return incomePerYear;
              }

              
          int getPopulation() {
                  
          return population;
              }

          }


          package com.wukong.test.family.control.impl;
          import com.wukong.test.family.control.FamilyInfoDatabase;
          import java.util.*;
          public class FamilyDatabase implements FamilyInfoDatabase {
              
          private LinkedList familyEntryList = new LinkedList();
              
          private Object[] sortedValues = null;
              
          public FamilyDatabase() {
                  
          this.familyEntryList.add(new FamilyInfoEntry("Zhang",3,1200));
                  
          this.familyEntryList.add(new FamilyInfoEntry("Li",6,1800));
                  
          this.familyEntryList.add(new FamilyInfoEntry("Liu",4,1500));
                  
          this.sortedValues = this.familyEntryList.toArray();
              }

              
          public String[] getColumns() {
                  
          return new String[] "Family Name""Family Population""Income" };
              }

              
          public Object getValueAt(int row, int column) {
                  FamilyInfoEntry entry 
          = (FamilyInfoEntry)this.sortedValues[row];
                  
          switch (column) {
                  
          case 0:
                      
          return entry.getFamilyName();
                  
          case 1:
                      
          return new Integer(entry.getPopulation());
                  
          case 2:
                      
          return new Integer(entry.getIncomePerYear());
                  
          default:
                      
          throw new IllegalArgumentException("Invalid column index.");
                  }

              }

              
          public String[] getSortingFields() {
                  
          return new String[] "FamilyName","Income" };
              }

              
          public int getRowCount() {
                  
          return this.familyEntryList.size();
              }

              
          public void addEntry(List columns, List values)
                      
          throws IllegalArgumentException {
              }

              
          public void deleteEntry(String familyName) {
              }

              
          public void update(String familyName, List columns, List values)
                      
          throws IllegalArgumentException {
              }


              
          public void sort(String sortField) throws IllegalArgumentException{
                  
          if(sortField.equals("FamilyName")){
                      
          this.sortedValues = this.familyEntryList.toArray();
                      Arrays.sort(
          this.sortedValues,new SortedByName());
                      
                  }

                  
          if(sortField.equals("Income")){
                      
          this.sortedValues = this.familyEntryList.toArray();
                      Arrays.sort(
          this.sortedValues,new SortedByIncome());
                  }

           
          throw new IllegalArgumentException("Sorting enties by field '" + sortField + "' is not supported."); 
              }

              
              
          class SortedByName implements Comparator{
                  
          public int compare(Object entry1,Object entry2) {
                      
          if (entry1 == entry2) {
                          
          return 0;
                      }

                      FamilyInfoEntry en1 
          = (FamilyInfoEntry) entry1;
                      FamilyInfoEntry en2 
          = (FamilyInfoEntry) entry2;
                      
                      
          return en1.getFamilyName().compareTo(en2.getFamilyName());
                  }

                  
              }

              
              
          class SortedByIncome implements Comparator {
                  
          public int compare(Object entry1, Object entry2) {
                      
          if (entry1 == entry2) {
                          
          return 0;
                      }

                      FamilyInfoEntry en1 
          = (FamilyInfoEntry) entry1;
                      FamilyInfoEntry en2 
          = (FamilyInfoEntry) entry2;
                      
          return en1.getIncomePerYear() - en2.getIncomePerYear();
                  }

              }

          }



          同樣需要注意的是我們把這個實現放到了package com.wukong.test.family.control.impl中
           
          下面看看v的bundle實現。

          package com.wukong.test.family.gui;
          import org.osgi.framework.*;
          import javax.swing.*;
          import javax.swing.table.*;
          import java.awt.*;
          import java.awt.event.*;
          import com.wukong.test.family.control.*;
          public class FamilyInfoGui implements BundleActivator, ActionListener,ItemListener {
              
          private JFrame mainFrame;
              
          private JPanel contentPanel;
              
          private JTable familiesTable;
              
          private JScrollPane familiesTableScrollPane;
              
          private JPanel sortedByPanel = new JPanel(new GridLayout(12));
              
          private JLabel sortedByLabel = new JLabel("Sorted By: ");
              
          private JComboBox sortedByList = null;
              
          private JPanel commandPanel = new JPanel(new GridLayout(13));
              
          private JButton addEntry = new JButton("Add");
              
          private JButton deleteEntry = new JButton("Delete");
              
          private JButton updateEntry = new JButton("Update");
              
              FamilyInfoDatabase database 
          = null;
              
          public void start(BundleContext context) throws Exception {
                  Runnable r 
          = new Runnable() {
                      
          public void run() {
                          contentPanel 
          = new JPanel();
                          familiesTableScrollPane 
          = new JScrollPane();
                          database 
          = FamilyInfoDatabase.FamilyFactory
                                  .getDatabaseInstance();
                          familiesTable 
          = new JTable(new FamilyInfoTableModel(database));
                          familiesTableScrollPane.setViewportView(familiesTable);
                          
                          String[] sortedFields 
          = database.getSortingFields();
                          
                          sortedByList 
          = new JComboBox(sortedFields);
                          
                          sortedByList.addItemListener(FamilyInfoGui.
          this);
                          sortedByPanel.add(sortedByLabel);
                          sortedByPanel.add(sortedByList);
                          commandPanel.add(addEntry);
                          commandPanel.add(deleteEntry);
                          commandPanel.add(updateEntry);
                          contentPanel.add(sortedByPanel, BorderLayout.NORTH);
                          contentPanel.add(familiesTableScrollPane, BorderLayout.CENTER);
                          contentPanel.add(commandPanel, BorderLayout.SOUTH);
                          mainFrame 
          = new JFrame();
                          mainFrame.setContentPane(contentPanel);
                          mainFrame.setSize(
          new Dimension(500600));
                          mainFrame.show();
                      }

                  }
          ;
                  Thread t 
          = new Thread(r);
                  t.start();
              }

              
          public void stop(BundleContext context) throws Exception {
                  
          this.mainFrame.dispose();
              }

              
          public void actionPerformed(ActionEvent event) {
              }

              
              
              
          public void itemStateChanged(ItemEvent event) {
                  
          if(event.getSource() == this.sortedByList){
                      String sortingField 
          = (String)event.getItem();
                      
          this.database.sort(sortingField);
                      
          this.familiesTable.repaint();
                  }

              }

              
          class FamilyInfoTableModel extends AbstractTableModel {
                  
          private FamilyInfoDatabase database;
                  FamilyInfoTableModel(FamilyInfoDatabase database) 
          {
                      
          this.database = database;
                  }

                  
          public String getColumnName(int col) {
                      
          return database.getColumns()[col];
                  }

                  
          public boolean isCellEditable(int row, int col) {
                      
          return false;
                  }

                  
          public int getColumnCount() {
                      
          return database.getColumns().length;
                  }

                  
          public int getRowCount() {
                      
          return database.getRowCount();
                  }

                  
          public Object getValueAt(int row, int col) {
                      
          return database.getValueAt(row, col);
                  }

              }

          }


          v的實現基本是界面的構造,而關于數據庫,它只能看到interface FamilyInfoDatabase,因為它只import com.wukong.test.family.control.*,然后通過FamilyInfoDatabase內嵌靜態類的靜態工廠方法獲得數據庫的實例,這樣它完全不用關心數據庫如何實現。
           
          源碼編譯后得到class文件后,下一步我們的工作就要來構造這兩個bundle。最關鍵的步驟就是為每個bundle寫它的manifest文件
          我們先給出mc bundle的manifest文件(familycontrol.mf):
          Manifest-Version: 1.0
          Bundle-SymbolicName: com.wukong.test.family.control
          Bundle-Name: family control
          Bundle-Version: 
          1.0
          Bundle-Vendor: LiMing
          Export-Package: com.wukong.test.family.control


          非常簡單,但請大家注意這行
          Export-Package: com.wukong.test.family.control
          它告訴framework,這個bundle提供com.wukong.test.family.control這個包。
          所謂“bundle A提供某個包”意思,通俗簡單的理解是如果bundle B在manifest文件中說明要使用這個包(通過Import-Package這個關鍵字,下面會在v bundle中有說明)那么bundle B運行時,當使用到這個包中的類時,framework將告訴B的class loader這個類的定義是來自bundle A,從而不會發生ClassNotFoundException。另外值得一提的是我們并沒有把com.wukong.test.family.control.impl這個包加進來,這樣做的目的也是為了隱藏具體實現。將來只要interface不變,我們可以使用不同的實現來替換現有實現,大大提高擴展性。
           
          還有一點,細心的話,你會發現這個bundle沒有activator。這是允許的,這種bundle就像動態連接庫,只提供接口和方法以及實現,等待其他實體的調用,而本身不能運行。
          再來看看v bundle的manifest文件(family.mf):

          Manifest-Version: 1.0
          Bundle-SymbolicName: com.wukong.test.family.gui
          Bundle-Name: family gui
          Bundle-Version: 
          1.0
          Bundle-Vendor: LiMing
          Bundle-Activator: com.wukong.test.family.gui.FamilyInfoGui
          Import-Package: org.osgi.framework
          ;version=1.3,com.wukong.test.family.control


          正如上面曾經提到過的一樣,我們在Import-Package:關鍵字中指定該bundle需要使用com.wukong.test.family.control包。在v bundle的源代碼中,我們也看到這個bundle的確使用了com.wukong.test.family.control包中的FamilyInfoDatabase。更深入一點,我們可以簡單的這樣理解,當v bundle運行的時候,framework通過匹配bundle間的Import-Package和Export-Package,
          然后告訴v bundle,FamilyInfoDatabase這個類型的定義和裝載實際上是由mc bundle完成的。這樣,v bundle就會使用mc bundle的class loader來加在這個類型。另外,按照osgi framework的spec規定,javax.swing;javax.swing.table;java.awt;java.awt.event這4個包都都不屬于framework的必備包,所以應該在Import-Package中指定。為了不用在Import-Package中指定這4個包,這個例子要求運行在sun的j2se的jvm(比如version 1.4.2_04),這樣4個包都作為java api由bootstrap classloader加載,每個bundle都可以直接使用了。
           
          有了manifest文件,我們最后就是打jar包了。
          比如在windows下,你把兩個manifest文件放在包的根目錄,然后執行以下命令
          jar -cvfm family.jar family.mf com\wukong\test\family\gui\*.class
          jar -cvfm familycontrol.jar familycontrol.mf com\wukong\test\family\control
          這樣,我們就可以認為family.jar是我們的v bundle而familycontrol.jar是我們的mc bundle。
          下面,我們就來安裝運行這兩個bundle吧。
          我將首先簡單介紹一個我自己編寫的osgi framework,然后結合這個demo framework來部署和運行新的“扶貧助手”。
          本人編寫的osgi framework基本上完成了spec r4的核心規范,但是與安全相關的功能都沒有實現。
          啟動這個framework后,它的shell提供了如下簡單的framework管理功能命令:
          bl 即bundle list,查看所有已經安裝的bundle
          dl 即detailed bundle list,用來查看某個bundle的詳細信息
          sl 即service list,用來查看所有已經注冊的service
          ls 即list setting,用來查看所有設置的系統屬性(System.getProperties)和framework當前的active start level
          in 即install bundle,通過指定bundle文件的url來安裝該bundle
          up 即update bundle,通過指定bundle id來升級指定的bundle
          un 即uninstall bundle,通過指定bundle id來卸載指定的bundle
          stt 即start bundle,通過指定bundle id來啟動指定的bundle
          stp 即stop bundle,通過指定bundle id來停止指定bundle的運行
          rst 即restart bundle,通過指定bundle id來重新啟動指定的bundle
          rfr 即refresh package,當某些bundle被更新或卸載后,如果不執行這個命令,那么被更新或卸載的bundle的Export-Package(如果有的話)將繼續服役,
          也就是說,之前使用這些包的bundle不會因為這個bundle被更新或者卸載而發生變化。而執行這個命令后,受影響的bundle將被重新解析
          rest 即restart,重新啟動framework,屬于“熱起”
          shut 即shutdown,停止整個framework的運行
          set 用來設置framework的active startlevel或者某個bundle的start level
          log 用來查看系統的日志信息
          現在我們來假設運行環境如下:
          OS: window
          jvm: j2se 1.4.2_04
          family.jar和familycontrol.jar這兩個文件放在D:/framework/bundles目錄下
          第一步:安裝bundle
          輸入以下兩條命令:
          in file:D:/framework/bundles/familycontrol.jar
          in file:D:/framework/bundles/family.jar
          然后用bl命令查看結果,其中得到如下顯示結果:
          Bundle Id Bundle Name Bundle State Start Level Bundle Location     Bundle Symbolic Name
          0  OSGiFramework Active  0  System Bundle     system.bundle
          .
          .(省略部分結果顯示)
          .
          16  family control Installed 1  file:D:/framework/bundles/familycontrol.jar com.wukong.test.family.control
          17  family gui Installed 1  file:D:/framework/bundles/family.jar  com.wukong.test.family.gui
          說明兩個bundle安裝成功,其中mc bundle的id是16, v bundle的id是17
          第二步:啟動bundle
          可以輸入以下命令
          stt 16,17
          然后我們就可以看到一個JFrame顯示出來,里面顯示了3條記錄,沒有任何順序(其順序實際上是在程序中添加到LinkedList的順序)。我們可以在Sorted By的列表中看到有兩個選擇“FamilyName”和“Income”,當選擇“Income”時,你將發現3條記錄按年收入由小到大排列起來。程序的運行完全符合我們的意圖。
          這時如果我們再用bl命令查看,將得到如下結果:
          Bundle Id Bundle Name Bundle State Start Level Bundle Location     Bundle Symbolic Name
          0  OSGiFramework Active  0  System Bundle     system.bundle
          .
          .(省略部分結果顯示)
          .
          16  family control Resolved 1  file:D:/framework/bundles/familycontrol.jar com.wukong.test.family.control
          17  family gui Active  1  file:D:/framework/bundles/family.jar  com.wukong.test.family.gui
          注意:由于mc bundle沒有Activator,所以啟動這個bundle只會使其進入Resolved狀態。

          有了這樣的結構,程序變得靈活些了。現在假設客戶提出需要增加一個字段,即人均年收入,并且提供按人均年收入排列紀錄。看看framework框架下,這樣的變動是如何的簡單!
          首先,我們通過分析,發現只要更改mc bundle的代碼,使得數據庫的實現中增加這個字段以及相應的排序方法即可。更新后的代碼如下:

          package com.wukong.test.family.control.impl;
          import com.wukong.test.family.control.FamilyInfoDatabase;
          import java.util.*;
          public class FamilyDatabase implements FamilyInfoDatabase {
              
          private LinkedList familyEntryList = new LinkedList();
              
          private Object[] sortedValues = null;
              
          public FamilyDatabase() {
                  
          this.familyEntryList.add(new FamilyInfoEntry("Zhang",3,1260));
                  
          this.familyEntryList.add(new FamilyInfoEntry("Li",6,1800));
                  
          this.familyEntryList.add(new FamilyInfoEntry("Liu",4,1500));
                  
          this.sortedValues = this.familyEntryList.toArray();
              }

              
          public String[] getColumns() {
                  
          return new String[] "Family Name""Family Population""Income" ,"IncomePerPerson"};//添加了“IncomePerPerson”字段
              }

              
          public Object getValueAt(int row, int column) {
                  FamilyInfoEntry entry 
          = (FamilyInfoEntry)this.sortedValues[row];
                  
          switch (column) {
                  
          case 0:
                      
          return entry.getFamilyName();
                  
          case 1:
                      
          return new Integer(entry.getPopulation());
                  
          case 2:
                      
          return new Integer(entry.getIncomePerYear());
                  
          case 3:
                      
          return new Integer(entry.getIncomePerYear()/entry.getPopulation());//計算人均年收入
                  default:
                      
          throw new IllegalArgumentException("Invalid column index.");
                  }

              }

              
          public String[] getSortingFields() {
              }

              
          public int getRowCount() {
                  
          return this.familyEntryList.size();
              }

              
          public void addEntry(List columns, List values)
                      
          throws IllegalArgumentException {
                  
          // TODO Auto-generated method stub
              }

              
          public void deleteEntry(String familyName) {
                  
          // TODO Auto-generated method stub
              }

              
          public void update(String familyName, List columns, List values)
                      
          throws IllegalArgumentException {
                  
          // TODO Auto-generated method stub
              }

              
          public void sort(String sortField) throws IllegalArgumentException{
                  
          if(sortField.equals("FamilyName")){
                      
          this.sortedValues = this.familyEntryList.toArray();
                      Arrays.sort(
          this.sortedValues,new SortedByName());
                      
                  }

                  
          if(sortField.equals("Income")){
                      
          this.sortedValues = this.familyEntryList.toArray();
                      Arrays.sort(
          this.sortedValues,new SortedByIncome());
                  }

                  
          if(sortField.equals("IncomePerPerson")){//對紀錄按人均年收入進行排序
                      this.sortedValues = this.familyEntryList.toArray();
                      Arrays.sort(
          this.sortedValues,new SortedByIPP());
                      
                  }

              }

              
              
          class SortedByName implements Comparator{
                  
          public int compare(Object entry1,Object entry2) {
                      
          if (entry1 == entry2) {
                          
          return 0;
                      }

                      FamilyInfoEntry en1 
          = (FamilyInfoEntry) entry1;
                      FamilyInfoEntry en2 
          = (FamilyInfoEntry) entry2;
                      
                      
          return en1.getFamilyName().compareTo(en2.getFamilyName());
                  }

                  
              }

              
              
          class SortedByIncome implements Comparator {
                  
          public int compare(Object entry1, Object entry2) {
                      
          if (entry1 == entry2) {
                          
          return 0;
                      }

                      FamilyInfoEntry en1 
          = (FamilyInfoEntry) entry1;
                      FamilyInfoEntry en2 
          = (FamilyInfoEntry) entry2;
                      
          return en1.getIncomePerYear() - en2.getIncomePerYear();
                  }

              }

              
              
          class SortedByIPP implements Comparator {//人均年收入的排序標準。
                  public int compare(Object entry1, Object entry2) {
                      
          if (entry1 == entry2) {
                          
          return 0;
                      }

                      FamilyInfoEntry en1 
          = (FamilyInfoEntry) entry1;
                      FamilyInfoEntry en2 
          = (FamilyInfoEntry) entry2;
                      
          return (en1.getIncomePerYear()/en1.getPopulation()) - (en2.getIncomePerYear()/en2.getPopulation());
                  }

                  
              }

          }




          編譯完畢后,我們把mc bundle的manifest文件改為如下:

          Manifest-Version: 1.0
          Bundle-SymbolicName: com.wukong.test.family.control
          Bundle-Name: family control
          Bundle-Version: 
          2.0 //由1.0改為2.0
          Bundle-Vendor: LiMing
          Export-Package: com.wukong.test.family.control


          然后使用jar工具打包,并把產生的新的familycontrol.jar依舊放在D:/framework/bundles目錄下。
          這時,我們只要執行下面的命令,framework就完成對mc bundle的升級工作:
          up 16
          升級完畢后,你會發現v bundle的顯示依舊沒有任何變化。這時我們需要執行一個refresh package命令,來更新bundle的解析:
          rfr
          這時,你會發現原來的JFrame被關閉了,然后又一個JFrame顯示出來,里面的紀錄多了一列“IncomePerPerson”,而且在“Sorted By”里面也
          多了“IncomePerPerson”,而且,排序的結果也很正確。
          瞧,framework為應用程序提供了強大的動態更新功能,非常方便。甚至都不用重新啟動framework,更不用說JVM了。您也許會說,最終用戶根本不可能幫你輸入這些什么in,up晦澀的命令。還有,讓用戶拷貝新版本到本地似乎也不是什么好主意。嗯,這個問題說得很對。其實我是偷懶了,這些操作framework都留有接口,我們只要再編寫一個專門管理這兩個bundle升級的第三個bundle就可以了,這個bundle將提供友好的界面來告訴最終用戶如何升級。這樣就能夠提供比較好的解決方案了。至于第二個問題,別忘了bundle的location是一個url,只要jvm注冊了相應的url handler完全可以支持http,ftp等豐富的url,例如,升級的時候,framework自動連接到供應商的http下載服務器獲取最新版本進行更新,當然,我們也可以把更強大更靈活的升級功能及選項加入到上面提到的第三個bundle中,從而極大提高了程序的易用性,而且極大降低了軟件提供商的維護成本。
           
          到此為止,我已經想不出再多好話了,但是,還可以更好!下一節,將采用service的方式來實現,嘿嘿,情況又會不一樣了,到時候我會對比一下這兩種實現方式的優劣。
           
          題外話:抱歉沒有拿某個著名的產品或者open source的產品來舉例,我打算把自己的實現也opensource,有興趣參與嗎?不管怎樣,過兩天就把實現上傳上來,歡迎拍磚。

          posted on 2006-02-14 16:02 勤勞的蜜蜂 閱讀(4392) 評論(3)  編輯  收藏

          評論

          # re: OSGi介紹(五)兩個bundle 2007-06-22 10:27 hata

          這章講的比較長,要耐心看,有點遺憾的是沒有把工具放上來我測試不鳥。在eclipse里的equinox插件里只能看到bundle啟動與否的提示,看不到運行結果,很是郁悶。希望快點把實現傳上來?。?
          我分析下我的看法,看對不對啊:
          我有2個bundle:bundleA & bundleB
          bundleA里我寫入: interface,pojo,iterfaceImpl
          (在manifese里:Export-Package對外提供接口)
          bundleB里我寫入: 一系列的業務方法.
          (在manifese里:Import-Package獲取bundleA的服務)
          那么這個時候就能運用到OSGI的功能了?我即時更新bundleA的話那么bundleB會相應發生變化!這就完成了最基本的OSGI的功能?
          ===>
          附錄:針對OSGI實戰上的例子,那么功能基本相同,只是擴展了bundleB,多寫了2個bundleC,bundleD?只是里面用到了servlet和service,我想我要看看你下面寫的才知道怎么應用到上面.估計理解完后調通例子也就能進一步了解了.
          ===>
          我們做J2EE,只是想把接口放在OSGI里,經理也只是這么說,希望能用這個做吧....期待你更多的OSGI的資料,我想你這個blog也算國內討論osgi少有的好地方,加油加油!  回復  更多評論   

          # re: OSGi介紹(五)兩個bundle[未登錄] 2009-07-24 10:04 han

          有點長。?,F在也不干活了。。天天看OSGI。。呵呵  回復  更多評論   

          # re: OSGi介紹(五)兩個bundle[未登錄] 2009-07-24 17:26 han

          我想問一下:FamilyInfoEntry 這個類不放到mc bundle的manifest文件中嗎?
          這個要怎么處理?  回復  更多評論   


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


          網站導航:
           
          主站蜘蛛池模板: 八宿县| 泽州县| 隆子县| 化德县| 咸阳市| 临海市| 绥江县| 墨竹工卡县| 大埔县| 年辖:市辖区| 正定县| 乌拉特中旗| 金坛市| 舒兰市| 汉沽区| 民丰县| 台江县| 太白县| 洞头县| 尼勒克县| 绿春县| 饶阳县| 信宜市| 板桥市| 惠安县| 焦作市| 县级市| 娱乐| 大连市| 耒阳市| 台中县| 略阳县| 古交市| 交城县| 德兴市| 广饶县| 岱山县| 沈阳市| 榆中县| 彝良县| 金山区|