程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> struts+spring+ibatis輕量級J2EE開發

struts+spring+ibatis輕量級J2EE開發

編輯:JAVA編程入門知識

  多數IT 組織都必須解決三個主要問題:1.幫助組織減少成本 2.增加並且保持客戶 3.加快業務效率。完成這些問題一般都需要實現對多個業務系統的數據和業務邏輯的無縫訪問,也就是說,要實施系統集成工程,以便聯結業務流程、實現數據的訪問與共享。

  JpetStore 4.0是ibatis的最新示例程序,基於Struts MVC框架(注:非傳統Struts開發模式),以ibatis作為持久化層。該示例程序設計優雅,層次清楚,可以學習以及作為一個高效率的編程模型參考。本文是在其基礎上,采用Spring對其中間層(業務層)進行改造。使開發量進一步減少,同時又擁有了Spring的一些好處…

    1. 前言

  JpetStore 4.0是ibatis的最新示例程序。ibatis是開源的持久層產品,包含SQL Maps 2.0 和 Data Access Objects 2.0 框架。JpetStore示例程序很好的展示了如何利用ibatis來開發一個典型的J2EE web應用程序。JpetStore有如下特點:

  • ibatis數據層
  • POJO業務層
  • POJO領域類
  • Struts MVC
  • jsp 表示層

  以下是本文用到的要害技術介紹,本文假設您已經對Struts,SpringFramewok,ibatis有一定的了解。

  • Struts 是目前Java Web MVC框架中不爭的王者。經過長達五年的發展,Struts已經逐漸成長為一個穩定、成熟的框架,並且占有了MVC框架中最大的市場份額。但是Struts某些技術特性上已經落後於新興的MVC框架。面對Spring MVC、Webwork2 這些設計更精密,擴展性更強的框架,Struts受到了前所未有的挑戰。但站在產品開發的角度而言,Struts仍然是最穩妥的選擇。本文的原型例子JpetStore 4.0就是基於Struts開發的,但是不拘泥於Struts的傳統固定用法,例如只用了一個自定義Action類,並且在form bean類的定義上也是開創性的,令人耳目一新,稍後將具體剖析一下。
  • Spring Framework 實際上是EXPert One-on-One J2EE Design and Development 一書中所闡述的設計思想的具體實現。Spring Framework的功能非常多。包含AOP、ORM、DAO、Context、Web、MVC等幾個部分組成。Web、MVC暫不用考慮,JpetStore 4.0用的是更成熟的Struts和JSP;DAO由於目前Hibernate、JDO、ibatis的流行,也不考慮,JpetStore 4.0用的就是ibatis。因此最需要用的是AOP、ORM、Context。Context中,最重要的是Beanfactory,它能將接口與實現分開,非常強大。目前AOP應用最成熟的還是在事務治理上。
  • ibatis 是一個功能強大實用的SQL Map工具,不同於其他ORM工具(如hibernate),它是將SQL語句映射成Java對象,而對於ORM工具,它的SQL語句是根據映射定義生成的。ibatis 以SQL開發的工作量和數據庫移植性上的讓步,為系統設計提供了更大的自由空間。有ibatis代碼生成的工具,可以根據DDL自動生成ibatis代碼,能減少很多工作量。

    2. JpetStore簡述

  2.1. 背景

  最初是Sun公司的J2EE petstore,其最主要目的是用於學習J2EE,但是其缺點也很明顯,就是過度設計了。接著Oracle用J2EE petstore來比較各應用服務器的性能。微軟推出了基於。Net平台的 Pet shop,用於競爭J2EE petstore.而JpetStore則是經過改良的基於struts的輕便框架J2EE web應用程序,相比來說,JpetStore設計和架構更優良,各層定義清楚,使用了很多最佳實踐和模式,避免了很多"反模式",如使用存儲過程,在java代碼中嵌入SQL語句,把Html存儲在數據庫中等等。最新版本是JpetStore 4.0.

  2.2. JpetStore開發運行環境的建立

  1、開發環境

  • Java SDK 1.4.2
  • Apache Tomcat 4.1.31
  • Eclipse-SDK-3.0.1-win32
  • HSQLDB 1.7.2

  2、Eclipse插件

  • EMF SDK 2.0.1:Eclipse建模框架,lomboz插件需要,可以使用runtime版本。
  • lomboz 3.0:J2EE插件,用來在Eclipse中開發J2EE應用程序
  • Spring IDE 1.0.3:Spring Bean配置治理插件
  • xmlbuddy_2.0.10:編輯XML,用免費版功能即可
  • tomcatPluginV3:tomcat治理插件
  • Properties Editor:編輯java的屬性文件,並可以預覽以及自動存盤為Unicode格式。免去了手工或者ANT調用native2ascii的麻煩。

    3、示例源程序

  • ibatis示例程序JpetStore 4.0 http://www.ibatis.com/jpetstore/jpetstore.html
  • 改造後的源程序(+spring)(源碼鏈接)

  2.3. 架構

  圖1 JpetStore架構圖

  圖1 是JPetStore架構圖,更具體的內容請參見JPetStore的白皮書。參照這個架構圖,讓我們稍微剖析一下源代碼,得出JpetStore 4.0的具體實現圖(見圖2),思路一下子就豁然開朗了。前言中提到的非傳統的struts開發模式,要害就在struts Action類和form bean類上。

  struts Action類只有一個:BeanAction.沒錯,確實是一個!與傳統的struts編程方式很不同。再仔細研究BeanAction類,發現它其實是一個通用類,利用反射原理,根據URL來決定調用formbean的哪個方法。BeanAction大大簡化了struts的編程模式,降低了對struts的依靠(與struts以及WEB容器有關的幾個類都放在com.ibatis.struts包下,其它的類都可以直接復用)。利用這種模式,我們會很輕易的把它移植到新的框架如JSF,spring.

  這樣重心就轉移到form bean上了,它已經不是普通意義上的form bean了。查看源代碼,可以看到它不僅僅有數據和校驗/重置方法,而且已經具有了行為,從這個意義上來說,它更像一個BO(Business Object)。這就是前文講到的,BeanAction類利用反射原理,根據URL來決定調用form bean的哪個方法(行為)。form bean的這些方法的簽名很簡單,例如:
  
   public String myActionMethod() {
     //..work
     return "sUCcess";
   }

  方法的返回值直接就是字符串,對應的是forward的名稱,而不再是ActionForward對象,創建ActionForward對象的任務已經由BeanAction類代勞了。

  另外,程序還提供了ActionContext工具類,該工具類封裝了request 、response、form parameters、request attributes、session attributes和 application attributes中的數據存取操作,簡單而線程安全,form bean類使用該工具類可以進一步從表現層框架解耦。

  在這裡需要非凡指出的是,BeanAction類是對struts擴展的一個有益嘗試,雖然提供了非常好的應用開發模式,但是它還非常新,一直在發展中。

  圖2 JpetStore 4.0具體實現

點擊查看大圖

  

  2.4. 代碼剖析

  下面就讓我們開始進一步分析JpetStore4.0的源代碼,為下面的改造鋪路。

  • BeanAction.java是唯一一個Struts action類,位於com.ibatis.struts包下。正如上文所言,它是一個通用的控制類,利用反射機制,把控制轉移到form bean的某個方法來處理。具體處理過程參考其源代碼,簡單明晰。
  •   Form bean類位於com.ibatis.jpetstore.presentation包下,命名規則為***Bean。Form bean類全部繼續於BaseBean類,而BaseBean類實際繼續於ActionForm,因此,Form bean類就是Struts的 ActionForm,Form bean類的屬性數據就由struts框架自動填充。而實際上,JpetStore4.0擴展了struts中ActionForm的應用: Form bean類還具有行為,更像一個BO,其行為(方法)由BeanAction根據配置(struts-config.xml)的URL來調用。雖然如此,我們還是把Form bean類定位於表現層。

      Struts-config.xml的配置裡有3種映射方式,來告訴BeanAction把控制轉到哪個form bean對象的哪個方法來處理。
      
      以這個請求連接為例http://localhost/jpetstore4/shop/vieWorder.do
      
      1. URL Pattern
          <action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
          name="orderBean" scope="session"
          validate="false">
          <forward name="success" path="/order/ViewOrder.jsp"/>
        </action>
      

  此種方式表示,控制將被轉發到"orderBean"這個form bean對象 的"viewOrder"方法(行為)來處理。方法名取"path"參數的以"/"分隔的最後一部分。

  2. Method Parameter

  

      <action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
      name="orderBean" parameter="viewOrder" scope="session"
      validate="false">
      <forward name="success" path="/order/ViewOrder.jsp"/>
    </action>
  

  此種方式表示,控制將被轉發到"orderBean"這個form bean對象的"viewOrder"方法(行為)來處理。配置中的"parameter"參數表示form bean類上的方法。"parameter"參數優先於"path"參數。

  3. No Method call

  

      <action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
      name="orderBean" parameter="*" scope="session"
      validate="false">
      <forward name="success" path="/order/ViewOrder.jsp"/>
    </action>

  此種方式表示,form bean上沒有任何方法被調用。假如存在"name"屬性,則struts把表單參數等數據填充到form bean對象後,把控制轉發到"success".否則,假如name為空,則直接轉發控制到"success".

  這就相當於struts內置的org.apache.struts.actions.ForwardAction的功能

  <action path="/shop/viewOrder" type="org.apache.struts.actions.ForwardAction"
  
       parameter="/order/ViewOrder.jsp " scope="session" validate="false">
   </action>

  • Service類位於com.ibatis.jpetstore.service包下,屬於業務層。這些類封裝了業務以及相應的事務控制。Service類由form bean類來調用。
  • com.ibatis.jpetstore.persistence.iface包下的類是DAO接口,屬於業務層,其屏蔽了底層的數據庫操作,供具體的Service類來調用。DaoConfig類是工具類(DAO工廠類),Service類通過DaoConfig類來獲得相應的DAO接口,而不用關心底層的具體數據庫操作,實現了如圖2中{耦合2}的解耦。
  • com.ibatis.jpetstore.persistence.sqlmapdao包下的類是對應DAO接口的具體實現,在JpetStore4.0中采用了ibatis來實現ORM。這些實現類繼續BaseSqlMapDao類,而BaseSqlMapDao類則繼續ibatis DAO 框架中的SqlMapDaoTemplate類。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目錄下。這些類和配置文件位於數據層
  • Domain類位於com.ibatis.jpetstore.domain包下,是普通的javabean。在這裡用作數據傳輸對象(DTO),貫穿視圖層、業務層和數據層,用於在不同層之間傳輸數據。

  剩下的部分就比較簡單了,請看具體的源代碼,非常清楚。

  2.5. 需要改造的地方

  JpetStore4.0的要害就在struts Action類和form bean類上,這也是其精華之一(雖然該實現方式是試驗性,待擴充和驗證),在此次改造中我們要保留下來,即控制層一點不變,表現層獲取相應業務類的方式變了(要加載spring環境),其它保持不變。要非凡關注的改動是業務層和持久層,幸運的是JpetStore4.0設計非常好,需要改動的地方非常少,而且由模式可循,如下:

  1. 業務層和數據層用Spring BeanFactory機制治理。

  2. 業務層的事務由spring 的aop通過聲明來完成。

  3. 表現層(form bean)獲取業務類的方法改由自定義工廠類來實現(加載spring環境)。

  3. JPetStore的改造

  3.1. 改造後的架構

  

  
  

  其中紅色部分是要增加的部分,藍色部分是要修改的部分。下面就讓我們逐一剖析。

  3.2. Spring Context的加載

  為了在Struts中加載Spring Context,一般會在struts-config.xml的最後添加如下部分:

  <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
  <set-property property="contextConfigLocation"
  value="/WEB-INF/applicationContext.xml" />
  </plug-in>
  
  Spring在設計時就充分考慮到了與Struts的協同工作,通過內置的Struts Plug-in在兩者之間提供了良好的結合點。但是,因為在這裡我們一點也不改動JPetStore的控制層(這是JpetStore4.0的精華之一),所以本文不預備采用此方式來加載ApplicationContext.我們利用的是spring framework 的BeanFactory機制,采用自定義的工具類(bean工廠類)來加載spring的配置文件,從中可以看出Spring有多靈活,它提供了各種不同的方式來使用其不同的部分/層次,您只需要用你想用的,不需要的部分可以不用。

  具體的來說,就是在com.ibatis.spring包下創建CustomBeanFactory類,spring的配置文件applicationContext.xml也放在這個目錄下。以下就是該類的全部代碼,很簡單:

  public final class CustomBeanFactory {
   static XmlBeanFactory factory = null;
   static {
    Resource is = new
  InputStreamResource( CustomBeanFactory.class.getResourceAsStream("applicationContext.xml"));
    factory = new XmlBeanFactory(is);
   }
   public static Object getBean(String beanName){
    return factory.getBean(beanName);
   }
  }

  實際上就是封裝了Spring 的XMLBeanFactory而已,並且Spring的配置文件只需要加載一次,以後就可以直接用CustomBeanFactory.getBean("someBean")來獲得需要的對象了(例如someBean),而不需要知道具體的類。CustomBeanFactory類用於{耦合1}的解耦。

  CustomBeanFactory類在本文中只用於表現層的form bean對象獲得service類的對象,因為我們沒有把form bean對象配置在applicationContext.xml中。但是,為什麼不把表現層的form bean類也配置起來呢,這樣就用不著這CustomBeanFactory個類了,Spring會幫助我們創建需要的一切?問題的答案就在於form bean類是struts的ActionForm類!假如大家熟悉struts,就會知道ActionForm類是struts自動創建的:在一次請求中,struts判定,假如ActionForm實例不存在,就創建一個ActionForm對象,把客戶提交的表單數據保存到ActionForm對象中。因此formbean類的對象就不能由spring來創建,但是service類以及數據層的DAO類可以,所以只有他們在spring中配置。

  所以,很自然的,我們就創建了CustomBeanFactory類,在表現層來銜接struts和spring.就這麼簡單,實現了另一種方式的{耦合一}的解耦。

  3.3. 表現層

  上面分析到,struts和spring是在表現層銜接起來的,那麼表現層就要做稍微的更改,即所需要的service類的對象創建上。以表現層的AccountBean類為例:

  原來的源代碼如下
  
  
      private static final AccountService accountService = AccountService.getInstance();
    private static final CatalogService catalogService = CatalogService.getInstance();

  改造後的源代碼如下
  
  private static final AccountService accountService = (AccountService)CustomBeanFactory.getBean("AccountService");
  private static final CatalogService catalogService = (CatalogService)CustomBeanFactory.getBean("CatalogService");

  其他的幾個presentation類以同樣方式改造。這樣,表現層就完成了。關於表現層的其它部分如JSP等一概不動。也許您會說,沒有看出什麼非凡之處的好處啊?你還是額外實現了一個工廠類。別著急,帷幕剛剛開啟,spring是在表現層引入,但您發沒發現:

  • presentation類僅僅面向service類的接口編程,具體"AccountService"是哪個實現類,presentation類不知道,是在spring的配置文件裡配置。(本例中,為了最大限度的保持原來的代碼不作變化,沒有抽象出接口)。Spring鼓勵面向接口編程,因為是如此的方便和自然,當然您也可以不這麼做。
  • CustomBeanFactory這個工廠類為什麼會如此簡單,因為其直接使用了Spring的BeanFactory。Spring從其核心而言,是一個DI容器,其設計哲學是提供一種無侵入式的高擴展性的框架。為了實現這個目標,Spring 大量引入了Java 的Reflection機制,通過動態調用的方式避免硬編碼方式的約束,並在此基礎上建立了其核心組件BeanFactory,以此作為其依靠注入機制的實現基礎。org.springframework.beans包中包括了這些核心組件的實現類,核心中的核心為BeanWrapper和BeanFactory類。

  3.4. 持久層
  
  在討論業務層之前,我們先看一下持久層,如下圖所示:

  
  

  在上文中,我們把iface包下的DAO接口歸為業務層,在這裡不需要做修改。ibatis的sql配置文件也不需要改。要改的是DAO實現類,並在spring的配置文件中配置起來。

  1、修改基類

  所有的DAO實現類都繼續於BaseSqlMapDao類。修改BaseSqlMapDao類如下:
  
  public class BaseSqlMapDao extends SqlMapClientDaoSupport {
    protected static final int PAGE_SIZE = 4;
    protected SqlMapClientTemplate smcTemplate = this.getSqlMapClientTemplate();
    public BaseSqlMapDao() {
   }
  }

  使BaseSqlMapDao類改為繼續於Spring提供的SqlMapClientDaoSupport類,並定義了一個保護屬性smcTemplate,其類型為SqlMapClientTemplate。關於SqlMapClientTemplate類的具體說明請參照附錄中的"Spring中文參考手冊"

  2、修改DAO實現類

  所有的DAO實現類還是繼續於BaseSqlMapDao類,實現相應的DAO接口,但其相應的DAO操作委托SqlMapClientTemplate來執行,以AccountSqlMapDao類為例,部分代碼如下:

    public List getUsernameList() {
      return smcTemplate.queryForList("getUsernameList", null);
    }
    public Account getAccount(String username, String password) {
      Account account = new Account();
      account.setUsername(username);
      account.setPassword(password);
      return (Account) smcTemplate.queryForObject("getAccountByUsernameAndPassword", account);
    }
    public void insertAccount(Account account) {
     smcTemplate.update("insertAccount", account);
     smcTemplate.update("insertProfile", account);
     smcTemplate.update("insertSignon", account);
    }
  

  就這麼簡單,所有函數的簽名都是一樣的,只需要查找替換就可以了!

  3、除去工廠類以及相應的配置文件

  除去DaoConfig.java這個DAO工廠類和相應的配置文件dao.xml,因為DAO的獲取現在要用spring來治理。

  4、DAO在Spring中的配置(applicationContext.xml)
  
        <bean id="dataSource"
  
           class="org.springframework.jdbc.datasource.DriverManagerDataSource">
          <property name="driverClassName">
              <value>org.hsqldb.jdbcDriver</value>
          </property>
          <property name="url">
              <value>jdbc:hsqldb:hsql://localhost/xdb</value>
          </property>
          <property name="username">
              <value>sa</value>
          </property>
          <property name="password">
              <value></value>
          </property>
      </bean>   
      <!-- ibatis sqlMapClient config -->
      <bean id="sqlMapClient"
          class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
          <property name="configLocation">
              <value>
                  classpath:comibatisjpetstorepersistencesqlmapdaosqlsql-map-config.xml
              </value>
          </property>
          <property name="dataSource">
              <ref bean="dataSource"/>
          </property>   
      </bean>
      <!-- Transactions -->
      <bean id="TransactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource">
              <ref bean="dataSource"/>
          </property>
      </bean>
      <!-- persistence layer -->
      <bean id="AccountDao"
          class="com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao">
          <property name="sqlMapClient">
  
               <ref local="sqlMapClient"/>
          </property>
      </bean>

  具體的語法請參照附錄中的"Spring中文參考手冊".在這裡只簡單解釋一下:

  1. 我們首先創建一個數據源dataSource,在這裡配置的是hsqldb數據庫。假如是ORACLE數據庫,driverClassName的值是"oracle.jdbc.driver.OracleDriver",URL的值類似於"jdbc:oracle:thin:@wugfMobile:1521:cdcf".數據源現在由spring來治理,那麼現在我們就可以去掉properties目錄下database.properties這個配置文件了;還有不要忘記修改sql-map-config.xml,去掉對它的引用。

  2. sqlMapClient節點。這個是針對ibatis SqlMap的SqlMapClientFactoryBean配置。實際上配置了一個sqlMapClient的創建工廠類。configLocation屬性配置了ibatis映射文件的名稱。dataSource屬性指向了使用的數據源,這樣所有使用sqlMapClient的DAO都默認使用了該數據源,除非在DAO的配置中另外顯式指定。

  3. TransactionManager節點。定義了事務,使用的是DataSourceTransactionManager.

  4. 下面就可以定義DAO節點了,如AccountDao,它的實現類是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao,使用的SQL配置從sqlMapClient中讀取,數據庫連接沒有非凡列出,那麼就是默認使用sqlMapClient配置的數據源datasource.

  這樣,我們就把持久層改造完了,其他的DAO配置類似於AccountDao.怎麼樣?簡單吧。這次有接口了:) AccountDao接口->AccountSqlMapDao實現。

  3.5. 業務層

  業務層的位置以及相關類,如下圖所示:

  
  

  在這個例子中只有3個業務類,我們以OrderService類為例來改造,這個類是最復雜的,其中涉及了事務。

  1、在ApplicationContext配置文件中增加bean的配置:

    <bean id="OrderService"
          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          <property name="transactionManager">
              <ref local="TransactionManager"></ref>
          </property>
          <property name="target">
              <bean class="com.ibatis.jpetstore.service.OrderService">
                  <property name="itemDao">
                      <ref bean="ItemDao"/>
                  </property>
                  <property name="orderDao">
                      <ref bean="OrderDao"/>
                  </property>
                  <property name="sequenceDao">
                      <ref bean="SequenceDao"/>
  
                   </property>
              </bean>
          </property>
          <property name="transactionAttributes">
              <props>
                  <prop key="insert*">PROPAGATION_REQUIRED</prop>
              </props>
          </property>
      </bean>
  
  

  定義了一個OrderService,還是很輕易懂的。為了簡單起見,使用了嵌套bean,其實現類是com.ibatis.jpetstore.service.OrderService,分別引用了ItemDao,OrderDao,SequenceDao。該bean的insert*實現了事務治理(AOP方式)。TransactionProxyFactoryBean自動創建一個事務advisor, 該advisor包括一個基於事務屬性的pointcut,因此只有事務性的方法被攔截。

  2、業務類的修改

  以OrderService為例:
  public class OrderService {

     /* Private Fields */
    private ItemDao itemDao;
    private OrderDao orderDao;
    private SequenceDao sequenceDao;

    /* Constructors */

    public OrderService() {
    }

  /**
   * @param itemDao 要設置的 itemDao。
   */
  public final void setItemDao(ItemDao itemDao) {
   this.itemDao = itemDao;
  }
  /**
   * @param orderDao 要設置的 orderDao。
   */
  public final void setOrderDao(OrderDao orderDao) {
   this.orderDao = orderDao;
  }
  /**
   * @param sequenceDao 要設置的 sequenceDao。
   */
  public final void setSequenceDao(SequenceDao sequenceDao) {
   this.sequenceDao = sequenceDao;
  }
  //剩下的部分
  …….
  }

  紅色部分為修改部分。Spring采用的是Type2的設置依靠注入,所以我們只需要定義屬性和相應的設值函數就可以了,ItemDao,OrderDao,SequenceDao的值由spring在運行期間注入。構造函數就可以為空了,另外也不需要自己編寫代碼處理事務了(事務在配置中聲明),daoManager.startTransaction();等與事務相關的語句也可以去掉了。和原來的代碼比較一下,是不是處理精簡了很多!可以更關注業務的實現。

    4. 結束語
  
  ibatis是一個功能強大實用的SQL Map工具,可以直接控制SQL,為系統設計提供了更大的自由空間。其提供的最新示例程序JpetStore 4.0,設計優雅,應用了迄今為止很多最佳實踐和設計模式,非常適於學習以及在此基礎上創建輕量級的J2EE WEB應用程序。JpetStore 4.0是基於struts的,本文在此基礎上,最大程度保持了原有設計的精華以及最小的代碼改動量,在業務層和持久化層引入了Spring。在您閱讀了本文以及改造後的源代碼後,會深切的感受到Spring帶來的種種好處:自然的面向接口的編程,業務對象的依靠注入,一致的數據存取框架和聲明式的事務處理,統一的配置文件…更重要的是Spring既是全面的又是模塊化的,Spring有分層的體系結構,這意味著您能選擇僅僅使用它任何一個獨立的部分,就像本文,而它的架構又是內部一致。


 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved