<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    jier的博客

    2007年9月4日 #

    從request獲取各種路徑總結(jié)

    request.getRealPath("url");//虛擬目錄映射為實(shí)際目錄
    request.getRealPath("./");//網(wǎng)頁所在的目錄
    request.getRealPath("../");//網(wǎng)頁所在目錄的上一層目錄

    request.getContextPath();//應(yīng)用的web目錄的名稱
       如http://localhost:7001/bookStore/  
        /bookStore/      =>  [contextPath]  (request.getContextPath())

    獲取Web項(xiàng)目的全路徑
    String strDirPath = request.getSession().getServletContext().getRealPath("/");



    posted @ 2007-10-12 10:30 夏雪 閱讀(2751) | 評論 (2)編輯 收藏

    XDoclet

    XDoclet起步

    XDoclet是一個(gè)代碼生成工具,它可以把你從Java開發(fā)過程中繁重的重復(fù)勞動(dòng)中解脫出來。XDoclet可以讓你的應(yīng)用系統(tǒng)開發(fā)的更加快速,而你只要付比原先更少的努力。你可以把你手頭上的冗長而又必需的代碼交給它幫你完成,你可以逃脫“deployment descriptor地獄”,你還可以使你的應(yīng)用系統(tǒng)更加易于管理。而你所要做的,只不過是在你的注釋里,多加一些類javadoc屬性。然后,你會(huì)驚訝于XDoclet為了做到的一切。
    討論XDoclet,有一點(diǎn)比較容易產(chǎn)生混淆,那就是XDoclet不但是一系統(tǒng)的代碼生成應(yīng)用程序,而且它本身還是一個(gè)代碼生成框架。雖然每個(gè)應(yīng)用系統(tǒng)的細(xì)節(jié)千變?nèi)f化(比如EJB代碼生成和Struts代碼生成是不一樣的,而JMX代碼生成又是另一番景象),但這些代碼生成的核心概念和用法卻是類似的。
    在這一章里,我們將會(huì)看到滲透到所有XDoclet代碼生成程序當(dāng)中的XDoclet框架基礎(chǔ)概念。但在之前,我們先從一個(gè)例子入手。

    2.1 XDoclet in action

    每一個(gè)程序員都會(huì)認(rèn)識到,他們的程序永遠(yuǎn)也不會(huì)完成。總會(huì)有另一些的功能需要添加,另一些的BUG需要修正,或者需要不斷的進(jìn)行重構(gòu)。所以,在代碼里添加注釋,提醒自己(或者其他的程序員)有哪些任務(wù)需要完成已成為一個(gè)共識。
    如何來跟蹤這些任務(wù)是否完成了呢?理想情況下,你會(huì)收集整理出來一個(gè)TODO任務(wù)列表。在這方面,XDoclet提供了一個(gè)強(qiáng)大的TODO生成器,來幫助你完成這個(gè)任務(wù)。這是一個(gè)把XDoclet引入項(xiàng)目的好機(jī)會(huì)。
    2.1.1 一個(gè)公共的任務(wù)
    假設(shè)你正在開發(fā)一個(gè)使用了勺子的類。
    public class Matrix {
      // TODO ? 需要處理當(dāng)沒有勺子的情況
      public void reload() {
        // ...
        Spoon spoon = getSpoon();
        // ...
      }
    }
    理想情況下,你在下一次閱讀這段代碼的時(shí)候,你會(huì)處理這個(gè)“空勺子”(null spoon)的問題。但如果你過了很久才回來看這段代碼,你還會(huì)記得在這個(gè)類里還有一些工作要做嗎?當(dāng)然,你可以在你的源碼里全局搜索TODO,甚至你的集成開發(fā)環(huán)境有一個(gè)內(nèi)建的TODO列表支持。但如果你想把任務(wù)所在的類和方法也標(biāo)注出來的話,XDoclet可以是另一種選擇。XDoclet可以為你的項(xiàng)目生成一個(gè)TODO報(bào)表。

    2.1.2 添加XDoclet標(biāo)簽
    為了把你的TODO項(xiàng)目轉(zhuǎn)換成另一種更加正式的格式,你需要對代碼進(jìn)行一些細(xì)微的改動(dòng)。如下所示:
    public class Matrix {
      /** @todo 需要處理當(dāng)沒有勺子的情況 */
      public void reload() {
        // ...
      }
    }
    這里加入了一個(gè)XDoclet需要的類javadoc標(biāo)簽。XDoclet會(huì)使用這些標(biāo)簽標(biāo)記的信息,以及在這種情況下標(biāo)簽所處的類和方法,來生成TODO報(bào)表。

    2.1.3 與Ant集成
    要生成TODO報(bào)表,你需要確保在你的機(jī)器上正確安裝了XDoclet。
    在Ant任務(wù)里,最少要包含一個(gè)目標(biāo)(例如init目標(biāo))定義<documentdoclet>任務(wù),這是一個(gè)Ant自定義任務(wù),例如:
      <taskdef name=”documentdoclet”
        classname=”xdoclet.modules.doc.DocumentDocletTask”
        classname=”xdoclet.lib.path” />
    這個(gè)<documentdoclet>任務(wù)是XDoclet核心代碼生成應(yīng)用程序中的一個(gè)。
    現(xiàn)在,你可以在Ant構(gòu)建文件中加入一個(gè)todo目標(biāo)調(diào)用這個(gè)任務(wù)來生成TODO報(bào)表,如:
      <target name=”todo” depends=”init”>
        <documentdoclet destdir=”todo”>
          <fileset dir=”${dir.src}”>
            <include name=”**/*.java” />
          </fileset>
          <info/>
        </documentdoclet>
      </target>
    <info>子任務(wù)會(huì)遍歷你的源文件,查找todo標(biāo)簽,并在todo子目錄下生成HTML格式的TODO報(bào)表。

    2.1.4 創(chuàng)建一個(gè)更加職業(yè)化的TODO報(bào)表
    XDoclet生成的TODO報(bào)表可以有一個(gè)更加職業(yè)化的外表。報(bào)表會(huì)列出一個(gè)概覽,顯示在哪個(gè)包哪個(gè)類里有todo項(xiàng)(以及todo項(xiàng)的個(gè)數(shù))。Todo項(xiàng)可以跟在方法、類和域上,從報(bào)表上可以清楚的區(qū)別它們。類級別的todo項(xiàng)會(huì)標(biāo)注class,方法級別的todo項(xiàng)會(huì)在方法簽名上標(biāo)注M。構(gòu)造函數(shù)和域相關(guān)的todo項(xiàng)也會(huì)進(jìn)行相似的標(biāo)注。
    這個(gè)任務(wù)看起來很簡單,但考慮到你所需要做的只是在注釋上添加一些格式化的@todo標(biāo)簽,相對于那種只有人才可以理解的無格式的松散的注釋,這種標(biāo)簽是機(jī)器可讀的,也更容易編程處理。生成的輸出也更容易閱讀并且更加的商業(yè)化。

    2.2 任務(wù)和子任務(wù)
    生成todo報(bào)表,只是XDoclet可以完成的事情當(dāng)中的冰山一角。當(dāng)初,XDoclet因?yàn)榭梢宰詣?dòng)生成EJB繁雜的接口和布署描述文件而聲名鵲起。然而,現(xiàn)在的XDoclet已經(jīng)發(fā)展成了一個(gè)全功能的、面向?qū)傩缘拇a生成框架。J2EE代碼生成只是XDoclet的一個(gè)應(yīng)用方面,它可以完成的任務(wù)已經(jīng)遠(yuǎn)遠(yuǎn)超越了J2EE和項(xiàng)目文檔的生成。

    2.2.1 XDoclet 任務(wù)
    到現(xiàn)在為止,我們一直在討論使用XDoclet生成代碼,但事實(shí)上,更確切的說法應(yīng)該是,我們使用XDoclet的一個(gè)特定的任務(wù)來生成代碼,比如<ejbdoclet>。每一個(gè)XDoclet任務(wù)關(guān)注于一個(gè)特定的領(lǐng)域,并提供這個(gè)領(lǐng)域的豐富的代碼生成工具。
    [定義:任務(wù)(Tasks)是XDoclet里可用的代碼生成應(yīng)用程序的高層概念。]
    在XDoclet里,目前已有如下所示的七個(gè)核心任務(wù)。
    <ejbdoclet>:面向EJB領(lǐng)域,生成EJB、工具類和布署描述符。
    <webdoclet>:面向Web開發(fā),生成serlvet、自定義標(biāo)簽庫和web框架文件。
    <hibernatedoclet>:Hibernate持續(xù),配置文件、Mbeans
    <jdodoclet>:JDO,元數(shù)據(jù),vender configuration
    <jmxdoclet>:JMX,MBean接口,mlets,配置文件。
    <doclet>:使用用戶自定義模板來生成代碼。
    <documentdoclet>:生成項(xiàng)目文件(例如todo列報(bào)表)
    這其中,<ejbdoclet>最常用,并且很多項(xiàng)目也僅僅使用XDoclet來進(jìn)行EJB代碼生成。<webdoclet>是其次一個(gè)常用的代碼生成任務(wù)。當(dāng)然,在一個(gè)項(xiàng)目中同時(shí)使用幾個(gè)XDoclet任務(wù)是可能的(并且也是推薦的),但在這些任務(wù)之間是完全獨(dú)立的,它們彼此之間并不能進(jìn)行直接的交流。

    2.2.2 XDoclet子任務(wù)
    XDoclet的任務(wù)是領(lǐng)域相關(guān)的,而在某個(gè)特定領(lǐng)域的XDoclet任務(wù),又由許許多多緊密耦合在一起的子任務(wù)組成的,這些子任務(wù)每個(gè)都僅僅執(zhí)行一個(gè)非常特定和簡單的代碼生成任務(wù)。
    [定義:子任務(wù)(subtasks)是由任務(wù)提供的單目標(biāo)的代碼生成過程]
    任務(wù)提供子任務(wù)執(zhí)行時(shí)的上下文,并且把這些相關(guān)的子任務(wù)組織管理了起來。任務(wù)會(huì)依賴這些子任務(wù)來生成代碼。在一個(gè)任務(wù)當(dāng)中調(diào)用多個(gè)子任務(wù)來協(xié)同完成各種各樣比較大型的代碼生成任務(wù)是非常常見的。比如,在開發(fā)EJB時(shí),你可能想要為每一個(gè)bean生成一個(gè)home接口,一個(gè)remote接口以及ejb-jar.xml布署描述符文件。這就是在<ejbdoclet>任務(wù)的上下文環(huán)境中的三個(gè)獨(dú)立的代碼生成子任務(wù)。
    子任務(wù)可以隨意的組合排列,以滿足項(xiàng)目代碼生成的需要。某個(gè)XDoclet任務(wù)包含的子任務(wù)經(jīng)常會(huì)共享功能和在源文件中使用相同的XDoclet標(biāo)簽。這意味著當(dāng)你開始一個(gè)任務(wù)的時(shí)候,你可以很容易的集成進(jìn)一個(gè)相關(guān)的子任務(wù),而不需要很大的改動(dòng)。

    子任務(wù)交互
    讓我們以<ejbdoclet>任務(wù)為例,看一下相關(guān)的子任務(wù)之間是如何進(jìn)行關(guān)聯(lián)的。假設(shè)你正在開發(fā)一個(gè)CMP(容器管理持久化)實(shí)體Bean。你想要使用一些<ejbdoclet>的子任務(wù):
    •<deploymentdescriptor>:生成ejb-jar.xml布署描述符文件。
    •<localhomeinterface>:生成local home接口。
    •<localinterface>:生成local接口。
    在執(zhí)行如上子任務(wù)的時(shí)候,你需要標(biāo)記出你的實(shí)體Bean的CMP域。當(dāng)你發(fā)布你的bean的時(shí)候,你還需要在開發(fā)商相關(guān)的布署描述符中提供某個(gè)特定的關(guān)系數(shù)據(jù)庫中的特定表和列與你的CMP實(shí)體Bean的映射關(guān)系。XDoclet可以讓你在原先已存在的CMP XDoclet屬性基礎(chǔ)上再加上一些關(guān)系映射屬性,然后,你就可以在任務(wù)中加入一個(gè)開發(fā)商相關(guān)的子任務(wù)(例如<jboss>或者<weblogic>)來生成布署描述符文件。XDoclet提供了幾乎所有的應(yīng)用服務(wù)器的支持,你只需要一些初始化的小改動(dòng),就可以進(jìn)行這些應(yīng)用服務(wù)器相關(guān)的代碼生成了。
    但那只是冰山一角。你還可以使用<entitycmp>子任務(wù)為為你的bean生成一個(gè)實(shí)體bean接口的實(shí)現(xiàn)子類。如果你使用<valueobject>子任務(wù)來為了你的bean生成值對象,<entityemp>子任務(wù)還會(huì)為你的值對象生成方法的實(shí)現(xiàn)代碼。
    覺得不可思議了吧。可惜XDoclet沒有提供<cupofcoffee>子任務(wù),要不然我們可以喝杯咖啡,休息一下啦。
    這里不是想向你介紹<ejbdoclet>所有的子任務(wù)或者<ejbdoclet>可以完成的所有代碼生成功能,而僅僅是想向你展示一下任務(wù)的子任務(wù)之間是如何工作在一起的。一旦你開始并熟悉了一個(gè)XDoclet 子任務(wù),熟悉另一個(gè)子任務(wù)會(huì)變得非常簡單- 那種每個(gè)子任務(wù)都是孤立的相比,使用這種可以相互協(xié)作的子任務(wù),開發(fā)成本會(huì)顯著的降低,效果也更加的立竿見影。 

    2.3 使用Ant執(zhí)行任務(wù)
    XDoclet“嫁”給了Ant。XDoclet任務(wù)就是Ant的自定義任務(wù),除此以外,沒有其他運(yùn)行XDoclet任務(wù)的方法。所幸的是,Ant已經(jīng)成為了Java構(gòu)建工具事實(shí)上的標(biāo)準(zhǔn),所以這不算什么限制。事實(shí)上,反過來,XDoclet與Ant的這種“親密”關(guān)系使得XDoclet可以參與到任何Ant構(gòu)建過程當(dāng)中去。
    2.3.1 聲明任務(wù)
    XDoclet并沒有和Ant一起發(fā)布,所以如果你想要使用XDoclet的話,就需要單獨(dú)的下載和安裝。在使用任何一個(gè)XDoclet的任務(wù)之前,你首先需要在使用Ant的<taskdef>任務(wù)來聲明它。例如:
    <taskdef name=”ejbdoclet”
      classname=”xdoclet.modules.ejb.EjbDocletTask”
      classpathref=”xdoclet.lib.path”/>
    如果你熟悉Ant的話,你就會(huì)知道這段代碼是告訴Ant加載<ejbdoclet>的任務(wù)定義。當(dāng)然,你也可以以你喜歡的任何方式來命名這個(gè)自定義任務(wù),但最好還是遵守標(biāo)準(zhǔn)的命名規(guī)律以免發(fā)生混淆。classname和classpathref屬性告訴Ant到哪里去找實(shí)現(xiàn)這個(gè)自定義任務(wù)的XDoclet類。如果你想使用其他的XDoclet任務(wù),就必須要類似這樣首先聲明這個(gè)任務(wù)。
    一般共通的做法是,把所有需要使用的XDoclet任務(wù)都放在Ant的一個(gè)目標(biāo)里聲明,這樣在其他的目標(biāo)里如果需要使用這些任務(wù),只要depends這個(gè)任務(wù)就可以了。你可能已經(jīng)在Ant的構(gòu)建文件里包含了init目標(biāo),這就是放置XDoclet任務(wù)聲明的好地方(當(dāng)然如果你沒有,你也可以建一個(gè))。下面的例子就是在一個(gè)init目標(biāo)里加入了<ejbdoclet>和<webdoclet>的聲明:
    <target name=”init”>
      <taskdef name=”documentdoclet”
        classname=”xdoclet.modules.doc.DocumentDocletTask”
        classpathref=”xdoclet.lib.path” />
        <taskdef name=”ejbdoclet”
          classname=”xdoclet.modules.ejb.EjbDocletTask”
          classpathref=”xdoclet.lib.path” />
        <taskdef name=”webdoclet”
          classname=”xdoclet.modules.web.WebDocletTask”
          classpathref=”xdoclet.lib.path” />
    </target>
    現(xiàn)在,任務(wù)聲明好了,XDoclet“整裝待發(fā)”。

    2.3.2 使用任務(wù)
    你可以在任何目標(biāo)里使用聲明好的任務(wù)。在任務(wù)的上下文環(huán)境里,可以調(diào)動(dòng)相關(guān)的子任務(wù)。讓我們看一個(gè)例子,這個(gè)例子調(diào)用了<ejbdoclet>任務(wù)。不要擔(dān)心看不懂語法的細(xì)節(jié),現(xiàn)在你只需要關(guān)心一些基礎(chǔ)概念就可以了。
    <target name=”generateEjb” depends=”init”>
      <ejbdoclet destdir=”${gen.src.dir}”>
        <fileset dir=”${src.dir}”>
          <include name=”**/*Bean.java”/>
        </fileset>
      <deploymentdescriptor destdir=”${ejb.deployment.dir}”/>
      <homeinterface/>
      <remoteinterface/>
      <localinterface/>
       <localhomeinterface/>
      </ejbdoclet>
    </target>
    把任務(wù)想像成一個(gè)子程序運(yùn)行時(shí)需要的一個(gè)配置環(huán)境(記住,子任務(wù)才是真正進(jìn)行代碼生成工作的)。當(dāng)調(diào)用一個(gè)子任務(wù)時(shí),子任務(wù)從任務(wù)繼承上下文環(huán)境,當(dāng)然,你也可以根據(jù)需要隨意的覆蓋這些值。在上面的例子里,因?yàn)?lt;deploymentdescriptor>子任務(wù)生成的布署描述符文件和其他生成各種接口的子任務(wù)生成的Java源文件需要放在不同的位置,所以覆蓋了destdir的屬性值。布署描述符文件需要放在一個(gè)在打包EJB JAR文件的時(shí)候可以容易包含進(jìn)來的地方,而生成的Java代碼則需要放置在一個(gè)可以調(diào)用Java編譯器進(jìn)行編譯的地方。需要這些子任務(wù)之間是緊密關(guān)聯(lián)的,但只要你需要,你可以有足夠的自主權(quán)控制任務(wù)的生成環(huán)境。
    <fileset>屬性同樣被應(yīng)用到所有的子任務(wù)。這是一個(gè)Ant的復(fù)雜類型(相對于文本和數(shù)值的簡單類型),所以以子元素的方式在任務(wù)中聲明。不要把它和子任務(wù)混為一談。當(dāng)然,如果你想在某個(gè)子任務(wù)中另外指定一個(gè)不同的輸入文件集,你也可以在這個(gè)子任務(wù)中放置一個(gè)<fileset>子元素來覆蓋它。
    子任務(wù)的可配置選項(xiàng)遠(yuǎn)遠(yuǎn)不止這些。我們會(huì)在下一章繼續(xù)介紹所有的任務(wù)和子任務(wù),以及常用的配置選項(xiàng)。

    2.4 用屬性標(biāo)注你的代碼
    可重用的代碼生成系統(tǒng)需要輸入來生成感興趣的輸出。一個(gè)解析器生成器也需要一個(gè)語言描述來解析生成解析器。一個(gè)商務(wù)對象代碼生成器需要領(lǐng)域模型來知道要生成哪些商務(wù)對象。XDoclet則需要Java源文件做為輸出來生成相關(guān)的類或者布署/配置文件。
    然而,源文件可能并沒有提供代碼生成所需要的所有信息。考慮一個(gè)基于servlet的應(yīng)用,當(dāng)你想生成web.xml文件的時(shí)候,servlet源文件僅可以提供類名和適當(dāng)?shù)膕ervlet接口方法。其他的信息比如URI pattern映射、servlet需要的初始化參數(shù)等信息并沒有涵蓋。顯而易見,如果class并沒有提供這些信息給你,你就需要自己手動(dòng)在web.xml文件時(shí)填寫這些信息。
    XDoclet當(dāng)然也不會(huì)知道這些信息。幸運(yùn)的是,解決方法很簡單。如果所需信息在源文件時(shí)沒有提供,那就提供它,做法就是在源文件里加入一些XDoclet屬性。XDoclet解析源文件,提取這些屬性,并把它們傳遞給模板,模板使用這些信息生成代碼。

    2.4.1 剖析屬性
    XDoclet屬性其實(shí)就是javadoc的擴(kuò)展。它們在外表上和使用上都有javadoc屬性一樣,可以放置在javadoc文檔注釋里。文檔注釋以/**開始,*/結(jié)尾。下面是一個(gè)簡單的例子:
    /**
    * 這是一段javadoc注釋。
    * 注釋可以被分解成多行,每一行都以星號開始。
    */
    在注釋里的所有文本都被視為javadoc注釋,并且都能夠被XDoclet訪問到。注釋塊一般都與Java源文件中的某個(gè)實(shí)體有關(guān),并緊跟在這個(gè)實(shí)體的前面。沒有緊跟實(shí)體的注釋塊將不會(huì)被處理。類(或者接口)可以有注釋塊,方法和域也可以有自己的注釋塊,比如:
    /**
    * 類注釋塊
    */
    public class SomeClass {
      /** 域注釋塊 */
      private int id;
      /**
    * 構(gòu)造函數(shù)注釋塊
    */
      public SomeClass() {
        // ...
      }
      /**
       * 方法注釋塊
       */
      public int getId() {
        return id;
      }
    }
    注釋塊分成兩部分:描述部分和標(biāo)簽部分。當(dāng)遇到第一個(gè)javadoc標(biāo)簽時(shí),標(biāo)簽部分開始。Javadoc標(biāo)簽也分成兩部分:標(biāo)簽名和標(biāo)簽描述。標(biāo)簽描述是可選的,并且可以多行。例如:
    /**
    * 這是描述部分
    * @tag1 標(biāo)簽部分從這里開始
    * @tag2
    * @tag3 前面一個(gè)標(biāo)簽沒有標(biāo)簽描述。
    * 這個(gè)標(biāo)簽有多行標(biāo)簽描述。
    */
    XDoclet使用參數(shù)化標(biāo)簽擴(kuò)展了javadoc標(biāo)簽。在XDoclet里,你可以在javadoc標(biāo)簽的標(biāo)簽描述部分加入name=”value”參數(shù)。這個(gè)微小的改動(dòng)大大增強(qiáng)了javadoc標(biāo)簽的表達(dá)能力,使得javadoc標(biāo)簽可以用來描述復(fù)雜的元數(shù)據(jù)。下面的代碼顯示了使用XDoclet屬性描述實(shí)體Bean方法:
    /**
    * @ejb.interface-method
    * @ejb.relation
    * name=”blog-entries”
    * role-name=”blog-has-entries”
    * @ejb.value-object
    * compose=”com.xdocletbook.blog.value.EntryValue”
    * compose-name=”Entry”
    * members=”com.xdocletbook.blog.interfaces.EntryLocal”
    * members-name=”Entries”
    * relation=”external”
    * type=”java.util.Set”
    */
    public abstract Set getEntries();
    參數(shù)化標(biāo)簽允許組合邏輯上相關(guān)聯(lián)的屬性。你可以加入描述這個(gè)類的元信息,使得這個(gè)類的信息足夠生成代碼。另外,程序員借由閱讀這樣的元信息,可以很快的理解這個(gè)類是如何使用的。(如果這個(gè)例子上的元信息你看不懂,不要擔(dān)心,在第4章里,我們會(huì)學(xué)到EJB相關(guān)的標(biāo)簽以及它們的涵意。)
    另外,請注意上面的例子中,所有的標(biāo)簽名都以ejb開頭。XDoclet使用namespace.tagname的方式給標(biāo)簽提供了一個(gè)命名空間。這樣做除了可以跟javadoc區(qū)別開來以外,還可以把任務(wù)相關(guān)的標(biāo)簽組織起來,以免任務(wù)之間的標(biāo)簽產(chǎn)生混淆。 

    2.5 代碼生成模式
    XDoclet是一種基于模板的代碼生成引擎。從高層視圖上來看,輸出文件其實(shí)就是由解析執(zhí)行各式各樣的模板生成出來的。如果你理解了模板以及它所執(zhí)行的上下文環(huán)境,就可以確切的認(rèn)識到,XDoclet可以生成什么,不可以生成什么。如果你正在評估XDoclet平臺,理解這些概念是非常重要的。要不然,你可能會(huì)錯(cuò)過XDoclet的許多強(qiáng)大的功能,也可能會(huì)被XDoclet的一些限制感到迷惑。
    XDoclet運(yùn)行在在Ant構(gòu)建文件環(huán)境中,它提供了Ant自定義任務(wù)和子任務(wù)來與XDoclet引擎交互。任務(wù)是子任務(wù)的容器,子任務(wù)負(fù)責(zé)執(zhí)行代碼生成。子任務(wù)調(diào)用模板。模板提供了你將生成代碼的餅干模子。XDoclet解析輸入的源文件,提取出源文件中的XDoclet屬性元數(shù)據(jù),再把這些數(shù)據(jù)提供給模板,驅(qū)動(dòng)模板執(zhí)行。除此之外,模板還可以提供合并點(diǎn)(merge points),允許用戶插入一些模板片斷(合并文件merge files)來根據(jù)需要定制代碼生成。
    2.5.1 模板基礎(chǔ)
    XDoclet使用代碼模板來生成代碼。模板(template)是你想生成文件的原型。模板里使用一些XML標(biāo)簽來指導(dǎo)模板引擎如何根據(jù)輸入類以及它們的元數(shù)據(jù)來調(diào)整代碼的生成。
    [定義:模板(template)是生成代碼或描述文件的抽象模視圖。當(dāng)模板被解析的時(shí)候,指定的細(xì)節(jié)信息會(huì)被填入。]
    模板一般情況下會(huì)有一個(gè)執(zhí)行環(huán)境。模板可能應(yīng)用在一個(gè)類環(huán)境(轉(zhuǎn)換生成transform generation),也有可能應(yīng)用在一個(gè)全局環(huán)境(聚集生成aggregate generation)。轉(zhuǎn)換生成和聚集生成是XDoclet的兩種類型的任務(wù)模式,理解它們之間的區(qū)別對于理解XDoclet是非常重要的。
    當(dāng)你使用XDoclet生成布置描述符文件時(shí),你使用的是聚集生成。布置描述符文件并不僅僅只與一個(gè)類相關(guān),相反,它需要從多個(gè)類里聚集信息到一個(gè)輸入文件。在這種生成模式里,解析一次模板只會(huì)生成一個(gè)輸出文件,不管有多少個(gè)輸入文件。
    在轉(zhuǎn)換生成模式里,模板遇到每一個(gè)源文件就會(huì)解析一次,根據(jù)該文件類的上下文環(huán)境生成輸出。這種生成模式會(huì)為每一個(gè)輸入文件生成一個(gè)輸出文件。
    轉(zhuǎn)換生成模式的一個(gè)很好的例子是生成EJB的local和remote接口。顯然,接口是和Bean類一一相關(guān)的。從每一個(gè)類里提取信息(類以及它的方法、域、接口以及XDoclet屬性等信息)轉(zhuǎn)換出接口。除此以外,不需要其他的信息。
    從實(shí)現(xiàn)里提取出接口似乎有點(diǎn)反向。如果你手寫程序的話,一般來說會(huì)先定義一個(gè)接口,然后再寫一個(gè)類來關(guān)現(xiàn)它。但XDoclet做不到,XDoclet不可能幫你實(shí)現(xiàn)一個(gè)已有接口,因?yàn)樗豢赡軒湍闵赡愕臉I(yè)務(wù)邏輯。當(dāng)然,如果業(yè)務(wù)邏輯可以從接口本身得到(比如JavaBean的get/set訪問器)或者使用XDoclet屬性聲明好,那么生成業(yè)務(wù)邏輯代碼來實(shí)現(xiàn)一個(gè)接口也不是不可能。但一般情況下,這樣做不太現(xiàn)實(shí)。相比而言,提供一個(gè)實(shí)現(xiàn),并描述接口與這個(gè)實(shí)現(xiàn)之間的關(guān)聯(lián)就容易多了。
    聚集生成和轉(zhuǎn)換生成主要區(qū)別在它們的環(huán)境信息上。即使一個(gè)代碼生成任務(wù)中生成一個(gè)Java文件,一般也不常用聚集生成,因?yàn)樯梢粋€(gè)Java類還需要一些重要信息如類所處的包以及你想生成的類名,在這種環(huán)境下是無法提供的。如果一定要使用聚集生成的話,那就需要在另一個(gè)單獨(dú)的地方提供好配置信息了。
    2.5.2 模板標(biāo)簽
    在還沒見到模板長啥樣子之前,我們已經(jīng)比較深入的認(rèn)識它了。那模板文件究竟長啥樣子呢?它有點(diǎn)像JSP文件。它們都包含文件和XML標(biāo)簽,生成輸出文件時(shí)XML標(biāo)簽會(huì)被解析,然后生成文本并顯示在XML標(biāo)簽所處的位置上。除了以XDt為命名空間打頭的XML標(biāo)簽會(huì)被XDoclet引擎解析以外,其余的XML標(biāo)簽XDoclet會(huì)忽略不管。下面的代碼片斷顯示了XDoclet模板的“經(jīng)典造型”:
      public class
        <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
        Extends Observabe {
        static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
          _instance = null;
        public static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClassOf>
          getInstance() {
          if (_instance == null) {
            _instance =
    new <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/>
              </XDtClass:classOf>();
          }
          return _instance;
    }
    }
    研究一下這個(gè)模板,你會(huì)發(fā)現(xiàn),它生成的是一個(gè)類定義。這個(gè)類里定義了一個(gè)靜態(tài)變量instance,并且使用一個(gè)靜態(tài)方法來控制這個(gè)靜態(tài)文件的訪問。借助Java語法,你可以很容易的推斷出那些XDoclet模板標(biāo)簽的目錄是生成類名,雖然對于這個(gè)標(biāo)簽如何工作你還并不是很了解。
    即使你從沒打算過要自己寫模板,但理解模板是如何被解析運(yùn)行的還是很有必要的。遲早你會(huì)調(diào)用到一個(gè)運(yùn)行失敗的XDoclet任務(wù),沒有產(chǎn)生你所期望的輸出,那么最快捷的找出原因的方法就是直接檢查模板文件,看看是哪里出了問題。
    讓我們看一下生成靜態(tài)域定義的片斷:
    static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
        _instance = null;
    在XDoclet的眼里,這段模板代碼很簡單,那就是:
      static <tag/> _instance = null;
    XDoclet解析執(zhí)行標(biāo)簽,如果有輸出的話,把輸入置回到文本里去。有些標(biāo)簽會(huì)執(zhí)行一些運(yùn)算,把輸出放回到一個(gè)流里。這樣的標(biāo)簽稱之為內(nèi)容標(biāo)簽(content tags),因?yàn)樗鼈儺a(chǎn)生內(nèi)容。
    另一種類型的標(biāo)簽稱之為BODY標(biāo)簽。BODY標(biāo)簽在開始和結(jié)束標(biāo)簽之間存在文本。而BODY標(biāo)簽強(qiáng)大就強(qiáng)大在這些文本自己也可以是一斷可以由外圍標(biāo)簽解析的模板片斷。比如在上面的例子里,XDtClass:classOf標(biāo)簽,它們的內(nèi)容就是模板片斷:
      <XDtEjbFacade:remoteFacadeClass/>
    classOf標(biāo)簽解析這段模板,提取出全限制的內(nèi)容,然后剃除前面的包面,只輸出類名。BODY標(biāo)簽并不總是會(huì)解析它的內(nèi)容,在做這件事之前,它們會(huì)事先檢查一些外部判斷條件(比如檢查檢查你正在生成的是一個(gè)接口還是一個(gè)類)。這里標(biāo)標(biāo)簽稱之為條件標(biāo)簽(conditional tags)。還有一些BODY標(biāo)簽提供類似迭代的功能,它的內(nèi)容會(huì)被解析多次。比如一個(gè)標(biāo)簽針對類里的每一個(gè)方法解析一次內(nèi)容。
    XDoclet標(biāo)簽提供了許多高層次的代碼生成功能,但是有時(shí)候,它們可能顯得不夠靈活,或者表達(dá)能力滿足不了你的需要。這時(shí)候,相對于另外開發(fā)一套通用功能的模板引擎相比,你可以選擇擴(kuò)展XDoclet模板引擎。你可以使用更具表述能力、功能更加強(qiáng)大的Java平臺開發(fā)你自己的一套標(biāo)簽。 

    2.6 使用合并定制
    代碼生成系統(tǒng)之所以使用的不多,主要原因就在于它們往往只能生成一些死板的、不夠靈活的代碼。大多數(shù)代碼生成系統(tǒng)不允許你改動(dòng)它們生成的代碼;如果,如果這個(gè)系統(tǒng)不夠靈活,你所能做到的最好的擴(kuò)展就是應(yīng)用繼承擴(kuò)展生成的代碼,或者使用一些共通的設(shè)計(jì)模式(比如Proxy和Adaptor)來滿足你的需要。無論如此,這都不是產(chǎn)生你想生成的代碼的好辦法。代碼生成器最好能做到所生成即所得WYGIWYG(what you generate is what you get),來取代你需要花費(fèi)大量的時(shí)間來粉飾生成出來的并不滿足要求的代碼。所以,對于代碼生成器來說,支持靈活的定制,是生成能夠完全滿足要求的代碼的前提條件。
    XDoclet通過合并點(diǎn)(merge points)支持定制??合并點(diǎn)是在模板文件定義里允許運(yùn)行時(shí)插入定制代碼的地方。有時(shí)候,合并點(diǎn)甚至可以影響到全局代碼的生成,不但允許你添加一些定制內(nèi)容,還可以從根本上改變將要生成出來的東西。
    [定義:合并點(diǎn)(Merge points)是模板預(yù)先定義的允許你在代碼生成的運(yùn)行時(shí)加入定制內(nèi)容的擴(kuò)展點(diǎn)]
    讓我們研究一段從XDoclet源代碼里摘取出來的模板代碼。在為實(shí)體Bean生成主鍵的模板末尾,定義了這樣一個(gè)合并點(diǎn):
      <XDtMerge:merge file=”entitypk-custom.xdt”></XDtMerge:merge>
    如果你在你的merge目錄下創(chuàng)建了一個(gè)名為entitypk-custom.xdt文件,那么這個(gè)模板文件的內(nèi)容將會(huì)在這個(gè)合并點(diǎn)被包含進(jìn)來。你的定制可以執(zhí)行高層模板可以執(zhí)行的所有強(qiáng)大功能,可以進(jìn)行所有模板可以進(jìn)行的運(yùn)算(包括定義自定義標(biāo)簽,定義它們自己的合并點(diǎn))。
    上面的這種合并點(diǎn),為所有的類環(huán)境使用了同一個(gè)文件。當(dāng)然,也可以為每一個(gè)類環(huán)境使用不同的合并文件。如果你不想定制全部的類文件,或者你不想為了某些改動(dòng)而重寫模板的時(shí)候,這會(huì)很有用。不管動(dòng)機(jī)是什么,逐類的合并點(diǎn)很容易識別出來:他們會(huì)在名字里包含一個(gè)XDoclet的逐類標(biāo)記{0}。這里有一個(gè)生成ejb-jar.xml文件里的安全角色引用的例子:
      <XDtMerge:merge file=”ejb-sec-rolerefs-{0}.xml”>
        <XDtClass:forAllClassTags tagName=”ejb:security-role-ref”>
          <security-role-ref>
            <role-name>
    <XDtClass:classTagValue
    tagName=”ejb:security-roleref”
    paramName=”role-name”/>
            </role-name>
            <role-link>
              <XDtClass:classTagValue
                tagName=”ejb:security-roleref”
                paramName=”role-link”/>
            </role-link>
          </security-role-ref>
        </XDtClass:forAllClassTags>
      </XDtMerge:merge>
    這段模板會(huì)遍歷工程里的所有Bean。對于每一個(gè)Bean,XDoclet先從Bean的文件名里提取出Bean名,然后替換{0},再根據(jù)替換后的文件名去尋找合并文件。例如,如果你有一個(gè)名為BlogFacadeBean的Bean,XDoclet會(huì)嘗試尋找一個(gè)名為ejb-src-rolerefs-BlogFacade.xml的合并文件。
    如果找不到這個(gè)合并文件,則這個(gè)<merge>標(biāo)簽的內(nèi)容模板會(huì)被解析。這意味著合并點(diǎn)不僅可以提供定制內(nèi)容,還可以在一個(gè)模板文件里定義一個(gè)替換點(diǎn),當(dāng)定制內(nèi)容不存在的時(shí)候使用替換點(diǎn)里的內(nèi)容。不是所有的XDoclet任務(wù)都提供了有替換內(nèi)容的合并點(diǎn),一般來說,它們更傾向于只提供一個(gè)簡單的合并點(diǎn),僅僅當(dāng)合并文件存在的時(shí)候解析并導(dǎo)入合并文件的內(nèi)容。這取決于任務(wù)的開發(fā)者覺得哪種合并點(diǎn)更符合他的要求。
    還有一點(diǎn)沒有介紹到的是XDoclet如何定位合并文件,每一個(gè)XDoclet任務(wù)或者子任務(wù)都會(huì)提供一個(gè)mergeDir屬性,這個(gè)屬性用于設(shè)置你存放合并文件的目錄。 

    posted @ 2007-09-30 15:59 夏雪 閱讀(1013) | 評論 (0)編輯 收藏

    EJB技術(shù)

     一、EJB技術(shù)簡介
    EJB的全稱是Enterprise java bean。是JAVA中的商業(yè)應(yīng)用組件技術(shù)。EJB結(jié)構(gòu)中的角色 EJB 組件結(jié)構(gòu)是基于組件的分布式計(jì)算結(jié)構(gòu),是分布式應(yīng)用系統(tǒng)中的組件。
    一個(gè)完整的基于EJB的分布式計(jì)算結(jié)構(gòu)由六個(gè)角色組成,這六個(gè)角色可以由不同的開發(fā)商提供,每個(gè)角色所作的工作必須遵循Sun公司提供的EJB規(guī)范,以保證彼此之間的兼容性。這六個(gè)角色分別是EJB組件開發(fā)者(Enterprise Bean Provider) 、應(yīng)用組合者(Application Assembler)、部署者(Deployer)、EJB 服務(wù)器提供者(EJB Server Provider)、EJB 容器提供者(EJB Container Provider)、系統(tǒng)管理員(System Administrator):
    二、EJB中各角色的分析
    1、EJB組件開發(fā)者(Enterprise Bean Provider)
    EJB組件開發(fā)者負(fù)責(zé)開發(fā)執(zhí)行商業(yè)邏輯規(guī)則的EJB組件,開發(fā)出的EJB組件打包成ejb-jar文件。EJB組件開發(fā)者負(fù)責(zé)定義EJB的remote和home接口,編寫執(zhí)行商業(yè)邏輯的EJB class,提供部署EJB的部署文件(deployment descriptor)。部署文件包含EJB的名字,EJB用到的資源配置,如JDBC等。EJB組件開發(fā)者是典型的商業(yè)應(yīng)用開發(fā)領(lǐng)域?qū)<摇?br /> EJB組件開發(fā)者不需要精通系統(tǒng)級的編程,因此,不需要知道一些系統(tǒng)級的處理細(xì)節(jié),如事務(wù)、同步、安全、分布式計(jì)算等。
    2、應(yīng)用組合者(Application Assembler)
    應(yīng)用組合者負(fù)責(zé)利用各種EJB組合一個(gè)完整的應(yīng)用系統(tǒng)。應(yīng)用組合者有時(shí)需要提供一些相關(guān)的程序,如在一個(gè)電子商務(wù)系統(tǒng)里,應(yīng)用組合者需要提供JSP(Java Server Page)程序。
    應(yīng)用組合者必須掌握所用的EJB的home和remote接口,但不需要知道這些接口的實(shí)現(xiàn)。
    3、部署者(Deployer)
    部署者負(fù)責(zé)將ejb-jar文件部署到用戶的系統(tǒng)環(huán)境中。系統(tǒng)環(huán)境包含某種EJB Server和EJB Container。部署者必須保證所有由EJB組件開發(fā)者在部署文件中聲明的資源可用,例如,部署者必須配置好EJB所需的數(shù)據(jù)庫資源。
    部署過程分兩步:部署者首先利用EJB Container提供的工具生成一些類和接口,使EJB Container能夠利用這些類和接口在運(yùn)行狀態(tài)管理EJB。 部署者安裝EJB組件和其他在上一步生成的類到EJB Container中。 部署者是某個(gè)EJB運(yùn)行環(huán)境的專家。
    某些情況下,部署者在部署時(shí)還需要了解EJB包含的業(yè)務(wù)方法,以便在部署完成后,寫一些簡單的程序測試。
    4、EJB 服務(wù)器提供者(EJB Server Provider)
    EJB 服務(wù)器提供者是系統(tǒng)領(lǐng)域的專家,精通分布式交易管理,分布式對象管理及其它系統(tǒng)級的服務(wù)。EJB 服務(wù)器提供者一般由操作系統(tǒng)開發(fā)商、中間件開發(fā)商或數(shù)據(jù)庫開發(fā)商提供。
    在目前的EJB規(guī)范中,假定EJB 服務(wù)器提供者和EJB 容器提供者來自同一個(gè)開發(fā)商,所以,沒有定義EJB 服務(wù)器提供者和EJB容器提供者之間的接口標(biāo)準(zhǔn)。
    5、EJB 容器提供者(EJB Container Provider)
    EJB 容器提供者提供以下功能:
    提供EJB部署工具為部署好的EJB組件提供運(yùn)行環(huán)境 。EJB容器負(fù)責(zé)為EJB提供交易管理,安全管理等服務(wù)。
    EJB 容器提供者必須是系統(tǒng)級的編程專家,還要具備一些應(yīng)用領(lǐng)域的經(jīng)驗(yàn)。EJB 容器提供者的工作主要集中在開發(fā)一個(gè)可伸縮的,具有交易管理功能的集成在EJB 服務(wù)器中的容器。EJB 容器提供者為EJB組件開發(fā)者提供了一組標(biāo)準(zhǔn)的、易用的API訪問EJB 容器,使EJB組件開發(fā)者不需要了解EJB服務(wù)器中的各種技術(shù)細(xì)節(jié)。
    EJB容器提供者負(fù)責(zé)提供系統(tǒng)監(jiān)測工具用來實(shí)時(shí)監(jiān)測EJB容器和運(yùn)行在容器中的EJB組件狀態(tài)。
    6、系統(tǒng)管理員(System Administrator)
    系統(tǒng)管理員負(fù)責(zé)為EJB服務(wù)器和容器提供一個(gè)企業(yè)級的計(jì)算和網(wǎng)絡(luò)環(huán)境。
    系統(tǒng)管理員負(fù)責(zé)利用EJB 服務(wù)器和容器提供的監(jiān)測管理工具監(jiān)測EJB組件的運(yùn)行情況。
    三、EJB的體系結(jié)構(gòu):
    EJB分布式應(yīng)用程序是基于對象組件模型的,低層的事務(wù)服務(wù)用了API技術(shù)。EJB技術(shù)簡化了用JAVA語言編寫的企業(yè)應(yīng)用系統(tǒng)的開發(fā),配置。EJB技術(shù)定義了一組可重用的組件:Enterprise Beans。你可以利用這些組件,象搭積木一樣的建立你的分布式應(yīng)用程序。當(dāng)你把代碼寫好之后,這些組件就被組合到特定的文件中去。每個(gè)文件有一個(gè)或多個(gè)Enterprise Beans,在加上一些配置參數(shù)。最后,這些Enterprise Beans被配置到一個(gè)裝了EJB容器的平臺上。客戶能夠通過這些Beans的home接口,定位到某個(gè)beans,并產(chǎn)生這個(gè)beans的一個(gè)實(shí)例。這樣,客戶就能夠調(diào)用Beans的應(yīng)用方法和遠(yuǎn)程接口。
    EJB服務(wù)器作為容器和低層平臺的橋梁管理著EJB容器和函數(shù)。它向EJB容器提供了訪問系統(tǒng)服務(wù)的能力。例如:數(shù)據(jù)庫的管理和事務(wù)的管理,或者對于其它的Enterprise的應(yīng)用服務(wù)器。所有的EJB 實(shí)例都運(yùn)行在EJB容器中。容器提供了系統(tǒng)級的服務(wù),控制了EJB的生命周期。EJB中的有一些易于使用的管理工具如:Security--配置描述器(The Deployment descriptor)定義了客戶能夠訪問的不同的應(yīng)用函數(shù)。容器通過只允許授權(quán)的客戶訪問這些函數(shù)來達(dá)到這個(gè)效果。Remote Connectivity--容器為遠(yuǎn)程鏈接管理著低層的通信issues,而且對Enterprise Beas的開發(fā)者和客戶都隱藏了通信細(xì)節(jié)。EJB的開發(fā)者在編寫應(yīng)用方法的時(shí)候,就象是在條用本地的平臺一樣的。客戶也不清楚他們調(diào)用的方法可能是在遠(yuǎn)程被處理的。Life Cycle managment--客戶簡單的創(chuàng)建一個(gè)Enterprise beans的實(shí)例,并通常取消一個(gè)實(shí)例。而容器管理著Enterprise Beans的實(shí)例,使Enterprise Beans實(shí)現(xiàn)最大的效能和內(nèi)存利用率。容器能夠這樣來激活和使Enterprise Beans失效,保持眾多客戶共享的實(shí)例池。等等。  Trasction management-配置描述器定義了Enterprise beans 的事務(wù)處理的需求。容器管理著那些管理分布式事務(wù)處理的復(fù)雜的issues。這些事務(wù)可能要在不同的平臺之間更新數(shù)據(jù)庫。容器使這些事務(wù)之間互相獨(dú)立,互不干擾。保證所有的更新數(shù)據(jù)庫都是成功發(fā)生的,否者,就回滾到事務(wù)處理之前的狀態(tài)。
    EJB 組件是基于分布式事務(wù)處理的企業(yè)級應(yīng)用程序的組件。所有的EJB都有如下的特點(diǎn):EJB包含了處理企業(yè)數(shù)據(jù)的應(yīng)用邏輯。定義了EJB的客戶界面。這樣的界面不受容器和服務(wù)器的影響。于是,當(dāng)一個(gè)EJB被集合到一個(gè)應(yīng)用程序中去時(shí),不用更改代碼和重新編譯。EJB能夠被定制 各種系統(tǒng)級的服務(wù),例如安全和事務(wù)處理的特性,都不是屬于EJB類的。而是由配置和組裝應(yīng)用程序的工具來實(shí)現(xiàn)。 有兩種類型的EJB: Session beans 和 entity beans.Session beans是一種作為單用戶執(zhí)行的對象。作為對遠(yuǎn)程的任務(wù)請求的相應(yīng),容器產(chǎn)生一個(gè)Session beans 的實(shí)例。一個(gè)Session beans有一個(gè)用戶.從某種程度上來說,一個(gè)Session bean 對于服務(wù)器來說就代表了它的那個(gè)用戶.Session beans 也能用于事務(wù),它能夠更新共享的數(shù)據(jù),但它不直接描繪這些共享的數(shù)據(jù)。Session beans 的生命周期是相對較短的。典型的是,只有當(dāng)用戶保持會(huì)話的時(shí)候,Session beans 才是活著的。一旦用戶退出了,Session beans 就不再與用戶相聯(lián)系了。Session beans被看成是瞬時(shí)的,因?yàn)槿绻萜鞅罎⒘耍敲从脩舯仨氈匦陆⒁粋€(gè)新的Session對象來繼續(xù)會(huì)話。
    Session bean典型的聲明了與用戶的互操作或者會(huì)話。也就是說,Session bean了在客戶會(huì)話期間,通過方法的調(diào)用,掌握用戶的信息。一個(gè)具有狀態(tài)的Session bean稱為有狀態(tài)的Session bean.當(dāng)用戶終止與Session beans互操作的時(shí)候.會(huì)話終止了,而且,bean 也不再擁有狀態(tài)值。Session bean也可能是一個(gè)無狀態(tài)的 session bean.無狀態(tài)的Session beans并不掌握它的客戶的信息或者狀態(tài)。用戶能夠調(diào)用beans的方法來完成一些操作。但是,beans只是在方法調(diào)用的時(shí)候才知道用戶的參數(shù)變量。當(dāng)方法調(diào)用完成以后,beans并不繼續(xù)保持這些參數(shù)變量。這樣,所有的無狀態(tài)的session beans的實(shí)例都是相同的,除非它正在方法調(diào)用期間。這樣,無狀態(tài)的Session beans就能夠支持多個(gè)用戶.容器能夠聲明一個(gè)無狀態(tài)的Session beans.能夠?qū)⑷魏蜸ession beans指定給任何用戶.
    Entity Beans對數(shù)據(jù)庫中的數(shù)據(jù)提供了一種對象的視圖。例如:一個(gè)Entity bean能夠模擬數(shù)據(jù)庫表中一行相關(guān)的數(shù)據(jù)。多個(gè)client能夠共享訪問同一個(gè)Entity bean.多個(gè)client也能夠同時(shí)的訪問同一個(gè)Entity bean.Entity beans通過事務(wù)的上下文來訪問或更新下層的數(shù)據(jù)。這樣,數(shù)據(jù)的完整性就能夠被保證。Entity Beans能存活相對教長的時(shí)間,并且狀態(tài)是持續(xù)的。只要數(shù)據(jù)庫中的數(shù)據(jù)存在,Entity beans就一直存活。而不是按照應(yīng)用程序或者服務(wù)進(jìn)程來說的。即使EJB容器崩潰了,Entity beans也是存活的。Entity Beans生命周期能夠被容器或者 Beans自己管理。如果由容器控制著保證 Entity beans持續(xù)的issus。如果由Beans自己管理,就必須寫Entity beans的代碼,包括訪問數(shù)據(jù)庫的調(diào)用。
    Entity Beans是由主鍵(primary key 一種唯一的對象標(biāo)識符)標(biāo)識的。通常,主鍵與標(biāo)識數(shù)據(jù)庫中的一塊數(shù)據(jù),例如一個(gè)表中的一行,的主鍵是相同的。主鍵是client能夠定位特定的數(shù)據(jù)塊。
    四、開發(fā)EJB
    1、類介紹:
    開發(fā)EJB的主要步驟一般來說,整個(gè)的開發(fā)步驟(開發(fā),配置,組裝)包括如下幾個(gè)方面。開發(fā):首先要定義三個(gè)類:Bean類本身,Bean的本地和遠(yuǎn)程接口類。 配置:配置包括產(chǎn)生配置描述器--這是一個(gè)XML文件、聲明了Enterprise Bean的屬性、綁定了bean的class文件(包括stub文件和skeleton文件)。最后將這些配置都放到一個(gè)jar文件中。還需要在配置器中定義環(huán)境屬性。組裝應(yīng)用程序:包括將Enterprise beans安裝到Server服務(wù)器中,測試各層的連接情況。程序組裝器將若干個(gè)Enterprise Beans與其它的組件結(jié)合起來。組合成一個(gè)完整的應(yīng)用程序。或者將若干個(gè)Enterprise beans組合成一個(gè)復(fù)雜的Enterprise Bean。管理Enterprise Bean。
    我們必須定義和編寫一些EJB中的基本類。如Enterprise bean類:這是Enterprise bean內(nèi)部應(yīng)用邏輯的實(shí)現(xiàn)。編寫Enterprise bean的遠(yuǎn)程接口類。編寫Enterprise bean的本地接口類。說明主鍵類,主鍵類只是對于Entity bean才需要的。在Enterprise bean的配置描述器中指定主鍵的名字。Enterprise beans提供者定義了遠(yuǎn)程接口和本地接口,實(shí)現(xiàn)了EJB類本身。Remote接口中提供了客戶調(diào)用EJB實(shí)現(xiàn)的應(yīng)用邏輯函數(shù)的接口。而home接口提供了產(chǎn)生和定位remote接口實(shí)例的方法。
    在Enterprise bean本身類的實(shí)現(xiàn),本地home接口,遠(yuǎn)程remote接口之間并沒有正式的聯(lián)系(例如繼承關(guān)系)。但是,在三個(gè)類里聲明的方法卻必須遵守EJB里面定義的規(guī)范。例如: 你在Enterprise bean里面聲明了一個(gè)應(yīng)用程序的方法或者說應(yīng)用邏輯。也在beans的remote接口中聲明了這個(gè)方法,那么,這兩個(gè)地方必須要同樣的名字。Bean的實(shí)現(xiàn)里面必須至少有一個(gè)Create()方法:ejbCreate()。但是可以有多個(gè)帶有不同參數(shù)的create()方法。  在home接口中,也必須有相同的方法定義(參數(shù)的個(gè)數(shù)相同)。EjbCreate()方法返回的一個(gè)容器管理的持久對象。它們都返回一個(gè)容器管理持久性的主鍵值。但是,在home的相應(yīng)的Create()方法中返回值的類型是remote接口。
    注意:實(shí)體bean的實(shí)現(xiàn)的ejbCreate方法有點(diǎn)不同。實(shí)體bean可以不定義ejbCreate方法。如果實(shí)體只是通過應(yīng)用程序或通過數(shù)據(jù)庫管理程序的途徑被加到數(shù)據(jù)庫中,實(shí)體bean就省略了ejbCreate方法。EjbCreate返回的值是主鍵類型。如果ejbCreate方法是容器管理持久性的實(shí)體bean的方法,它的返回值就是NULL類型。如果實(shí)體bean實(shí)現(xiàn)了Bean管理的持久性,ejbCreate方法就返回值類型就是主鍵類型。容器的任務(wù)是把各接口和Enterprise bean的實(shí)現(xiàn)類結(jié)合起來。保證在編譯時(shí)和運(yùn)行時(shí),各接口和實(shí)現(xiàn)類是相對應(yīng)的。
    EJB的實(shí)現(xiàn)類,各接口要從不同的基類中繼承下來。一個(gè)會(huì)話bean必須實(shí)現(xiàn)基類javax.ejb.SessionBean。而實(shí)體bean必須實(shí)現(xiàn)基類javax.ejb.EntiyBean。這些EJB的基類都是從javax.ejb.EnterpriseBean繼承而來。而javax.ejb.EnterpriseBean又是從java.io.Serializable繼承而來。每一個(gè)Enterprise Bean都必須有一個(gè)remote接口。Remote接口定義了應(yīng)用程序規(guī)定客戶可以調(diào)用的邏輯操作。這些是一些可以由客戶調(diào)用的公共的方法,通常由Enterprise beans類來實(shí)現(xiàn)。注意,Enterprise bean的客戶并不直接訪問Bean。而是通過remote接口來訪問。Enterprise bean類的remote接口擴(kuò)展了javax.ejb.EJBObject類的公共java接口。而Javax.ejb.EJBObject是所有remote接口的基類。其代碼如下:
    package javax.ejb;
    public interface EJBObject extends java.rmi.Remote{
    public EJBHome getEJBHome() throws java.rmi.RemoteException;
    public Object getPrimaryKey() throws java.rmi.RemoteException;
    public void Remove() throws java.rmi.RemtoeException, java.rmi.RemoveException
    public Handle getHandle() throws java.rmi.RemoteException;
    boolean isIdentical (EJBObject p0) throws java.rmi.RemoteException;
    }
    getEJBHome()方法允許你取得一個(gè)相關(guān)的Home接口。對于 實(shí)體Bean,用getPrimaryKey()方法獲得實(shí)體Bean的主鍵值。Remove()可以刪除一個(gè)Enterprise bean。具體的語義在各種不同類型的enterprise beans的生命周期中,由上下文中解釋的。方法getHandle()返回了一個(gè)Enterprise bean實(shí)例的持久的句柄。IsIndentical()方法允許你去比較Enterprise beans是否相同。
    2、方法:
    所有的remote接口中的方法必須聲明為公共(public)的,并必須拋出java.rmi.RemotException異常。另外,所有的remote接口中的方法定義的參數(shù)和都必須是在RMI-IIOP中有效的。對每一個(gè)在remote接口中定義的方法,在Enterprise bean 類里面都要有相應(yīng)的方法。相應(yīng)的方法必須要有同樣的名字,同樣類型和數(shù)量的參數(shù),同樣的返回值,而且還要拋出同樣的例外。 如下代碼顯示了一個(gè)ATM例子的會(huì)話bean的remote接口Atm,。里面聲明了一個(gè)應(yīng)用方法transfer()。黑體部分表示EJB規(guī)范中必須要有的內(nèi)容。Remote接口必須擴(kuò)展javax.ejb.EJBObject類。從客戶端調(diào)用的Enterprise bean的每一個(gè)方法都必須在remote接口中聲明。Transfer()方法拋出了兩個(gè)意外。其中InSufficientFundsException例外是應(yīng)用程序定義的意外。
    Public interface Atm extends javax.ejb.EJBObject{
    Public void transfer(String Source, String Target, float amount)
    Throws java.rmi.RemoteException, InSufficientFundsException;
    }
    Home接口必須定義一個(gè)或多個(gè)的Create()方法。每一個(gè)這樣的Create()方法都必須命名為Create。并且,它的參數(shù),不管是類型還是數(shù)量都必須與bean類里面的ejbCreate()方法對應(yīng)。注意,home接口中的Create()方法和bean類中ejbCreate()方法的返回值類型是不同的。實(shí)體bean的home接口還包含find()方法。 每一個(gè)Home接口都擴(kuò)展了javax.ejb.EJBHome接口。如下代碼顯示了javax.ejb.EJBHome接口的定義:
    package javax.ejb;
    public interface EJBHome extends java.rmi.Remote() {
    void remove(Handle handle) throws java.rmi.RemoteException,RemoveException;
    void remove(Object primarykey) throws java.rmi.RemoteException,RemoveException;
    EJBMetaData getEJBMetaData() throws RemoteException;
    Homehandle getHomeHandle() throws RemoteException;
    }
    這里提供了兩個(gè)remove()方法來刪除Enterprise bean的實(shí)例。第一個(gè)remove方法是通過句柄來刪除一個(gè)Enterprise bean的實(shí)例。第二個(gè)remove方法通過主鍵來刪除一個(gè)Enterprise bean的實(shí)例。 在眾多的Enterprise bean實(shí)例中,句柄唯一的標(biāo)識一個(gè)實(shí)例。一個(gè)句柄與它引用的Enterprise bean有相同的生命期。考慮一個(gè)實(shí)體對象,客戶可以通過一個(gè)句柄來重新獲得相應(yīng)的Enterprise bean的實(shí)例。一個(gè)句柄能夠?qū)?yīng)一個(gè)Enterprise bean對象的多個(gè)實(shí)例。例如,即使當(dāng)Enterprise bean對象所在的主機(jī)崩潰了,或者Enterprise bean對象在不同的機(jī)器之間移動(dòng),句柄仍是有效的。這里的句柄是Serialized句柄,與CORBA中的字符串化的CORBA對象的引用是相似的概念。在EJBHome接口中的第二個(gè)remove操作通過其主鍵來決定要?jiǎng)h除的Enterprise bean。主鍵可以是擴(kuò)展了Java Object類的任何類型,但是,必須要實(shí)現(xiàn)Java的Serializable接口。主鍵是標(biāo)識實(shí)體bean的主要的方法。通常,主鍵是數(shù)據(jù)庫中的一個(gè)關(guān)鍵字,唯一的定義了由實(shí)體bean代表的數(shù)據(jù)。
    方法getEJBMetaData()返回了Enterprise bean對象的metadata接口。這個(gè)接口允許客戶獲得Enterprise bean的metadata信息。當(dāng)開發(fā)工具來編譯鏈接應(yīng)用程序的時(shí)候,或者配置工具來配置的時(shí)候,可能會(huì)用到metadata信息。Javax.ejb.EJBMetadata接口提供了獲得javax.ejb.EJBHome接口,home類,remote接口,還有獲得主鍵的方法。也提供了一個(gè)isSesson()的方法來確定在放這個(gè)home接口的對象是會(huì)話bean還是實(shí)體bean。IsStatelessSession()方法指示這個(gè)會(huì)話bean是有狀態(tài)還是無狀態(tài)的。如下代碼顯示了javax.ejb.EJBMetadata接口的定義部分的代碼。
    Public javax.ejb; Public interface EJBMetaData{
    EJBHome getEJBHome();
    Class getHomeInterfaceClass();
    Class getRemoteInterfaceClasss();
    Class getPrimaryKeyClass();
    Boolean isSession();
    Boolean isStatelesssSession();
    }
    對每一個(gè)Create()方法,EJB規(guī)范定義了如下的命名約定。它的返回值是會(huì)話bean的remote接口的類型。方法的名字只能是Create()。對會(huì)話bean類中的每一個(gè)ejbCreate()方法都必須有一個(gè)Create()與之對應(yīng)。 對于每一個(gè)Create()方法的參數(shù)的類型和數(shù)量都必須與會(huì)話bean類中的ejbCreate()方法相對應(yīng)。方法必須拋出java.rmi.RemoteException例外。 方法必須拋出javax.rmi.CreateExeption例外。 Create()方法的參數(shù)是用來初始化新的會(huì)話bean對象的。 如下代碼顯示了一個(gè)會(huì)話bean對象的不同的Create()方法,其中必須的部分用粗體顯示:
    public interface AtmHome extends javax.ejb.EJBHome{
    Atm create() throws java.rmi.RemoteException,javax.ejb.CreateException;
    Atm create(Profile preferredProfile)
    Throws java.rmi.RemoteExeption,javax.ehrows java.rmi.RemoteException,RemoveException;
    EJBMetaData getEJBMetaData() throws RemoteException;
    Homehandle getHomeHandle() throws RemoteException;
    }
    這里提供了兩個(gè)remove()方法來刪除Enterprise bean的實(shí)例。第一個(gè)remove方法是通過句柄來刪除一個(gè)Enterprise bean的實(shí)例。第二個(gè)remove方法通過主鍵來刪除一個(gè)Enterprise bean的實(shí)例。在眾多的Enterprise bean實(shí)例中,句柄唯一的標(biāo)識一個(gè)實(shí)例。一個(gè)句柄與它引用的Enterprise bean有相同的生命期。考慮一個(gè)實(shí)體對象,客戶可以通過一個(gè)句柄來重新獲得相應(yīng)的Enterprise bean的實(shí)例。一個(gè)句柄能夠?qū)?yīng)一個(gè)Enterprise bean對象的多個(gè)實(shí)例。例如,即使當(dāng)Enterprise bean對象所在的主機(jī)崩潰了,或者Enterprise bean對象在不同的機(jī)器之間移動(dòng),句柄仍是有效的。這里的句柄是Serialized句柄,與CORBA中的字符串化的CORBA對象的引用是相似的概念。
    在EJBHome接口中的第二個(gè)remove操作通過其主鍵來決定要?jiǎng)h除的Enterprise bean。主鍵可以是擴(kuò)展了Java Object類的任何類型,但是,必須要實(shí)現(xiàn)Java的Serializable接口。主鍵是標(biāo)識實(shí)體bean的主要的方法。通常,主鍵是數(shù)據(jù)庫中的一個(gè)關(guān)鍵字,唯一的定義了由實(shí)體bean代表的數(shù)據(jù)。方法getEJBMetaData()返回了Enterprise bean對象的metadata接口。這個(gè)接口允許客戶獲得Enterprise bean的metadata信息。當(dāng)開發(fā)工具來編譯鏈接應(yīng)用程序的時(shí)候,或者配置工具來配置的時(shí)候,可能會(huì)用到metadata信息。Javax.ejb.EJBMetadata接口提供了獲得javax.ejb.EJBHome接口,home類,remote接口,還有獲得主鍵的方法。也提供了一個(gè)isSesson()的方法來確定在放這個(gè)home接口的對象是會(huì)話bean還是實(shí)體bean。IsStatelessSession()方法指示這個(gè)會(huì)話bean是有狀態(tài)還是無狀態(tài)的。如下代碼顯示了javax.ejb.EJBMetadata接口的定義部分的代碼。
    Public javax.ejb;
    Public interface EJBMetaData{
    EJBHome getEJBHome();
    Class getHomeInterfaceClass();
    Class getRemoteInterfaceClasss();
    Class getPrimaryKeyClass();
    Boolean isSession();
    Boolean isStatelesssSession();
    }
    五、EJB的編程環(huán)境:
    1、 使用Jbuilder
    Jbuilder與EJB Container能夠進(jìn)行無縫連接。Jbuilder和Inprise的應(yīng)用服務(wù)器包括了所有的開發(fā)和配置Enterprise Beans的工具以及所需要的庫:運(yùn)行和管理Enterprise Bean的容器、命名服務(wù)、 事務(wù)服務(wù)、Java數(shù)據(jù)庫、開發(fā)Enterprise Beans所需要的API、一個(gè)增強(qiáng)的java-to-iiop編譯器,支持值類型和RMI信號等等。
    Jbuilder還提供了一個(gè)快速開發(fā)應(yīng)用程序Enterprise Beans的工具和向?qū)АMㄟ^簡單而且直觀的步驟,向?qū)椭憬⒁粋€(gè)Enterprise Bean。自己設(shè)定某些缺省值,產(chǎn)生了bean的模板,在上面,我們可以增加我們自己的應(yīng)用邏輯。Jbuilder也提供了一個(gè)EJB的接口生成向?qū)АO驅(qū)г贓nterprise Bean的公共方法基礎(chǔ)上生成了Remote接口和Home接口。Jbuilder還提供一個(gè)配置器的向?qū)椭覀冎鸩降慕ML描述器文件。并將生成的Stubs集中到一個(gè)jar文件中。
    2、使用Jbuilder之外的集成環(huán)境:
    如果你使用其它的除了別的集成環(huán)境(IDE)。要確定使用了集成環(huán)境IDE所帶的容器工具。也要驗(yàn)證IDE是否支持EJB規(guī)范的相應(yīng)的版本,還要確定它是否正確的支持EJB的API。
    要確定JD到所支持的EJB容器的版本。可以通過檢查Inprise的安裝說明來確定EJB容器所支持的支持JDK的版本。
    在配置Enterprise Bean的時(shí)候,你必須使用Inprise的應(yīng)用服務(wù)器所提供的工具。這些工具能夠編輯和修改第三方的代理商提供的Inprise配置描述器。還能夠驗(yàn)證配置描述器,能夠驗(yàn)證bean的源代碼。
    六、一個(gè)簡單的HELLO例子
    1、安裝Apusic Application Server
    Note:以下以Linux為例,來說明Apusic Application Server的安裝過程。其他平臺的安裝,可參考Apusic Application Server安裝手冊。
    下載JDK1.2,Apusic Application Server必須運(yùn)行在JDK1.2以上環(huán)境中。可從以下站點(diǎn)下載最新JDK。
    http://java.sun.com
    下載Apusic Application Server
    Apusic Application Server 試用版可從以下網(wǎng)址得到:
    http://www.apusic.com/download/enter.jsp
    在下載完成后,你可以得到一個(gè)包裹文件apusic.zip,選定安裝目錄,假設(shè)安裝到/usr下,則用以下命令:
       cd /usr
    jar xvf apusic.zip
    /usr下會(huì)出現(xiàn)一個(gè)目錄apusic,Apusic Application Server的所有程序都被解壓到/usr/apusic下。
    將以下路徑加入到CLASSPATH中
    /usr/apusic/lib/apusic.jar
    $JAVA_HOME/lib/tools.jar
    用以下命令運(yùn)行Apusic Application Server
    java -Xms64m com.apusic.server.Main -root /usr/apusic
    2、定義EJB遠(yuǎn)程接口(Remote Interface)
    任何一個(gè)EJB都是通過Remote Interface被調(diào)用,EJB開發(fā)者首先要在Remote Interface中定義這個(gè)EJB可以被外界調(diào)用的所有方法。執(zhí)行Remote Interface的類由EJB生成工具生成。
    以下是HelloBean的Remote Inteface程序:
    package ejb.hello;
    import java.rmi.RemoteException;
    import java.rmi.Remote;
    import javax.ejb.*;
    public interface Hello extends EJBObject, Remote {
    file://this method just get "Hello World" from HelloBean.
    public String getHello() throws RemoteException;
    }
    3、定義Home Interface
    EJB容器通過EJB的Home Interface來創(chuàng)建EJB實(shí)例,和Remote Interface一樣,執(zhí)行Home Interface的類由EJB生成工具生成。
    以下是HelloBean 的Home Interface程序:
    package ejb.hello;
    import javax.ejb.*;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    import java.util.*;
    /**
    * This interface is extremely simple it declares only
    * one create method.
    */
    public interface HelloHome extends EJBHome {
    public Hello create() throws CreateException,
    RemoteException;
    }
    4、寫EJB類
    在EJB類中,編程者必須給出在Remote Interface中定義的遠(yuǎn)程方法的具體實(shí)現(xiàn)。EJB類中還包括一些 EJB規(guī)范中定義的必須實(shí)現(xiàn)的方法,這些方法都有比較統(tǒng)一的實(shí)現(xiàn)模版,編程者只需花費(fèi)精力在具體業(yè)務(wù)方法的實(shí)現(xiàn)上。
    以下是HelloBean的代碼:
    package ejb.hello;
    import javax.ejb.*;
    import java.util.*;
    import java.rmi.*;
    public class HelloBean implements SessionBean {
    static final boolean verbose = true;
    private transient SessionContext ctx;
    // Implement the methods in the SessionBean
    // interface
    public void ejbActivate() {
    if (verbose)
    System.out.println("ejbActivate called");
    }
    public void ejbRemove() {
    if (verbose)
    System.out.println("ejbRemove called");
    }
    public void ejbPassivate() {
    if (verbose)
    System.out.println("ejbPassivate called");
    }
    /**
    * Sets the session context.
    *
    * @param SessionContext
    */
    public void setSessionContext(SessionContext ctx) {
    if (verbose)
    System.out.println("setSessionContext called");
    this.ctx = ctx;
    }
    /**
    * This method corresponds to the create method in
    * the home interface HelloHome.java.
    * The parameter sets of the two methods are
    * identical. When the client calls
    * HelloHome.create(), the container allocates an
    * instance of the EJBean and calls ejbCreate().
    */
    public void ejbCreate () {
    if (verbose)
    System.out.println("ejbCreate called");
    }
    /**
    * **** HERE IS THE BUSINESS LOGIC *****
    * the getHello just return a "Hello World" string.
    */
    public String getHello()
    throws RemoteException
    {
    return("Hello World");
    }
    }
    5、創(chuàng)建ejb-jar.xml文件
    ejb-jar.xml文件是EJB的部署描述文件,包含EJB的各種配置信息,如是有狀態(tài)Bean(Stateful Bean) 還是無狀態(tài)Bean(Stateless Bean),交易類型等。ejb-jar.xml文件的詳細(xì)信息請參閱EJB規(guī)范。以下是HelloBean的配置文件:
    <?xml version="1.0"?>
    <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.2//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_2.dtd">
    <ejb-jar>
    <enterprise-beans>
    <session>
    <ejb-name>Hello</ejb-name>
    <home>ejb.hello.HelloHome</home>
    <remote>ejb.hello.Hello</remote>
    <ejb-class>ejb.hello.HelloBean</ejb-class>
    <session-type>Stateless</session-type>
    <transaction-type>Container</transaction-type>
    </session>
    </enterprise-beans>
    <assembly-descriptor>
    <container-transaction>
    <method>
    <ejb-name>Hello</ejb-name>
    <method-name>*</method-name>
    </method>
    <trans-attribute>Required</trans-attribute>
    </container-transaction>
    </assembly-descriptor>
    </ejb-jar>
      6、編譯和部署
      編譯Java源文件并將編譯后class和ejb-jar.xml打包到Hello.jar
      mkdir build
      mkdir build/META-INF
    cp ejb-jar.xml build/META-INF
      javac -d build *.java
      cd build
    jar cvf Hello.jar META-INF ejb
      cd ..
    用EJB工具生成可部署到Apusic Application Server中運(yùn)行的jar文件:
    java com.apusic.ejb.utils.EJBGen -d /usr/apusic/classes/Hello.jar build/Hello.jar
    增加/usr/apusic/classes/Hello.jar到CLASSPATH中
    將Hello.jar加入到Apusic Application Server配置文件中。在/usr/apusic/config/server.xml 加入以下幾行:
    <module>
    <ejb>
    <ejb-uri>classes/Hello.jar</ejb-uri>
    <bean>
    <ejb-name>Hello</ejb-name>
    <jndi-name>HelloHome</jndi-name>
    </bean>
    </ejb>
    </module>
    啟動(dòng)服務(wù)器
    java -Xms64m com.apusic.server.Main -root /usr/apusic
    7、寫客戶端調(diào)用程序
    您可以從Java Client,JSP,Servlet或別的EJB調(diào)用HelloBean。
    調(diào)用EJB有以下幾個(gè)步驟:
    通過JNDI(Java Naming Directory Interface)得到EJB Home Interface
    通過EJB Home Interface 創(chuàng)建EJB對象,并得到其Remote Interface
    通過Remote Interface調(diào)用EJB方法
    以下是一個(gè)從Java Client中調(diào)用HelloBean的例子:
    package ejb.hello;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import java.util.Hashtable;
    import javax.ejb.*;
    import java.rmi.RemoteException;

    /**
    * @author Copyright (c) 2000 by Apusic, Inc. All Rights Reserved.
    */
    public class HelloClient{
    public static void main(String args[]){
    String url = "rmi://localhost:6888";
    Context initCtx = null;
    HelloHome hellohome = null;
    try{
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.apusic.jndi.InitialContextFactory");
    env.put(Context.PROVIDER_URL, url);
    initCtx = new InitialContext(env);
    }catch(Exception e){
    System.out.println("Cannot get initial context: " + e.getMessage());
    System.exit(1);
    }
    try{
    hellohome = (HelloHome)initCtx.lookup("HelloHome");
    Hello hello = hellohome.create();
    String s = hello.getHello();
    System.out.println(s);
    }catch(Exception e){
    System.out.println(e.getMessage());
    System.exit(1);
    }
    }
    }
    運(yùn)行HelloClient,可得到以下輸出:
    Hello World

    http://yingmingpan.bokee.com/tb.b?diaryId=14817948

    posted @ 2007-09-30 10:20 夏雪 閱讀(379) | 評論 (0)編輯 收藏

    轉(zhuǎn): EJB2.0雨夜教程之一

    EJB2.0雨夜教程之一

    EJB雨夜原創(chuàng)講解

    初識EJB

    EJB是什么呢?
    EJB是一個(gè)J2EE體系中的組件.再簡單的說它是一個(gè)能夠遠(yuǎn)程調(diào)用的javaBean.
    它同普通的javaBean有兩點(diǎn)不同.
    第一點(diǎn),就是遠(yuǎn)程調(diào)用.
    第二點(diǎn),就是事務(wù)的功能,我們在EJB中聲明的事務(wù)會(huì)通過容器幫助我們來處理.
    支持EJB的SERVER有以下幾個(gè):
    Weblogic
    Webspere
    Appserver
    Jboss

    我選用的是weblogic+JBuilder9開發(fā).

    回過來我們繼續(xù)說這個(gè)EJB的原理.
    它是分布式的.這個(gè)的意思其實(shí)很簡單不需要想太復(fù)雜.
    就是我們把事情不是交給一個(gè)人去處理.而是把事情劃分一下交給多個(gè)人處理,
    而多個(gè)人處理之后我們讓這個(gè)分配的人來把最后得到的結(jié)合處理.

    EJB我們首先要明確它是放在服務(wù)器端的組件.
    一共有三種EJB
    1,SessionBean 復(fù)雜處理業(yè)務(wù)邏輯的.
    2,EntityBean  處理數(shù)據(jù)
    3,MessageBean 消息Bean異步,耦合的處理.

    那么誰能調(diào)用EJB呢?
    也或者說EJB被誰調(diào)用呢?
    我們說它就是放在server的一個(gè)能夠遠(yuǎn)程調(diào)用的javaBean.
    所以它可以被Java的語言調(diào)用servlet,jsp等都o(jì)k的說.
    而且它還可以被C++語言調(diào)用.很強(qiáng)大吧.
    EJB的最基本的出發(fā)點(diǎn)就是推動(dòng)Java在服務(wù)器端的編程能力.
    所以呢它的功能就我個(gè)人來看太強(qiáng)悍了..= =

    這個(gè)遠(yuǎn)程調(diào)用是通過什么呢.
    Java是通過RMI-JRMP(java遠(yuǎn)程方法調(diào)用)協(xié)議來調(diào)用.EJB就是通過這個(gè)來實(shí)現(xiàn)的.
    C++是通過Corba-iiop協(xié)議來調(diào)用的.這個(gè)協(xié)議支持兩端不同語言的調(diào)用.
    Corba是公共對象請求代理服務(wù)
    iiop是網(wǎng)絡(luò)內(nèi)部對象協(xié)議.

    下面我們來簡單說一下這個(gè)分布式的調(diào)用。
    客戶端發(fā)送一個(gè)請求給服務(wù)器
    首先是傳給stub代理類它發(fā)送參數(shù)給skeleton委托類然后這個(gè)類調(diào)用我們的實(shí)現(xiàn)類取得結(jié)果再遠(yuǎn)路返回。
    這樣一個(gè)分布處理就結(jié)束了。
    后面會(huì)具體的結(jié)合代碼分析。
    先記住這個(gè)大致的結(jié)構(gòu)就好。

    我們寫一個(gè)EJB需要做那些工作呢?
    1,寫一個(gè)接口繼承EJBObject        這個(gè)類作為遠(yuǎn)程接口
    2,寫一個(gè)接口繼承EJBHome          這個(gè)類里有一個(gè)方法是create()它返回接口類型。
    3,寫一個(gè)Bean繼承SessionBean,        這個(gè)類里包含一個(gè)create()方法和一個(gè)業(yè)務(wù)方法。
    4,寫一個(gè)ejb-jar.xml              這個(gè)xml是把上面的三個(gè)文件組合起來
    5,寫一個(gè)weblogic-ejb-jar.xml   這個(gè)xml是連接查找source的作用
                                     (不同的server會(huì)是不同的。這里選用的是weblogic)

    上面的兩個(gè)xml文件需要放在META-INF目錄下。
    而以上這些類都需要打包在一個(gè)jar文件中然后在server部署。
    這樣就完成了EJB的部署
    例如:
    我們寫個(gè)簡單的計(jì)算吧。
    (先聲明下吧本教程的說明都是我自己的理解,
    也許并不是一些文檔上的描述那么正規(guī)但是很方便理解的。
    如果有實(shí)在無法茍同的地方大家多多包含。這是為了便于理解的講解)
    AddCount.java這個(gè)就是我們繼承EJBObject的類.

    import javax.ejb.*;
    import java.rmi.*;

    public interface AddCount extends EJBObject
    {
            public int addCount(int a,int b) throws RemoteException;
    }
    接口的作用.在這里強(qiáng)調(diào)一下吧.所有實(shí)現(xiàn)這個(gè)接口的類都會(huì)是這個(gè)接口的類型.
    同時(shí)都包含接口的方法的實(shí)現(xiàn).
    這個(gè)接口中的方法也就是在以后會(huì)實(shí)現(xiàn)的方法.我們這里要做的是一個(gè)加法的運(yùn)算.


    AddCountHome.java這個(gè)是繼承EJBHome的類.它里面包含的這個(gè)create()返回的是AddCount類型對象.


    import javax.ejb.*;
    import java.rmi.*;

    public interface AddCountHome extends EJBHome
    {
            public AddCount create() throws RemoteException,CreateException;
    }





    下面這個(gè)是我們的Bean.這個(gè)類繼承了SessionBean
    import javax.ejb.*;

    public class AddCountBean implements SessionBean
    {
            public void ejbCreate()
            {
            }
            public void setSessionContext(SessionContext ctx)
                           throws EJBException,
                                  java.rmi.RemoteException
            {
            }
            public void ejbRemove()
                   throws EJBException,
                          java.rmi.RemoteException
            {
            }

            public void ejbActivate()
                     throws EJBException,
                            java.rmi.RemoteException
            {
            }
            public void ejbPassivate()
                      throws EJBException,
                             java.rmi.RemoteException
            {
            }
            public int addCount(double a,double b)
            {
                    return a+b;
            }
    }
    這個(gè)里面我們實(shí)現(xiàn)了業(yè)務(wù)的方法addCount(){a+b;}
    同時(shí)需要指出一下它的其他方法(這些僅僅簡單指出后面的教程有詳細(xì)說明)
    首先是ejbCreate()這個(gè)方法實(shí)際上是對我們的遠(yuǎn)程接口的實(shí)現(xiàn)類的初始化.
    setSessionContext(SessionContext ctx)設(shè)置context.容器是在這個(gè)方法之后產(chǎn)生的實(shí)例.
    ejbRemove()毫無疑問是一個(gè)實(shí)例結(jié)束移除.
    ejbActivate()激活方法.它的作用是激活鈍化.
    ejbPassivate()鈍化方法.當(dāng)實(shí)例的內(nèi)容長時(shí)間不進(jìn)行處理的時(shí)候就會(huì)鈍化.也就是閑置的意思.
    以上這幾個(gè)是SessionBean的基本方法.希望大家可以自己慢慢理解.
    之后也會(huì)反復(fù)說到這些的.




    下面我們寫xml文件
    首先是ejb-jar.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
    <ejb-jar>
        <enterprise-beans>
            <session>
                <display-name>first</display-name>
                <ejb-name>add</ejb-name>

                <home>AddCountHome</home>
                <remote>AddCount</remote>
                <ejb-class>AddCountBean</ejb-class>

                <session-type>Stateless</session-type>
                <transaction-type>Container</transaction-type>     
            </session>
        </enterprise-beans>
        <assembly-descriptor>
            <container-transaction>
                <method>
                    <ejb-name>add</ejb-name>
                    <method-name>*</method-name>
                </method>
                <trans-attribute>Required</trans-attribute>
            </container-transaction>
            
        </assembly-descriptor>
    </ejb-jar>

    下面這個(gè)是weblogic-ejb-jar.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 8.1.0 EJB//EN' 'http://www.bea.com/servers/wls810/dtd/weblogic-ejb-jar.dtd'>
    <weblogic-ejb-jar>
        <weblogic-enterprise-bean>
            <ejb-name>
                    add  
                    </ejb-name>
            
            <jndi-name>rainnight</jndi-name>

        </weblogic-enterprise-bean>
    </weblogic-ejb-jar>


    我們接下來寫測試類

    import java.util.*;
    import java.naming.*;
    import java.rim.*;
    public class AddTest{
            public static void main(String bb[])throws Exception{
                    Hashtable hash = new Hashtable();
                    hash.put(Context.INITIAL_COUNTEXT_FACTORY,
                                    "weblogic.jndi.WLInitialContextFactory");
                    hash.put(Context.PROVIDER_URL,
                                    "t3://localhost:7001");
                    Object obj = context.lookup("rainnight");
                    AddCountHome home = (AddCountHome)PortableRemoteObject(obj,AddCountHome.class);
                    AddCount local = home.create();
                    System.out.println(local.add(1,1));
            }
    }

    測試類的hash是初始化我們的weblogic的xml信息.
    這里的t3://localhost:7001是weblogic的特殊協(xié)議指向的是ip位置.
    然后lookup("rainnight")查找jndi,而實(shí)際上也就是通過這個(gè)jndi找到我們的ejb組件.
    通過得到的對象我們對比一下是不是Home類型.
    然后我們執(zhí)行home.create()產(chǎn)生AddCount的實(shí)例
    最后調(diào)用方法就ok了.


    下面是如何執(zhí)行這個(gè)的方法.
    第一步,jdk,weblogic.需要快些下載安裝啦..
    第二步,配置環(huán)境變量.基本的java的環(huán)境變量如何配置我就不說了.
           這里需要特別指出要把java目錄下的lib中的tools.jar加載到我們的class_path中.
           然后我們再把weblogic的目錄下的server目錄下的lib中的weblogic.jar找到也加載進(jìn)來.
           這樣我們編譯需要的東西就ok了.
    第三步,編譯java類.
    第四步,打包.jar cvf AddCount.jar *.class META-INF/*.xml
    第五步,java weblogic.appc AddCount.jar
    第六步,部署到weblogic中.
    第七步,運(yùn)行Test類.

    posted @ 2007-09-29 16:41 夏雪 閱讀(2387) | 評論 (2)編輯 收藏

    java jar命令

    jar

    功能說明
      
    Java歸檔工具

    語法
      jar [ 命令選項(xiàng) ] [manifest] destination input-file [input-files]

    補(bǔ)充說明
       jar工具是個(gè)java應(yīng)用程序,可將多個(gè)文件合并為單個(gè)JAR歸檔文件。jar是個(gè)多用途的存檔及壓縮工具,它基于ZIP和ZLIB壓縮格式。然而, 設(shè)計(jì)jar的主要目的是便于將java applet或應(yīng)用程序打包成單個(gè)歸檔文件。將applet或應(yīng)用程序的組件(.class 文件、圖像和聲音)合并成單個(gè)歸檔文件時(shí),可以用java代理(如瀏覽器)在一次HTTP事務(wù)處理過程中對它們進(jìn)行下載,而不是對每個(gè)組件都要求一個(gè)新連 接。這大大縮短了下載時(shí)間。jar還能壓縮文件,從而進(jìn)一步提高了下載速度。此外,它允許applet的作者對文件中的各個(gè)項(xiàng)進(jìn)行簽名,因而可認(rèn)證其來 源。jar工具的語法基本上與tar命令的語法相同。

    命令選項(xiàng)
      -c 在標(biāo)準(zhǔn)輸出上創(chuàng)建新歸檔或空歸檔。

      -t 在標(biāo)準(zhǔn)輸出上列出內(nèi)容表。

      -x[file] 從標(biāo)準(zhǔn)輸入提取所有文件,或只提取指定的文件。如果省略了file,則提取所有文件;否則只提取指定文件。

      -f 第二個(gè)參數(shù)指定要處理的jar文件。在-c(創(chuàng)建)情形中,第二個(gè)參數(shù)指的是要?jiǎng)?chuàng)建的jar文件的名稱(不是在標(biāo)準(zhǔn)輸出上)。在-t(表(或-x(抽取)這兩種情形中,第二個(gè)參數(shù)指定要列出或抽取的jar文件。

      -v 在標(biāo)準(zhǔn)錯(cuò)誤輸出設(shè)備上生成長格式的輸出結(jié)果。

      -m 包括指定的現(xiàn)有清單文件中的清單信息。用法舉例:“jar cmf myManifestFile myJarFile *.class”

      -0 只儲存,不進(jìn)行 ZIP 壓縮。

      -M 不創(chuàng)建項(xiàng)目的清單文件。

       -u 通過添加文件或更改清單來更新現(xiàn)有的 JAR 文件。例如:“jar -uf foo.jar foo.class”將文件 foo.class 添加到現(xiàn)有的JAR文件foo.jar中,而“jar umf manifest foo.jar”則用manifest中的信息更新foo.jar的清單。

      -C 在執(zhí)行 jar 命令期間更改目錄。例如:“jar -uf foo.jar -C classes *”將classes目錄內(nèi)的所有文件加到foo.jar中,但不添加類目錄本身。

    程序示例
      1:將當(dāng)前目錄下所有CLASS文件打包成新的JAR文件:
      jar cf file.jar *.class

      2:顯示一個(gè)JAR文件中的文件列表
      jar tf file.jar

      3:將當(dāng)前目錄下的所有文件增加到一個(gè)已經(jīng)存在的JAR文件中
      jar cvf file.jar *

    posted @ 2007-09-29 15:29 夏雪 閱讀(14038) | 評論 (0)編輯 收藏

    AOP是什么?

    為什么要區(qū)分J2EE容器和J2EE應(yīng)用系統(tǒng)?

      我們知道,J2EE應(yīng)用系統(tǒng)只有部署在J2EE容器中才能運(yùn)行,那么為什么劃分為J2EE容器和J2EE應(yīng)用系統(tǒng)? 通過對J2EE容器運(yùn)行機(jī)制的分析(見我的電子教材“EJB實(shí)用原理”),我們可以發(fā)現(xiàn):實(shí)際上J2EE容器分離了一般應(yīng)用系統(tǒng)的一些通用功能,例如事務(wù)機(jī)制、安全機(jī)制以及對象池或線程池等性能優(yōu)化機(jī)制。

      這些功能機(jī)制是每個(gè)應(yīng)用系統(tǒng)幾乎都需要的,因此可以從具體應(yīng)用系統(tǒng)中分離出來,形成一個(gè)通用的框架平臺,而且,這些功能機(jī)制的設(shè)計(jì)開發(fā)有一定難度,同時(shí)運(yùn)行的穩(wěn)定性和快速性都非常重要,必須經(jīng)過長時(shí)間調(diào)試和運(yùn)行經(jīng)驗(yàn)積累而成,因此,形成了專門的J2EE容器服務(wù)器產(chǎn)品,如Tomcat JBoss、Websphere、WebLogic等。

      從J2EE系統(tǒng)劃分為J2EE容器和J2EE應(yīng)用系統(tǒng)兩個(gè)方面,我們已經(jīng)看到一種分散關(guān)注的思路(separation of concerns)。

    分散關(guān)注

      將通用需求功能從不相關(guān)類之中分離出來;同時(shí),能夠使得很多類共享一個(gè)行為,一旦行為發(fā)生變化,不必修改很多類,只要修改這個(gè)行為就可以。

       AOP就是這種實(shí)現(xiàn)分散關(guān)注的編程方法,它將“關(guān)注”封裝在“方面”中。

    AOP是什么?

      AOP是OOP的延續(xù),是Aspect Oriented Programming的縮寫,意思是面向方面編程。AOP實(shí)際是GoF設(shè)計(jì)模式的延續(xù),設(shè)計(jì)模式孜孜不倦追求的是調(diào)用者和被調(diào)用者之間的解耦,AOP可以說也是這種目標(biāo)的一種實(shí)現(xiàn)。

      舉例:假設(shè)有在一個(gè)應(yīng)用系統(tǒng)中,有一個(gè)共享的數(shù)據(jù)必須被并發(fā)同時(shí)訪問,首先,將這個(gè)數(shù)據(jù)封裝在數(shù)據(jù)對象中,稱為Data Class,同時(shí),將有多個(gè)訪問類,專門用于在同一時(shí)刻訪問這同一個(gè)數(shù)據(jù)對象。

      為了完成上述并發(fā)訪問同一資源的功能,需要引入鎖Lock的概念,也就是說,某個(gè)時(shí)刻,當(dāng)有一個(gè)訪問類訪問這個(gè)數(shù)據(jù)對象時(shí),這個(gè)數(shù)據(jù)對象必須上鎖Locked,用完后就立即解鎖unLocked,再供其它訪問類訪問。

      使用傳統(tǒng)的編程習(xí)慣,我們會(huì)創(chuàng)建一個(gè)抽象類,所有的訪問類繼承這個(gè)抽象父類,如下:

    abstract class Worker{

      abstract void locked();
      abstract void accessDataObject();
      abstract void unlocked();

    }


      缺點(diǎn):
    • accessDataObject()方法需要有“鎖”狀態(tài)之類的相關(guān)代碼。
    • Java只提供了單繼承,因此具體訪問類只能繼承這個(gè)父類,如果具體訪問類還要繼承其它父類,比如另外一個(gè)如Worker的父類,將無法方便實(shí)現(xiàn)。
    • 重用被打折扣,具體訪問類因?yàn)橐舶?#8220;鎖”狀態(tài)之類的相關(guān)代碼,只能被重用在相關(guān)有“鎖”的場合,重用范圍很窄。

      仔細(xì)研究這個(gè)應(yīng)用的“鎖”,它其實(shí)有下列特性:
    • “鎖”功能不是具體訪問類的首要或主要功能,訪問類主要功能是訪問數(shù)據(jù)對象,例如讀取數(shù)據(jù)或更改動(dòng)作。
    • “鎖”行為其實(shí)是和具體訪問類的主要功能可以獨(dú)立、區(qū)分開來的。
    • “鎖”功能其實(shí)是這個(gè)系統(tǒng)的一個(gè)縱向切面,涉及許多類、許多類的方法。如下圖:

      因此,一個(gè)新的程序結(jié)構(gòu)應(yīng)該是關(guān)注系統(tǒng)的縱向切面,例如這個(gè)應(yīng)用的“鎖”功能,這個(gè)新的程序結(jié)構(gòu)就是aspect(方面)

      在這個(gè)應(yīng)用中,“鎖”方面(aspect)應(yīng)該有以下職責(zé):

      提供一些必備的功能,對被訪問對象實(shí)現(xiàn)加鎖或解鎖功能。以保證所有在修改數(shù)據(jù)對象的操作之前能夠調(diào)用lock()加鎖,在它使用完成后,調(diào)用unlock()解鎖。

    AOP應(yīng)用范圍

      很明顯,AOP非常適合開發(fā)J2EE容器服務(wù)器,目前JBoss 4.0正是使用AOP框架進(jìn)行開發(fā)。
      具體功能如下:
    Authentication 權(quán)限
    Caching 緩存
    Context passing 內(nèi)容傳遞
    Error handling 錯(cuò)誤處理
    Lazy loading 懶加載
    Debugging  調(diào)試
    logging, tracing, profiling and monitoring 記錄跟蹤 優(yōu)化 校準(zhǔn)
    Performance optimization 性能優(yōu)化
    Persistence  持久化
    Resource pooling 資源池
    Synchronization 同步
    Transactions 事務(wù)

    AOP有必要嗎?

      當(dāng)然,上述應(yīng)用范例在沒有使用AOP情況下,也得到了解決,例如JBoss 3.XXX也提供了上述應(yīng)用功能,但是沒有使用AOP。

      但是,使用AOP可以讓我們從一個(gè)更高的抽象概念來理解軟件系統(tǒng),AOP也許提供一種有價(jià)值的工具。可以這么說:因?yàn)槭褂肁OP結(jié)構(gòu),現(xiàn)在JBoss 4.0的源碼要比JBoss 3.X容易理解多了,這對于一個(gè)大型復(fù)雜系統(tǒng)來說是非常重要的。

      從另外一個(gè)方面說,好像不是所有的人都需要關(guān)心AOP,它可能是一種架構(gòu)設(shè)計(jì)的選擇,如果選擇J2EE系統(tǒng),AOP關(guān)注的上述通用方面都已經(jīng)被J2EE容器實(shí)現(xiàn)了,J2EE應(yīng)用系統(tǒng)開發(fā)者可能需要更多地關(guān)注行業(yè)應(yīng)用方面aspect。


    AOP具體實(shí)現(xiàn)

      AOP是一個(gè)概念,并沒有設(shè)定具體語言的實(shí)現(xiàn),它能克服那些只有單繼承特性語言的缺點(diǎn)(如Java),目前AOP具體實(shí)現(xiàn)有以下幾個(gè)項(xiàng)目:

      AspectJ (TM): 創(chuàng)建于Xerox PARC. 有近十年歷史,成熟
      缺點(diǎn):過于復(fù)雜;破壞封裝;需要專門的Java編譯器。

      動(dòng)態(tài)AOP:使用JDK的動(dòng)態(tài)代理API或字節(jié)碼Bytecode處理技術(shù)。

      基于動(dòng)態(tài)代理API的具體項(xiàng)目有:
      JBoss 4.0 JBoss 4.0服務(wù)器
      nanning 這是以中國南寧命名的一個(gè)項(xiàng)目,搞不清楚為什么和中國相關(guān)?是中國人發(fā)起的?

      基于字節(jié)碼的項(xiàng)目有:
      aspectwerkz 
      spring ?

    在以后其它文章中,我將繼續(xù)對AOP概念進(jìn)行分析,和大家一起學(xué)習(xí)進(jìn)步。


    轉(zhuǎn)載   板橋里人 http://www.jdon.com 2004/01/09

    posted @ 2007-09-04 10:18 夏雪 閱讀(251) | 評論 (0)編輯 收藏

    主站蜘蛛池模板: 亚洲精品无码专区在线播放| 在线观看免费大黄网站| 亚洲AⅤ男人的天堂在线观看| 久久精品国产亚洲麻豆| 亚洲国产日韩在线成人蜜芽| 亚洲国产成人久久综合一区77| 日本阿v免费费视频完整版| 四虎必出精品亚洲高清| 国产成人无码a区在线观看视频免费| 精品国产免费一区二区三区香蕉 | 日韩免费人妻AV无码专区蜜桃| 成人精品国产亚洲欧洲| 91丁香亚洲综合社区| gogo全球高清大胆亚洲| 免费看a级黄色片| 国产乱码免费卡1卡二卡3卡| 日韩人妻无码精品久久免费一| a级成人免费毛片完整版| 曰韩无码AV片免费播放不卡| 亚洲爆乳无码专区www| 亚洲狠狠成人综合网| 亚洲国产精品午夜电影| 中文字幕在线观看亚洲| 亚洲成在人天堂在线| 亚洲成Av人片乱码色午夜| 亚洲中文字幕无码永久在线 | 免费一级毛片正在播放| 免费国产黄网站在线观看视频| 西西人体免费视频| 在线人成免费视频69国产| 中文字幕在线免费观看视频| 成人免费av一区二区三区| a级毛片免费高清视频| 国产裸体美女永久免费无遮挡 | 亚洲自偷自偷在线制服 | 日本永久免费a∨在线视频| 在线观看亚洲电影| 日本特黄特色AAA大片免费| 四虎影视永久在线精品免费| 羞羞视频免费网站日本| 一级黄色毛片免费看|