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

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

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

    菠蘿三國

    大江東去,浪淘盡...
    隨筆 - 34, 文章 - 47, 評論 - 22, 引用 - 0
    數據加載中……

    2008年1月18日

    用instsrv與srvany在windows 建立服務

    用instsrv與srvany在windows 建立服務

    instsrv.exe  srvany.exe

    這兩個文件是MS批量生產的,網上應該能爛下載。

    首先將這兩個文件放到自定的路徑中。例如放在C:\根目錄下

    在CMD對話框中輸入 c:\instsrv.exe  servername c:\ srvany.exe 回車

    其中servername是你所需要的服務名。

    之后你需要進入注冊表進行相應的設置,在注冊表的:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\servername

    中簡歷Parameters子項,然后在其中建立一個字符串Application,雙擊該字符串,輸入如下格式的語句:

    C:\ABC\DEF\XXX.exe +Xms256M +Xmx256M -nodbcheck -minspan60 -retry1000

    其中的256M與服務占用內存大小有關,這就要看機器的配置了。

    修改結束后推出,使用命令services.msc進入服務界面,找到你剛剛定制的服務,雙擊進入,之后選擇“登陸”,再選中“本地登陸”并確定。之后手動啟動服務即可。

    C:\service\instsrv.exe adslSrv "C:\service\srvany.exe"

    posted @ 2009-04-19 14:44 菠蘿 閱讀(1145) | 評論 (0)編輯 收藏

    網上在線字典詞典大全

    翻譯類字典詞典

    posted @ 2008-04-07 12:26 菠蘿 閱讀(528) | 評論 (0)編輯 收藏

    XP/2003訪問XP的用戶驗證問題


      首先關于啟用Guest為什么不能訪問的問題:  
       
      1、默認情況下,XP   禁用Guest帳戶  
      2、默認情況下,XP的本地安全策略禁止Guest用戶從網絡訪問  
      3、默認情況下,XP的   本地安全策略   ->   安全選項   里,"帳戶:使用空密碼用戶只能進行控制臺登陸"是啟用的,也就是說,空密碼的任何帳戶都不能從網絡訪問只能本地登陸,Guest默認空密碼......  
       
      所以,如果需要使用Guest用戶訪問XP的話,要進行上面的三個設置:啟用Guest、修改安全策略允許Guest從網絡訪問、禁用3里面的安全策略或者給Guest加個密碼。  
       
      有時還會遇到另外一種情況:訪問XP的時候,登錄對話框中的用戶名是灰的,始終是Guest用戶,不能輸入別的用戶帳號。  
       
      原因是這個安全策略在作怪。默認情況下,XP的訪問方式是"僅來賓"的方式,那么你訪問它,當然就固定為Guest不能輸入其他用戶帳號了。  
       
      所以,訪問XP最簡單的方法就是:不用啟用Guest,僅修改上面的安全策略為"經典"就行了。別的系統訪問XP就可以自己輸入帳戶信息。  
       
      至于訪問2003,默認情況下2003禁用Guest,但是沒有   XP   那個討厭的默認自相矛盾的來賓方式共享,所以可以直接輸入用戶名密碼訪問。

    posted @ 2008-03-07 20:43 菠蘿 閱讀(398) | 評論 (0)編輯 收藏

    RSS的格式及解釋(轉)

         摘要:   閱讀全文

    posted @ 2008-01-30 09:27 菠蘿 閱讀(2153) | 評論 (1)編輯 收藏

    AJAX讀取rss的代碼(轉)

     
    2007-02-02 15:48

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
      " <html xmlns=" <head>
    <title>this is test</title>

    <script type="text/javascript">
    var xmlHttp;

    function createXMLHttpRequest() {
        if (window.ActiveXObject) {
            xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
        } 
        else if (window.XMLHttpRequest) {
            xmlHttp = new XMLHttpRequest();
        }
    }

    function readRSS(url) {
        createXMLHttpRequest();
        xmlHttp.onreadystatechange = handleStateChange;
        xmlHttp.open("GET", url, true);
        xmlHttp.send(null);
      
      
    }
        
    function handleStateChange() {
        if(xmlHttp.readyState == 4) {
            if(xmlHttp.status == 200) {
                ResultSet();
            }
        }
    }

    function ResultSet() {
        var results = xmlHttp.responseXML;
        var title = null;
        var item = null;
        var link=null;
        //得到channel
        var ccc=results.getElementsByTagName("channel");
         var headtitle=ccc[0].getElementsByTagName("title")[0].firstChild.nodeValue;
         var headlink=ccc[0].getElementsByTagName("link")[0].firstChild.nodeValue;
         var cell = document.createElement("div");
            cell.innerHTML="<h1><a href="+headlink+" target=_blank>"+headtitle+"</a></h1><br>";
            document.getElementById("result").appendChild(cell);
           //得到items
        var items = results.getElementsByTagName("item");
        for(var i = 0; i < items.length; i++) {
            item = items[i];
            link=item.getElementsByTagName("link")[0].firstChild.nodeValue;
            title = item.getElementsByTagName("title")[0].firstChild.nodeValue;
            var cell = document.createElement("div");
            cell.innerHTML="<a href="+link+" target=_blank>"+title+"</a><br>";
           document.getElementById("result").appendChild(cell);
        }

    }
    function readrss1()
    {
        var url=document.getElementById("txturl").value;
        if(url=="")
        {
            alert("請輸入RSS地址");
            }
        else
            {
                readRSS(url);
                }
        }

    </script>
    </head>

    <body">
      <h1>ajax讀rss示例</h1>
      <form >
          
      <a href="javascript:readRSS('http://m.tkk7.com/rss.aspx')">blogjava原創區 </a>&nbsp     
       <a href="javascript:readRSS('http://beginner.blogjava.net/rss.aspx')">blogjava新手區 </a> &nbsp 
        <a href="javascript:readRSS('http://life.blogjava.net/rss.aspx')">blogjava非技術區 </a> &nbsp 
         <a href="javascript:readRSS('http://general.blogjava.net/rss.aspx')">綜合區 </a>
         <br>
         輸入一個RSS地址:<input type="text" value="
    http://m.tkk7.com/wujun/rss.aspx" size=50 id="txturl">
         <input type="button" value="查 看" onclick="readrss1()">
         
      </form>
        <div id="result"></div>
    </body>
    </html>

    posted @ 2008-01-29 22:49 菠蘿 閱讀(354) | 評論 (0)編輯 收藏

    Session機制

    摘要:

    雖然session機制在web應用程序中被采用已經很長時間了,但是仍然有很多人不清楚session機制的本質,以至不能正確的應用這一技術。本文將詳細討論session的工作機制并且對在Java web application中應用session機制時常見的問題作出解答。
     
    一、術語session
    在我的經驗里,session這個詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語境下的含義是相同的。

    session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個 session。有時候我們可以看到這樣的話“在一個瀏覽器會話期間,...”,這里的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關閉這個期間 ①。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關的一系列動作,比如從登錄到選購商品到結賬登出這樣一個網上購物的過程,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接,也有可能是指含義①,其中的差別只能靠上下文來推斷②。

    然而當session一詞與網絡協議相關聯時,它又往往隱含了“面向連接”和/或“保持狀態”這樣兩個含義, “面向連接”指的是在通信雙方在通信之前要先建立一個通信的渠道,比如打電話,直到對方接了電話通信才能開始,與此相對的是寫信,在你把信發出去的時候你并不能確認對方的地址是否正確,通信渠道不一定能建立,但對發信人來說,通信已經開始了。“保持狀態”則是指通信的一方能夠把一系列的消息關聯起來,使得消息之間可以互相依賴,比如一個服務員能夠認出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢。這一類的例子有“一個TCP session”或者 “一個POP3 session”③。

    而到了web服務器蓬勃發展的時代,session在web開發語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器之間保持狀態的解決方案④。有時候session也用來指這種解決方案的存儲結構,如“把xxx保存在session 里”⑤。由于各種用于web開發的語言在一定程度上都提供了對這種解決方案的支持,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案,比如經常把Java里提供的javax.servlet.http.HttpSession簡稱為session⑥。

    鑒于這種混亂已不可改變,本文中session一詞的運用也會根據上下文有不同的含義,請大家注意分辨。
    在本文中,使用中文“瀏覽器會話期間”來表達含義①,使用“session機制”來表達含義④,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥

    二、HTTP協議與狀態保持
    HTTP 協議本身是無狀態的,這與HTTP協議本來的目的是相符的,客戶端只需要簡單的向服務器請求下載某些文件,無論是客戶端還是服務器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關系一樣。

    然而聰明(或者貪心?)的人們很快發現如果能夠提供一些按需生成的動態信息會使web變得更加有用,就像給有線電視加上點播功能一樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務器端則出現了CGI規范以響應客戶端的動態請求,作為傳輸載體的HTTP協議也添加了文件上載、 cookie這些特性。其中cookie的作用就是為了解決HTTP協議無狀態的缺陷所作出的努力。至于后來出現的session機制則是又一種在客戶端與服務器之間保持狀態的解決方案。

    讓我們用幾個例子來描述一下cookie和session機制之間的區別與聯系。筆者曾經常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數量。想象一下其實也無外乎下面的幾種方案:
    1、該店的店員很厲害,能記住每位顧客的消費數量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協議本身支持狀態。
    2、發給顧客一張卡片,上面記錄著消費的數量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯系起來。這種做法就是在客戶端保持狀態。
    3、發給顧客一張會員卡,除了卡號之外什么信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務器端保持狀態。

    由于HTTP協議是無狀態的,而出于種種考慮也不希望使之成為有狀態的,因此,后面兩種方案就成為現實的選擇。具體來說cookie機制采用的是在客戶端保持狀態的方案,而session機制采用的是在服務器端保持狀態的方案。同時我們也看到,由于采用服務器端保持狀態的方案在客戶端也需要保存一個標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的,但實際上它還有其他選擇。

    三、理解cookie機制
    cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發;“會員卡”的內容;以及客戶如何使用“會員卡”。

    正統的cookie分發是通過擴展HTTP協議來實現的,服務器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。

    而cookie 的使用是由瀏覽器按照一定的原則在后臺自動發送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發送給服務器。意思是麥當勞的會員卡只能在麥當勞的店里出示,如果某家分店還發行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。

    cookie的內容主要包括:名字,值,過期時間,路徑和域。
    其中域可以指定某一個域比如.google.com,相當于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。
    路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。
    路徑與域合在一起就構成了cookie的作用范圍。
    如果不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的 cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內存里,當然這種行為并不是規范規定的。如果設置了過期時間,瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。

    存儲在硬盤上的cookie 可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存里的cookie,不同的瀏覽器有不同的處理方式。對于IE,在一個打開的窗口上按 Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經打開的窗口的內存cookie;對于 Mozilla Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗口共享內存cookie。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經常給采用session機制的web應用程序開發者造成很大的困擾。

    下面就是一個goolge設置cookie的響應頭的例子
    HTTP/1.1 302 Found
    Location: http://www.google.com/intl/zh-CN/
    Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
    Content-Type: text/html


    image
    這是使用HTTPLook這個HTTP Sniffer軟件來俘獲的HTTP通訊紀錄的一部分

    image
    瀏覽器在再次訪問goolge的資源時自動向外發送cookie

    image
    用Firefox可以很容易的觀察現有的cookie的值
    使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。

    image
    IE也可以設置在接受cookie前詢問

    四、理解session機制

    session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。

    當程序需要為某個客戶端的請求創建一個session的時候,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識 - 稱為 session id,如果已包含一個session id則說明以前已經為此客戶端創建過session,服務器就按照session id把這個 session檢索出來使用(如果檢索不到,可能會新建一個),如果客戶端請求不包含session id,則為此客戶端創建一個session并且生成一個與此session相關聯的session id,session id的值應該是一個既不會重復,又不容易被找到規律以仿造的字符串,這個 session id將被在本次響應中返回給客戶端保存。

    保存這個session id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發揮給服務器。一般這個cookie的名字都是類似于SEEESIONID,而。比如weblogic對于web應用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

    由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞回服務器。經常被使用的一種技術叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現形式為http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    另一種是作為查詢字符串附加在URL后面,表現形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    這兩種方式對于用戶來說是沒有區別的,只是服務器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數區分開來。
    為了在整個交互過程中始終保持狀態,就必須在每個客戶端可能請求的路徑后面都包含這個session id。

    另一種技術叫做表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session id傳遞回服務器。比如下面的表單
    <form name="testform" action="/xxx">
    <input type="text">
    </form>


    在被傳遞給客戶端之前將被改寫成
    <form name="testform" action="/xxx">
    <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
    <input type="text">
    </form>


    這種技術現在已較少應用,筆者接觸過的很古老的iPlanet6(SunONE應用服務器的前身)就使用了這種技術。
    實際上這種技術可以簡單的用對action應用URL重寫來代替。

    在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保留,程序一般都是在用戶做log off的時候發個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器后這個 session id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的HTTP請求頭,把原來的session id發送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。

    恰恰是由于關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經停止了活動,才會把session刪除以節省存儲空間。

    五、理解javax.servlet.http.HttpSession
    HttpSession是Java平臺對session機制的實現規范,因為它僅僅是個接口,具體到每個web應用服務器的提供商,除了對規范支持之外,仍然會有一些規范里沒有規定的細微差異。這里我們以BEA的Weblogic Server8.1作為例子來演示。

    首先,Weblogic Server提供了一系列的參數來控制它的HttpSession的實現,包括使用cookie的開關選項,使用URL重寫的開關選項,session持久化的設置,session失效時間的設置,以及針對cookie的各種設置,比如設置cookie的名字、路徑、域, cookie的生存時間等。

    一般情況下,session都是存儲在內存里,當服務器進程被停止或者重啟的時候,內存里的session也會被清空,如果設置了session的持久化特性,服務器就會把session保存到硬盤上,當服務器進程重新啟動或這些信息將能夠被再次使用, Weblogic Server支持的持久性方式包括文件、數據庫、客戶端cookie保存和復制。

    復制嚴格說來不算持久化保存,因為session實際上還是保存在內存里,不過同樣的信息被復制到各個cluster內的服務器進程中,這樣即使某個服務器進程停止工作也仍然可以從其他進程中取得session。

    cookie生存時間的設置則會影響瀏覽器生成的cookie是否是一個會話cookie。默認是使用會話cookie。有興趣的可以用它來試驗我們在第四節里提到的那個誤解。

    cookie的路徑對于web應用程序來說是一個非常重要的選項,Weblogic Server對這個選項的默認處理方式使得它與其他服務器有明顯的區別。后面我們會專題討論。

    關于session的設置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

    六、HttpSession常見問題
    (在本小節中session的含義為⑤和⑥的混合)

    1、session在何時被創建
    一個常見的誤解是以為session在有客戶端訪問時就被創建,然而事實是直到某server端程序調用 HttpServletRequest.getSession(true)這樣的語句時才被創建,注意如果JSP沒有顯示的使用 <% @page session="false"%> 關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句 HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的 session對象的來歷。

    由于session會消耗內存資源,因此,如果不打算使用session,應該在所有的JSP中關閉它。

    2、session何時被刪除
    綜合前面的討論,session在下列情況下被刪除a.程序調用HttpSession.invalidate();或b.距離上一次收到客戶端發送的session id時間間隔超過了session的超時設置;或c.服務器進程被停止(非持久session)

    3、如何做到在瀏覽器關閉時刪除session
    嚴格的講,做不到這一點。可以做一點努力的辦法是在所有的客戶端頁面里使用javascript代碼window.oncolose來監視瀏覽器的關閉動作,然后向服務器發送一個請求來刪除session。但是對于瀏覽器崩潰或者強行殺死進程這些非常規手段仍然無能為力。

    4、有個HttpSessionListener是怎么回事
    你可以創建這樣的listener去監控session的創建和銷毀事件,使得在發生這樣的事件時你可以做一些相應的工作。注意是session的創建和銷毀動作觸發listener,而不是相反。類似的與HttpSession有關的listener還有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

    5、存放在session中的對象必須是可序列化的嗎
    不是必需的。要求對象可序列化只是為了session能夠在集群中被復制或者能夠持久保存或者在必要時server能夠暫時把session交換出內存。在 Weblogic Server的session中放置一個不可序列化的對象在控制臺上會收到一個警告。我所用過的某個iPlanet版本如果 session中有不可序列化的對象,在session銷毀時會有一個Exception,很奇怪。

    6、如何才能正確的應付客戶端禁止cookie的可能性
    對所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
    http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

    7、開兩個瀏覽器窗口訪問應用程序會使用同一個session還是不同的session
    參見第三小節對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲方式都會對這個問題的答案有影響。

    8、如何防止用戶打開兩個瀏覽器窗口操作導致的session混亂
    這個問題與防止表單多次提交是類似的,可以通過設置客戶端的令牌來解決。就是在服務器每次生成一個不同的id返回給客戶端,同時保存在session里,客戶端提交表單時必須把這個id也返回服務器,程序首先比較返回的id與保存在session里的值是否一致,如果不一致則說明本次操作已經被提交過了。可以參看《J2EE核心模式》關于表示層模式的部分。需要注意的是對于使用javascript window.open打開的窗口,一般不設置這個id,或者使用單獨的id,以防主窗口無法操作,建議不要再window.open打開的窗口里做修改操作,這樣就可以不用設置。

    9、為什么在Weblogic Server中改變session的值后要重新調用一次session.setValue
    做這個動作主要是為了在集群環境中提示Weblogic Server session中的值發生了改變,需要向其他服務器進程復制新的session值。

    10、為什么session不見了
    排除session正常失效的因素之外,服務器本身的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代理服務器在cookie處理上也有可能會出現問題。
    出現這一問題的大部分原因都是程序的錯誤,最常見的就是在一個應用程序中去訪問另外一個應用程序。我們在下一節討論這個問題。

    七、跨應用程序的session共享

    常常有這樣的情況,一個大項目被分割成若干小項目開發,為了能夠互不干擾,要求每個小項目作為一個單獨的web應用程序開發,可是到了最后突然發現某幾個小項目之間需要共享一些信息,或者想使用session來實現SSO(single sign on),在session中保存login的用戶信息,最自然的要求是應用程序間能夠訪問彼此的session。

    然而按照Servlet規范,session的作用范圍應該僅僅限于當前應用程序下,不同的應用程序之間是不能夠互相訪問對方的session的。各個應用服務器從實際效果上都遵守了這一規范,但是實現的細節卻可能各有不同,因此解決跨應用程序session共享的方法也各不相同。

    首先來看一下Tomcat是如何實現web應用程序之間session的隔離的,從 Tomcat設置的cookie路徑來看,它對不同的應用程序設置的cookie路徑是不同的,這樣不同的應用程序所用的session id是不同的,因此即使在同一個瀏覽器窗口里訪問不同的應用程序,發送給服務器的session id也可以是不同的。

    image
    image

    根據這個特性,我們可以推測Tomcat中session的內存結構大致如下。
    image

    筆者以前用過的iPlanet也采用的是同樣的方式,估計SunONE與iPlanet之間不會有太大的差別。對于這種方式的服務器,解決的思路很簡單,實際實行起來也不難。要么讓所有的應用程序共享一個session id,要么讓應用程序能夠獲得其他應用程序的session id。

    iPlanet中有一種很簡單的方法來實現共享一個session id,那就是把各個應用程序的cookie路徑都設為/(實際上應該是/NASApp,對于應用程序來講它的作用相當于根)。
    <session-info>
    <path>/NASApp</path>
    </session-info>


    需要注意的是,操作共享的session應該遵循一些編程約定,比如在session attribute名字的前面加上應用程序的前綴,使得 setAttribute("name", "neo")變成setAttribute("app1.name", "neo"),以防止命名空間沖突,導致互相覆蓋。

    在Tomcat中則沒有這么方便的選擇。在Tomcat版本3上,我們還可以有一些手段來共享session。對于版本4以上的Tomcat,目前筆者尚未發現簡單的辦法。只能借助于第三方的力量,比如使用文件、數據庫、JMS或者客戶端cookie,URL參數或者隱藏字段等手段。

    我們再看一下Weblogic Server是如何處理session的。
    image
    image

    從截屏畫面上可以看到Weblogic Server對所有的應用程序設置的cookie的路徑都是/,這是不是意味著在Weblogic Server中默認的就可以共享session了呢?然而一個小實驗即可證明即使不同的應用程序使用的是同一個session,各個應用程序仍然只能訪問自己所設置的那些屬性。這說明Weblogic Server中的session的內存結構可能如下
    image

    對于這樣一種結構,在 session機制本身上來解決session共享的問題應該是不可能的了。除了借助于第三方的力量,比如使用文件、數據庫、JMS或者客戶端 cookie,URL參數或者隱藏字段等手段,還有一種較為方便的做法,就是把一個應用程序的session放到ServletContext中,這樣另外一個應用程序就可以從ServletContext中取得前一個應用程序的引用。示例代碼如下,

    應用程序A
    context.setAttribute("appA", session);
    


    應用程序B
    contextA = context.getContext("/appA");
    HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");


    值得注意的是這種用法不可移植,因為根據ServletContext的JavaDoc,應用服務器可以處于安全的原因對于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通過。

    那么Weblogic Server為什么要把所有的應用程序的cookie路徑都設為/呢?原來是為了SSO,凡是共享這個session的應用程序都可以共享認證的信息。一個簡單的實驗就可以證明這一點,修改首先登錄的那個應用程序的描述符weblogic.xml,把cookie路徑修改為/appA 訪問另外一個應用程序會重新要求登錄,即使是反過來,先訪問cookie路徑為/的應用程序,再訪問修改過路徑的這個,雖然不再提示登錄,但是登錄的用戶信息也會丟失。注意做這個實驗時認證方式應該使用FORM,因為瀏覽器和web服務器對basic認證方式有其他的處理方式,第二次請求的認證不是通過 session來實現的。具體請參看[7] secion 14.8 Authorization,你可以修改所附的示例程序來做這些試驗。

    八、總結
    session機制本身并不復雜,然而其實現和配置上的靈活性卻使得具體情況復雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,服務器的經驗當作普遍適用的經驗,而是始終需要具體情況具體分析。
    摘要:雖然session機制在web應用程序中被采用已經很長時間了,但是仍然有很多人不清楚session機制的本質,以至不能正確的應用這一技術。本文將詳細討論session的工作機制并且對在Java web application中應用session機制時常見的問題作出解答。

    posted @ 2008-01-26 09:53 菠蘿 閱讀(336) | 評論 (0)編輯 收藏

    URLClassLoader加載class到當前線程類加載器(轉)

    我們知道,Java利用ClassLoader將類載入內存,并且在同一應用中,可以有很多個ClassLoader,通過委派機制,把裝載的任務傳遞給上級的裝載器的,依次類推,直到啟動類裝載器(沒有上級類裝載器)。如果啟動類裝載器能夠裝載這個類,那么它會首先裝載。如果不能,則往下傳遞。當父類為null時,JVM內置的類(稱為:bootstrap class loader)就會充當父類。想想眼下的越來越多用XML文件做配置文件或者是描述符、部署符。其實這些通過XML文檔描述的配置信息最終都要變成Java類,基實都是通過ClassLoader來完成的。URLClassLoader是ClassLoader的子類,它用于從指向 JAR 文件和目錄的 URL 的搜索路徑加載類和資源。也就是說,通過URLClassLoader就可以加載指定jar中的class到內存中。
    下面來看一個例子,在該例子中,我們要完成的工作是利用URLClassLoader加載jar并運行其中的類的某個方法。

    首先我們定義一個接口,使所有繼承它的類都必須實現action方法,如下:

      public   interface  ActionInterface  {
         public  String action();
    }
    完成后將其打包為testInterface.jar文件。

    接下來新建一工程,為了編譯通過,引入之前打好的testInterface.jar包。并創建TestAction類,使它實現ActionInterface接口。如下:


      public   class  TestAction  implements  ActionInterface  {
         public  String action()  {
             return   " com.mxjava.TestAction.action " ;
        }
    }
     
    完成后將其打包為test.jar,放在c盤根目錄下。下面要做的就是利用URLClassLoader加載并運行TestAction的action方法,并將返回的值打印在控制臺上。

    新建一工程,引入testInterface.jar包。并創建一可執行類(main方法),在其中加入如下代碼:

     URL url  =   new  URL(“file:C: / test.jar”);
    URLClassLoader myClassLoader  =   new  URLClassLoader( new  URL[]  { url } );
    Class myClass  =  myClassLoader.loadClass(“com.mxjava.TestAction”);
    ActionInterface action  =  (ActionInterface)myClass.newInstance();
    System.out.println(action.action());
      在上面的例子中,首先利用URLClassLoader加載了C:\test.jar包,將其中的com.mxjava.TestAction類載入內存,將其強制轉型為testInterface包中的ActionInterface類型,最后調用其action方法,并打印到控制臺中。

      執行程序后,在控制臺上如期打印出我們想要的內容。但是,事情并沒有那么簡單,當我們將該代碼移動web應用中時,就會拋出異常。原來,Java為我們提供了三種可選擇的ClassLoader:
    1. 系統類加載器或叫作應用類加載器 (system classloader or application classloader)
    2. 當前類加載器
    3. 當前線程類加載器

      在上例中我們使用javac命令來運行該程序,這時候使用的是系統類加載器 (system classloader)。這個類加載器處理 -classpath下的類加載工作,可以通過ClassLoader.getSystemClassLoader()方法調用。 ClassLoader 下所有的 getSystemXXX()的靜態方法都是通過這個方法定義的。在代碼中,應該盡量少地調用這個方法,以其它的類加載器作為代理。否則代碼將只能工作在簡單的命令行應用中。當在web應用中時,服務器也是利用ClassLoader來加載class的,由于ClassLoader的不同,所以在強制轉型時JVM認定不是同一類型。(在JAVA中,一個類用其完全匹配類名(fully qualified class name)作為標識,這里指的完全匹配類名包括包名和類名。但在JVM中一個類用其全名和一個加載類ClassLoader的實例作為唯一標識。因此,如果一個名為Pg的包中,有一個名為Cl的類,被類加載器KlassLoader的一個實例kl1加載,Cl的實例,即C1.class在JVM中表示為(Cl, Pg, kl1)。這意味著兩個類加載器的實例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它們所加載的類也因此完全不同,互不兼容的。)為了能夠使程序正確運行,我們首要解決的問題就是,如何將URLClassLoader加載的類,同當前ClassLoader保持在同一類加載器中。解決方法很簡單,利用java提供的第三種ClassLoader—當前線程類加載器即可。jdk api文檔就會發現,URLClassLoader提供了三種構造方式:

     // 使用默認的委托父 ClassLoader 為指定的 URL 構造一個新 URLClassLoader。 
     URLClassLoader(URL[] urls)
    // 為給定的 URL 構造新 URLClassLoader。 
    URLClassLoader(URL[] urls, ClassLoader parent)
    // 為指定的 URL、父類加載器和 URLStreamHandlerFactory 創建新 URLClassLoader。
     URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) 
    接下來要做的就是,在構造URLClassLoader時,將當前線程類加載器置入即可。如下:

    URLClassLoader myClassLoader  =   new  URLClassLoader( new  URL[]  { url } , Thread.currentThread().getContextClassLoader());
    總結:
      Java是利用ClassLoader來加載類到內存的,ClassLoader本身是用java語言寫的,所以我們可以擴展自己的ClassLoader。利用URLClassLoader可以加載指定jar包中的類到內存。在命行上利用URLClassLoader加載jar時,是使用系統類加載器來加載class的,所以在web環境下,就會出錯。這是因為JVM中一個類用其全名和一個加載類ClassLoader的實例作為唯一標識的。我們只要利用URLClassLoader的第二種構造方法并傳入當前線程類加載器即可解決。

    posted @ 2008-01-26 09:51 菠蘿 閱讀(457) | 評論 (0)編輯 收藏

    了解Java ClassLoader

     

    【原文地址:https://www6.software.ibm.com/developerworks/cn/education/java/j-classloader/tutorial/】
    1.介紹
    2.ClassLoader的結構
    3.Compiling ClassLoader
    4.java2 中ClassLoader的變動
    5.源代碼
    ---------------------------------------------------------------------------

    第一章 介紹

    什么是 ClassLoader

    在流行的商業化編程語言中,Java 語言由于在 Java 虛擬機 (JVM) 上運行而顯得與眾不同。這意味著已編譯的程序是一種特殊的、獨立于平臺的格式,并非依賴于它們所運行的機器。在很大程度上,這種格式不同于傳統的可執行程序格式。

    與 C 或 C++ 編寫的程序不同,Java 程序并不是一個可執行文件,而是由許多獨立的類文件組成,每一個文件對應于一個 Java 類。

    此外,這些類文件并非立即全部都裝入內存,而是根據程序需要裝入內存。ClassLoader 是 JVM 中將類裝入內存的那部分。

    而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著創建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小細節。

    為什么編寫 ClassLoader?

    如果 JVM 已經有一個 ClassLoader,那么為什么還要編寫另一個呢?問得好。缺省的 ClassLoader 只知道如何從本地文件系統裝入類文件。不過這只適合于常規情況,即已全部編譯完 Java 程序,并且計算機處于等待狀態。

    但 Java 語言最具新意的事就是 JVM 可以非常容易地從那些非本地硬盤或從網絡上獲取類。例如,瀏覽者可以使用定制的 ClassLoader 從 Web 站點裝入可執行內容。

    有許多其它方式可以獲取類文件。除了簡單地從本地或網絡裝入文件以外,可以使用定制的 ClassLoader 完成以下任務:

    • 在執行非置信代碼之前,自動驗證數字簽名
    • 使用用戶提供的密碼透明地解密代碼
    • 動態地創建符合用戶特定需要的定制化構建類
    任何您認為可以生成 Java 字節碼的內容都可以集成到應用程序中。

    定制 ClassLoader 示例

    如果使用過 JDK 或任何基于 Java 瀏覽器中的 Applet 查看器,那么您差不多肯定使用過定制的 ClassLoader。

    Sun 最初發布 Java 語言時,其中最令人興奮的一件事是觀看這項新技術是如何執行在運行時從遠程的 Web 服務器裝入的代碼。(此外,還有更令人興奮的事 -- Java 技術提供了一種便于編寫代碼的強大語言。)更一些令人激動的是它可以執行從遠程 Web 服務器通過 HTTP 連接發送過來的字節碼。

    此項功能歸功于 Java 語言可以安裝定制 ClassLoader。Applet 查看器包含一個 ClassLoader,它不在本地文件系統中尋找類,而是訪問遠程服務器上的 Web 站點,經過 HTTP 裝入原始的字節碼文件,并把它們轉換成 JVM 內的類。

    瀏覽器和 Applet 查看器中的 ClassLoaders 還可以做其它事情:它們支持安全性以及使不同的 Applet 在不同的頁面上運行而互不干擾。

    Luke Gorrie 編寫的 Echidna 是一個開放源碼包,它可以使您在單個虛擬機上運行多個 Java 應用程序。(請參閱進一步了解和參考資料。)它使用定制的 ClassLoader,通過向每個應用程序提供該類文件的自身副本,以防止應用程序互相干擾。


    我們的 ClassLoader 示例

    了解了 ClassLoader 如何工作以及如何編寫 ClassLoader 之后,我們將創建稱作 CompilingClassLoader (CCL) 的 Classloader。CCL 為我們編譯 Java 代碼,而無需要我們干涉這個過程。它基本上就類似于直接構建到運行時系統中的 "make" 程序。

    注:進一步了解之前,應注意在 JDK 版本 1.2 中已改進了 ClassLoader 系統的某些方面(即 Java 2 平臺)。本教程是按 JDK 版本 1.0 和 1.1 寫的,但也可以在以后的版本中運行。

    Java 2 中 ClassLoader 的變動描述了 Java 版本 1.2 中的變動,并提供了一些詳細信息,以便修改 ClassLoader 來利用這些變動。

     


     

    ------------------------------------------------------------------------------------------------------

    第二章.ClassLoader的結構



    ClassLoader 的基本目標是對類的請求提供服務。當 JVM 需要使用類時,它根據名稱向 ClassLoader 請求這個類,然后 ClassLoader 試圖返回一個表示這個類的 Class 對象。

    通過覆蓋對應于這個過程不同階段的方法,可以創建定制的 ClassLoader。

    在本章的其余部分,您會學習 Java ClassLoader 的關鍵方法。您將了解每一個方法的作用以及它是如何適合裝入類文件這個過程的。您也會知道,創建自己的 ClassLoader 時,需要編寫什么代碼。

    在下一章中,您將會利用這些知識來使用我們的 ClassLoader 示例 -- CompilingClassLoader。


    方法 loadClass


    ClassLoader.loadClass() 是 ClassLoader 的入口點。其特征如下:

     

    Class loadClass( String name, boolean resolve );

    name 參數指定了 JVM 需要的類的名稱,該名稱以包表示法表示,如 Foojava.lang.Object

    resolve 參數告訴方法是否需要解析類。在準備執行類之前,應考慮類解析。并不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那么就不需要解析。

    在 Java 版本 1.1 和以前的版本中,loadClass 方法是創建定制的 ClassLoader 時唯一需要覆蓋的方法。(Java 2 中 ClassLoader 的變動提供了關于 Java 1.2 中 findClass() 方法的信息。)


    方法 defineClass

    defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節組成的數組并把它轉換成 Class 對象。原始數組包含如從文件系統或網絡裝入的數據。

    defineClass 管理 JVM 的許多復雜、神秘和倚賴于實現的方面 -- 它把字節碼分析成運行時數據結構、校驗有效性等等。不必擔心,您無需親自編寫它。事實上,即使您想要這么做也不能覆蓋它,因為它已被標記成最終的。


    方法 findSystemClass

    findSystemClass 方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用 defineClass 將原始字節轉換成 Class 對象,以將該文件轉換成類。當運行 Java 應用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關于 Java 版本 1.2 這個過程變動的詳細信息。)

    對于定制的 ClassLoader,只有在嘗試其它方法裝入類之后,再使用 findSystemClass。原因很簡單:ClassLoader 是負責執行裝入類的特殊步驟,不是負責所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統。這就是 findSystemClass 的用途。

    其工作流程如下:

     

    • 請求定制的 ClassLoader 裝入類。
    • 檢查遠程 Web 站點,查看是否有所需要的類。
    • 如果有,那么好;抓取這個類,完成任務。
    • 如果沒有,假定這個類是在基本 Java 庫中,那么調用 findSystemClass,使它從文件系統裝入該類。

    在大多數定制 ClassLoaders 中,首先調用 findSystemClass 以節省在本地就可以裝入的許多 Java 庫類而要在遠程 Web 站點上查找所花的時間。然而,正如,在下一章節所看到的,直到確信能自動編譯我們的應用程序代碼時,才讓 JVM 從本地文件系統裝入類。


    方法 resolveClass

    正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以調用 resolveClass,這取決于 loadClassresolve 參數的值。

    方法 findLoadedClass

    findLoadedClass 充當一個緩存:當請求 loadClass 裝入類時,它調用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先調用該方法。

    組裝

     

     

     

    讓我們看一下如何組裝所有方法。

    我們的 loadClass 實現示例執行以下步驟。(這里,我們沒有指定生成類文件是采用了哪種技術 -- 它可以是從 Net 上裝入、或者從歸檔文件中提取、或者實時編譯。無論是哪一種,那是種特殊的神奇方式,使我們獲得了原始類文件字節。)

     

    • 調用 findLoadedClass 來查看是否存在已裝入的類。

    • 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節。

    • 如果已有原始字節,調用 defineClass 將它們轉換成 Class 對象。

    • 如果沒有原始字節,然后調用 findSystemClass 查看是否從本地文件系統獲取類。

    • 如果 resolve 參數是 true,那么調用 resolveClass 解析 Class 對象。

    • 如果還沒有類,返回 ClassNotFoundException

    • 否則,將類返回給調用程序。
    推想

    現在您已經了解了 ClassLoader 的工作原理,現在該構建一個了。在下一章中,我們將討論 CCL。

    ---------------------------------------------------------------------------------------------

    第三章:Compiling ClassLoader

    CCL 揭密

    我們的 ClassLoader (CCL) 的任務是確保代碼被編譯和更新。

    下面描述了它的工作方式:

     

    • 當請求一個類時,先查看它是否在磁盤的當前目錄或相應的子目錄。

    • 如果該類不存在,但源碼中有,那么調用 Java 編譯器來生成類文件。

    • 如果該類已存在,檢查它是否比源碼舊。如果是,調用 Java 編譯器來重新生成類文件。

    • 如果編譯失敗,或者由于其它原因不能從現有的源碼中生成類文件,返回 ClassNotFoundException

    • 如果仍然沒有該類,也許它在其它庫中,所以調用 findSystemClass 來尋找該類。

    • 如果還是沒有,則返回 ClassNotFoundException

    • 否則,返回該類。
    Java 編譯的工作方式

     

    在深入討論之前,應該先退一步,討論 Java 編譯。通常,Java 編譯器不只是編譯您要求它編譯的類。它還會編譯其它類,如果這些類是您要求編譯的類所需要的類。

    CCL 逐個編譯應用程序中的需要編譯的每一個類。但一般來說,在編譯器編譯完第一個類后,CCL 會查找所有需要編譯的類,然后編譯它。為什么?Java 編譯器類似于我們正在使用的規則:如果類不存在,或者與它的源碼相比,它比較舊,那么它需要編譯。其實,Java 編譯器在 CCL 之前的一個步驟,它會做大部分的工作。

    當 CCL 編譯它們時,會報告它正在編譯哪個應用程序上的類。在大多數的情況下,CCL 會在程序中的主類上調用編譯器,它會做完所有要做的 -- 編譯器的單一調用已足夠了。

    然而,有一種情形,在第一步時不會編譯某些類。如果使用 Class.forName 方法,通過名稱來裝入類,Java 編譯器會不知道這個類時所需要的。在這種情況下,您會看到 CCL 再次運行 Java 編譯器來編譯這個類。在源代碼中演示了這個過程。

    使用 CompilationClassLoader

    要使用 CCL,必須以特殊方式調用程序。不能直接運行該程序,如:

     

    % java Foo arg1 arg2

    應以下列方式運行它:

     

    % java CCLRun Foo arg1 arg2

    CCLRun 是一個特殊的存根程序,它創建 CompilingClassLoader 并用它來裝入程序的主類,以確保通過 CompilingClassLoader 來裝入整個程序。CCLRun 使用 Java Reflection API 來調用特定類的主方法并把參數傳遞給它。有關詳細信息,請參閱源代碼

    運行示例

    源碼包括了一組小類,它們演示了工作方式。主程序是 Foo 類,它創建類 Bar 的實例。類 Bar 創建另一個類 Baz 的實例,它在 baz 包內,這是為了展示 CCL 是如何處理子包里的代碼。Bar 也是通過名稱裝入的,其名稱為 Boo,這用來展示它也能與 CCL 工作。

    每個類都聲明已被裝入并運行。現在用源代碼來試一下。編譯 CCLRun 和 CompilingClassLoader。確保不要編譯其它類(FooBarBazBoo),否則將不會使用 CCL,因為這些類已經編譯過了。

     


    % java CCLRun Foo arg1 arg2
    CCL: Compiling Foo.java...
    foo! arg1 arg2
    bar! arg1 arg2
    baz! arg1 arg2
    CCL: Compiling Boo.java...
    Boo!

    請注意,首先調用編譯器,Foo.java 管理 Barbaz.Baz。直到 Bar 通過名稱來裝入 Boo 時,被調用它,這時 CCL 會再次調用編譯器來編譯它。

     

     

     

     

    --------------------------------------------------------------------------------------

    第四章:java2 中ClassLoader的變動


    概述

    在 Java 版本 1.2 和以后的版本中,對 ClassLoader 做了一些改進。任何為老系統編寫的代碼可以在新版本中運行,但新系統為您提供了一些便利。

    新模型是委托模型,這意味著如果 ClassLoader 不能找到類,它會請求父代 ClassLoader 來執行此項任務。所有 ClassLoaders 的根是系統 ClassLoader,它會以缺省方式裝入類 -- 即,從本地文件系統。

    loadClass 的缺省實現

    定制編寫的 loadClass 方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發現一次次地在相同的、很復雜的方法上編寫變量。

    在 Java 1.2 中 loadClass 的實現嵌入了大多數查找類的一般方法,并使您通過覆蓋 findClass 方法來定制它,在適當的時候 findClass 會調用 loadClass

    這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。

    新方法:findClass

    loadClass 的缺省實現調用這個新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無需要復制其它代碼(例如,當專門的方法失敗時,調用系統 ClassLoader)。

    新方法:getSystemClassLoader

    如果覆蓋 findClassloadClassgetSystemClassLoader 使您能以實際 ClassLoader 對象來訪問系統 ClassLoader(而不是固定的從 findSystemClass 調用它)。

    新方法:getParent

    為了將類請求委托給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定制的 ClassLoader 不能找到類時,可以使用這種方法。

    父代 ClassLoader 被定義成創建該 ClassLoader 所包含代碼的對象的 ClassLoader。

    ----------------------------------------------------------------------------------

     

     

     

    第五章.源代碼

     

    CompilingClassLoader.java

    以下是 CompilingClassLoader.java 的源代碼

    // $Id$
    import java.io.*;
    /*
    A CompilingClassLoader compiles your Java source on-the-fly. It checks
    for nonexistent .class files, or .class files that are older than their
    corresponding source code.*/
    public class CompilingClassLoader extends ClassLoader
    {
    // Given a filename, read the entirety of that file from disk
    // and return it as a byte array.
    private byte[] getBytes( String filename ) throws IOException {
    // Find out the length of the file
    File file = new File( filename );
    long len = file.length();
    // Create an array that's just the right size for the file's
    // contents
    byte raw[] = new byte[(int)len];
    // Open the file
    FileInputStream fin = new FileInputStream( file );
    // Read all of it into the array; if we don't get all,
    // then it's an error.
    int r = fin.read( raw );
    if (r != len)
    throw new IOException( "Can't read all, "+r+" != "+len );
    // Don't forget to close the file!
    fin.close();
    // And finally return the file contents as an array
    return raw;
    }
    // Spawn a process to compile the java source code file
    // specified in the 'javaFile' parameter. Return a true if
    // the compilation worked, false otherwise.
    private boolean compile( String javaFile ) throws IOException {
    // Let the user know what's going on
    System.out.println( "CCL: Compiling "+javaFile+"..." );
    // Start up the compiler
    Process p = Runtime.getRuntime().exec( "javac "+javaFile );
    // Wait for it to finish running
    try {
    p.waitFor();
    } catch( InterruptedException ie ) { System.out.println( ie ); }
    // Check the return code, in case of a compilation error
    int ret = p.exitValue();
    // Tell whether the compilation worked
    return ret==0;
    }
    // The heart of the ClassLoader -- automatically compile
    // source as necessary when looking for class files
    public Class loadClass( String name, boolean resolve )
    throws ClassNotFoundException {

    // Our goal is to get a Class object
    Class clas = null;

    // First, see if we've already dealt with this one
    clas = findLoadedClass( name );

    //System.out.println( "findLoadedClass: "+clas );

    // Create a pathname from the class name
    // E.g. java.lang.Object => java/lang/Object
    String fileStub = name.replace( '.', '/' );

    // Build objects pointing to the source code (.java) and object
    // code (.class)
    String javaFilename = fileStub+".java";
    String classFilename = fileStub+".class";

    File javaFile = new File( javaFilename );
    File classFile = new File( classFilename );

    //System.out.println( "j "+javaFile.lastModified()+" c "+
    // classFile.lastModified() );

    // First, see if we want to try compiling. We do if (a) there
    // is source code, and either (b0) there is no object code,
    // or (b1) there is object code, but it's older than the source
    if (javaFile.exists() &&
    (!classFile.exists() ||
    javaFile.lastModified() > classFile.lastModified())) {

    try {
    // Try to compile it. If this doesn't work, then
    // we must declare failure. (It's not good enough to use
    // and already-existing, but out-of-date, classfile)
    if (!compile( javaFilename ) || !classFile.exists()) {
    throw new ClassNotFoundException( "Compile failed: "+javaFilename );
    }
    } catch( IOException ie ) {

    // Another place where we might come to if we fail
    // to compile
    throw new ClassNotFoundException( ie.toString() );
    }
    }

    // Let's try to load up the raw bytes, assuming they were
    // properly compiled, or didn't need to be compiled
    try {

    // read the bytes
    byte raw[] = getBytes( classFilename );

    // try to turn them into a class
    clas = defineClass( name, raw, 0, raw.length );
    } catch( IOException ie ) {
    // This is not a failure! If we reach here, it might
    // mean that we are dealing with a class in a library,
    // such as java.lang.Object
    }

    //System.out.println( "defineClass: "+clas );

    // Maybe the class is in a library -- try loading
    // the normal way
    if (clas==null) {
    clas = findSystemClass( name );
    }

    //System.out.println( "findSystemClass: "+clas );

    // Resolve the class, if any, but only if the "resolve"
    // flag is set to true
    if (resolve && clas != null)
    resolveClass( clas );

    // If we still don't have a class, it's an error
    if (clas == null)
    throw new ClassNotFoundException( name );

    // Otherwise, return the class
    return clas;
    }
     }
    CCRun.java


    以下是 CCRun.java 的源代碼


    // $Id$

    import java.lang.reflect.*;

    /*

    CCLRun executes a Java program by loading it through a
    CompilingClassLoader.

    */

    public class CCLRun
    {
    static public void main( String args[] ) throws Exception {

    // The first argument is the Java program (class) the user
    // wants to run
    String progClass = args[0];

    // And the arguments to that program are just
    // arguments 1..n, so separate those out into
    // their own array
    String progArgs[] = new String[args.length-1];
    System.arraycopy( args, 1, progArgs, 0, progArgs.length );

    // Create a CompilingClassLoader
    CompilingClassLoader ccl = new CompilingClassLoader();

    // Load the main class through our CCL
    Class clas = ccl.loadClass( progClass );

    // Use reflection to call its main() method, and to
    // pass the arguments in.

    // Get a class representing the type of the main method's argument
    Class mainArgType[] = { (new String[0]).getClass() };

    // Find the standard main method in the class
    Method main = clas.getMethod( "main", mainArgType );

    // Create a list containing the arguments -- in this case,
    // an array of strings
    Object argsArray[] = { progArgs };

    // Call the method
    main.invoke( null, argsArray );
    }
    }
    Foo.java


    以下是 Foo.java 的源代碼



    // $Id$

    public class Foo
    {
    static public void main( String args[] ) throws Exception {
    System.out.println( "foo! "+args[0]+" "+args[1] );
    new Bar( args[0], args[1] );
    }
    }
    Bar.java


    以下是 Bar.java 的源代碼


    // $Id$

    import baz.*;

    public class Bar
    {
    public Bar( String a, String b ) {
    System.out.println( "bar! "+a+" "+b );
    new Baz( a, b );

    try {
    Class booClass = Class.forName( "Boo" );
    Object boo = booClass.newInstance();
    } catch( Exception e ) {
    e.printStackTrace();
    }
    }
    }
    baz/Baz.java


    以下是 baz/Baz.java 的源代碼


    // $Id$

    package baz;

    public class Baz
    {
    public Baz( String a, String b ) {
    System.out.println( "baz! "+a+" "+b );
    }
    }

    Boo.java


    以下是 Boo.java 的源代碼


    // $Id$

    public class Boo
    {
    public Boo() {
    System.out.println( "Boo!" );
    }
    }

    posted @ 2008-01-25 13:52 菠蘿 閱讀(325) | 評論 (0)編輯 收藏

    eclipse配置weblogic(轉)

    安裝WebLogic8.1
    安裝WebLogic比較容易,在這里就不再累述了,大家可以參閱相關文檔。現在著重講一下WebLogic的配置,因為后面在配置MyEclipse時將用到這里的有關信息。
    (1)運行開始\程序\BEA WebLogic PlatFORM 8.1\Configuration Wizard。
    (2)選擇Create a new WebLogic configuration,下一步。
    (3)選擇Basic WebLogic Server Domain,下一步。
    (4)選擇Custom,下一步。
    (5)在Name處輸入admin,Listen Address處選擇localhost,以下兩個Port均采用默認值,下一步。
    (6)選擇Skip跳過Multiple Servers,Clusters,and Machines Options,下一步。
    (7)選擇Skip跳過JDBC連接池的配置(注:JDBC連接池的配置可以在啟動WebLogic后到控制臺上進行,大家可以參閱相關文檔),下一步。
    (選擇Skip跳過JMS的配置(同樣留到控制臺上做),下一步。
    (9)繼續跳過,下一步。
    (10)選擇Yes,下一步。
    (11)在User頁點擊Add,隨意添加一個用戶user,密碼12345678,下一步。
    (12)將用戶user分配到Administrators組(還可以同時分配到其它組,方法是選中待加入的組,然后勾中user前的復選框即可),下一步。
    (13)直接點擊下一步跳過。
    (14)設置用戶user的權限,選中Admin,勾中user前的復選框(要指定其它權限依次類推),下一步。
    (15)采用默認設置,直接點擊下一步跳過。
    (16)同樣采用默認設置,直接點擊下一步跳過。
    (17)配置JDK,采用WebLogic的默認值,直接點擊下一步跳過。
    (1最后在Configuration Name處輸入dev,然后點擊Create生成配置,完畢點擊Done關閉Configuration Wizard對話框。
    5.配置MyEclipse的WebLogic服務器
    MyEclipse默認的應用服務器為JBoss3,這里我們使用WebLogic8.1。啟動Eclipse,選擇“窗口\首選項”菜單,打開首選項對話框。展開MyEclipse下的Application Servers結點,點擊JBoss 3,選中右面的Disable單選按鈕,停用JBoss 3。然后點擊WebLogic 8,選中右邊的Enable單選按鈕,啟用WebLogic服務器。同時下面的配置如下:
    (1)BEA home directory:D:\BEA。假定WebLogic安裝在D:\BEA文件夾中。
    (2)WebLogic installation directory:D:\BEA\weblogic81。
    (3)Admin username:user。
    (4)Admin password:12345678。
    (5)Execution domain root:D:\BEA\user_projects\dev。
    (6)Execution domain name:dev。
    (7)Execution server name:admin。
    (8)Hostname:PortNumber:localhost:7001。
    (9)Security policy file:D:\BEA\weblogic81\server\lib\weblogic.policy。
    (10)JAAS login configuration file:省略。
    接著展開WebLogic 8結點,點擊JDK,在右邊的WLS JDK name處選擇WebLogic 8的默認JDK。這里組合框中缺省為j2re1.4.2_03,即之前單獨安裝的jre。單擊Add按鈕,彈出WebLogic > Add JVM對話框,在JRE名稱處隨便輸入一個名字,如jre1.4.1_02。然后在JRE主目錄處選擇WebLogic安裝文件夾中的JDK文件夾,如D:\BEA\jdk141_02,程序會自動填充Javadoc URL文本框和JRE系統庫列表框。單擊確定按鈕關閉對話框。這時候就可以在WLS JDK name組合框中選擇jre1.4.1_02了。之后還要在下面的Optional Java VM arguments,如-ms64m -mx64m -Djava.library.path="D:/BEA/weblogic81/server/bin" -Dweblogic.management.discover=false -Dweblogic.ProductionModeEnabled=false
    最后點擊Paths,在右邊的Prepend to classpath列表框中,通過Add JAR/ZIP按鈕,加入D:\BEA\weblogic81\server\lib\weblogic.jar、D:\BEA\weblogic81\server\lib\webservices.jar。如果用到數據庫,還需把數據庫的驅動類庫加進來,這里我們用WebLogic自帶的SQL Server數據庫驅動庫D:\BEA\weblogic81\server\lib\mssqlserver4v65.jar。
    至此,MyEclipse中WebLogic8的配置工作就算完成了。下面可以看看在Eclipse中能否啟動WebLogic了?自從安裝了MyEclipse之后,Eclipse工具欄中就會有一個Run/Stop Servers下拉按鈕。點擊該按鈕的下拉部分,選擇“WebLogic 8\Start”菜單,即開始啟動WebLogic了。通過查看下面的控制臺消息,就可以知道啟動是否成功,或有什么異常發生。停止WebLogic可選擇“WebLogic\Stop”菜單。 

    posted @ 2008-01-24 15:33 菠蘿 閱讀(990) | 評論 (0)編輯 收藏

    J2EE配置WebLogic-Eclipse插件(轉)

    Eclipse插件設計用于從Eclipse IDE運行 WebLogic Server.借助WebLogic Server插件,可以從Eclipse中啟動和停止WebLogic Server,可以通過 Eclipse調試WebLogic Server中部署的應用程序。在Eclipse中安裝WebLogic插件,并在Eclipse中設置服務器類路徑和JVM選項后,即可通過Eclipse IDE配置和管理WebLogic Server.

      概述

      J2EE開發人員經常需要管理WebLogic Server并調試WebLogic Server上部署的應用程序。 WebLogic Server管理控制臺雖然能夠啟動和停止WebLogic Server,卻不能設置JVM選項和服務器類路徑。必須使用startWebLogic腳本來設置JVM選項和服務器類路徑。而要調試WebLogic Server上部署的應用程序,則需要帶遠程調試器的IDE.有了WebLogic插件后,就可以通過Eclipse IDE管理WebLogic Server. 在文本中,我們將開發一個包括會話EJB和servlet的J2EE應用程序、通過Eclipse IDE在WebLogic Server中部署應用程序、在Eclipse中調試應用程序。

      安裝準備

      下載并安裝Eclipse 3.0 IDE:www.eclipse.org

      下載并安裝WebLogic Server 8.1:

      www.bea.com/framework.jsp?CNT=index.htm&FP=/content/products/weblogic/server

      安裝WebLogic-Eclipse插件

      現在安裝WebLogic-Eclipse IDE.在Eclipse IDE上,選擇Help>Software Updates>Find and Install,將顯示Install/Update窗體。選擇Search for new features to install,然后單擊Next按鈕。在顯示的Install窗體中,單擊New Remote Site按鈕指定要從其安裝插件的更新Web站點。在New Update Site窗體中,指定名稱和安裝WebLogic-Eclipse插件的URL.WebLogic-Eclipse插件的URL是

      選擇許可條款并單擊Next按鈕。在Install location窗體中指定將安裝WebLogic-Eclipse插件的目錄。單擊Finish按鈕完成WebLogic插件的配置。在顯示的JAR Verification窗體中,單擊Install按鈕安裝WebLogic-Eclipse插件。重啟Eclipse工作臺完成插件安裝。現在WebLogic-Eclipse插件便安裝在 Eclipse IDE中了。Eclipse中新添了Run>Start WebLogic和Run>Stop WebLogic兩個功能。

      配置WebLogic-Eclipse插件

      安裝了WebLogic-Eclipse插件后,我們將在Eclipse IDE中配置該插件。首先,創建一個用于配置WebLogic插件的項目。選擇File>New>Project.在New Project窗體中選擇Java>Java Project,然后單擊Next按鈕。在Create a Java project窗體中指定項目名稱,然后單擊Next按鈕。在Java Settings窗體中為項目添加源文件夾。單擊Add Folder按鈕。在New Source Folder窗體中指定文件夾名稱。出現一個消息窗體提示設置bin文件夾作為構建輸出文件夾。接下來,添加項目所需的庫。示例應用程序需要在類路徑中添加J2EE JAR.選擇Libraries選項卡,然后單擊Add External JARs按鈕。

      為項目添加J2EE 1.4 j2ee.jar文件。1.4 j2ee.jar將在項目庫中列出。單擊Finish按鈕完成項目配置。這樣便將一個項目添加到Eclipse IDE Package Explorer視圖中。

      接下來指定WebLogic Server配置。選擇Window>Preferences.在出現的Preferences窗體中,選擇WebLogic節點。在WebLogic preference頁面,選擇要配置的WebLogic Server版本。指定不同的字段值,如 表1 所示。由于安裝服務器和配置域的目錄不同,值也有所不同。單擊Apply按鈕應用指定的值。

      字段描述值

      表1 WebLogic-Eclipse插件

      如果必須向服務器類路徑添加JAR文件,請選擇WebLogic>Classpath節點。可以在添加WebLogic庫之前或之后添加JAR/Zip文件或目錄。選擇WebLogic>JavaVM Options節點指定JavaVM選項。例如,修改weblogic.ProductionModeEnabled屬性。將屬性值設置為false可使用開發模式啟動服務器。單擊Apply按鈕應用JavaVM選項。

      接下來,指定要使用WebLogic Server配置進行調試的項目。單擊Add按鈕,選擇要添加到插件配置的項目。若要調試某個項目,該項目必須位于插件配置中。單擊OK按鈕。

      這樣便將選擇的項目添加到項目列表中了。單擊Apply按鈕,然后單擊OK按鈕,使用項目和WebLogic Server完成WebLogic插件的配置。

      開發和調試WebLogic應用程序

      配置了WebLogic插件后,將開發一個J2EE應用程序在WebLogic Server中進行部署和調試。示例J2EE應用程序由Session EJB和客戶端servlet組成。可從資源zip文件中獲取該 J2EE應用程序(關于本文的源代碼,可在線查看WLDJ歸檔文件中的文章 http://wldj.sys-con.com/read/issue/archives/,Vol. 5,iss. 2)。將資源zip文件提取到目錄。在上文中配置的Eclipse項目EclipseWebLogic中,選擇File>Import導入J2EE應用程序的src目錄。在Import窗體中,選擇File System節點,然后單擊Next按鈕。在File system窗體中,選擇directories/files添加項目,然后單擊Finish按鈕(見圖1)。

    配置WebLogic-Eclipse插件 圖-1

      圖1

      這樣便將示例J2EE應用程序文件添加到項目中。使用Ant build.xml文件構建項目。右鍵單擊build.xml,選擇Run>Ant Build即可構建J2EE應用程序并將其部署在WebLogic Server應用程序目錄中。接下來,選擇Run>Start WebLogic在Eclipse IDE中啟動WebLogic Server.這樣便將Session EJB/Servlet應用程序部署在 WebLogic Server中,如應用程序節點所示。

      在瀏覽器中輸入URL http://localhost:7001/weblogic/webLogicPlug-in運行WebLogicServlet. servlet的輸出將在瀏覽器中顯示。接下來向客戶端servlet添加一個異常(NullPointerException),以驗證WebLogic插件的調試功能。在WebLogicServlet servlet中將:

      out.println(sessionEJB.getEclipsePlug-in());

      替換為:

      String str=null;

      out.println(str.toString());

      選擇Run>Add Java Exception Breakpoint向servlet添加一個斷點。在Add Java Exception Breakpoint窗體中,選擇NullPointerException.刪除之前構建的目錄并使用build.xml構建應用程序。選擇Debug perspective.在Debug perspective可以看到WebLogic Server正運行在localhost主機中。

      在瀏覽器中運行示例servlet(帶NullPointerException)。因為servlet帶有異常,所以服務器被中斷,并且Debug perspective顯示NullPointerException.使用Run菜單項中的調試功能可以調試應用程序。

      結束語

      綜上所述,使用WebLogic插件可以通過Eclipse IDE管理WebLogic Server,還可通過Eclipse IDE調試服務器中部署的應用程序。WebLogic插件的局限性在于不支持JSP調試。該插件的2.0版本將有更多功能。

    J2EE配置WebLogic-Eclipse插件

    posted @ 2008-01-24 15:32 菠蘿 閱讀(451) | 評論 (0)編輯 收藏

    C3P0連接池詳細配置(轉)

    <c3p0-config>
    <default-config>
    <!--當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數。Default: 3 -->
    <property name="acquireIncrement">3</property>

    <!--定義在從數據庫獲取新連接失敗后重復嘗試的次數。Default: 30 -->
    <property name="acquireRetryAttempts">30</property>

    <!--兩次連接中間隔時間,單位毫秒。Default: 1000 -->
    <property name="acquireRetryDelay">1000</property>

    <!--連接關閉時默認將所有未提交的操作回滾。Default: false -->
    <property name="autoCommitOnClose">false</property>

    <!--c3p0將建一張名為Test的空表,并使用其自帶的查詢語句進行測試。如果定義了這個參數那么
    屬性preferredTestQuery將被忽略。你不能在這張Test表上進行任何操作,它將只供c3p0測試
    使用。Default: null-->
    <property name="automaticTestTable">Test</property>

    <!--獲取連接失敗將會引起所有等待連接池來獲取連接的線程拋出異常。但是數據源仍有效
    保留,并在下次調用getConnection()的時候繼續嘗試獲取連接。如果設為true,那么在嘗試
    獲取連接失敗后該數據源將申明已斷開并永久關閉。Default: false-->
    <property name="breakAfterAcquireFailure">false</property>

    <!--當連接池用完時客戶端調用getConnection()后等待獲取新連接的時間,超時后將拋出
    SQLException,如設為0則無限期等待。單位毫秒。Default: 0 -->
    <property name="checkoutTimeout">100</property>

    <!--通過實現ConnectionTester或QueryConnectionTester的類來測試連接。類名需制定全路徑。
    Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->
    <property name="connectionTesterClassName"></property>

    <!--指定c3p0 libraries的路徑,如果(通常都是這樣)在本地即可獲得那么無需設置,默認null即可
    Default: null-->
    <property name="factoryClassLocation">null</property>

    <!--Strongly disrecommended. Setting this to true may lead to subtle and bizarre bugs.
    (文檔原文)作者強烈建議不使用的一個屬性-->
    <property name="forceIgnoreUnresolvedTransactions">false</property>

    <!--每60秒檢查所有連接池中的空閑連接。Default: 0 -->
    <property name="idleConnectionTestPeriod">60</property>

    <!--初始化時獲取三個連接,取值應在minPoolSize與maxPoolSize之間。Default: 3 -->
    <property name="initialPoolSize">3</property>

    <!--最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。Default: 0 -->
    <property name="maxIdleTime">60</property>

    <!--連接池中保留的最大連接數。Default: 15 -->
    <property name="maxPoolSize">15</property>

    <!--JDBC的標準參數,用以控制數據源內加載的PreparedStatements數量。但由于預緩存的statements
    屬于單個connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素。
    如果maxStatements與maxStatementsPerConnection均為0,則緩存被關閉。Default: 0-->
    <property name="maxStatements">100</property>

    <!--maxStatementsPerConnection定義了連接池內單個連接所擁有的最大緩存statements數。Default: 0 -->
    <property name="maxStatementsPerConnection"></property>

    <!--c3p0是異步操作的,緩慢的JDBC操作通過幫助進程完成。擴展這些操作可以有效的提升性能
    通過多線程實現多個操作同時被執行。Default: 3-->
    <property name="numHelperThreads">3</property>

    <!--當用戶調用getConnection()時使root用戶成為去獲取連接的用戶。主要用于連接池連接非c3p0
    的數據源時。Default: null-->
    <property name="overrideDefaultUser">root</property>

    <!--與overrideDefaultUser參數對應使用的一個參數。Default: null-->
    <property name="overrideDefaultPassword">password</property>

    <!--密碼。Default: null-->
    <property name="password"></property>

    <!--定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個一顯著提高測試速度。注意:
    測試的表必須在初始數據源的時候就存在。Default: null-->
    <property name="preferredTestQuery">select id from test where id=1</property>

    <!--用戶修改系統配置參數執行前最多等待300秒。Default: 300 -->
    <property name="propertyCycle">300</property>

    <!--因性能消耗大請只在需要的時候使用它。如果設為true那么在每個connection提交的
    時候都將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable
    等方法來提升連接測試的性能。Default: false -->
    <property name="testConnectionOnCheckout">false</property>

    <!--如果設為true那么在取得連接的同時將校驗連接的有效性。Default: false -->
    <property name="testConnectionOnCheckin">true</property>

    <!--用戶名。Default: null-->
    <property name="user">root</property>

    <!--早期的c3p0版本對JDBC接口采用動態反射代理。在早期版本用途廣泛的情況下這個參數
    允許用戶恢復到動態反射代理以解決不穩定的故障。最新的非反射代理更快并且已經開始
    廣泛的被使用,所以這個參數未必有用。現在原先的動態反射與新的非反射代理同時受到
    支持,但今后可能的版本可能不支持動態反射代理。Default: false-->
    <property name="usesTraditionalReflectiveProxies">false</property>

    <property name="automaticTestTable">con_test</property>
    <property name="checkoutTimeout">30000</property>
    <property name="idleConnectionTestPeriod">30</property>
    <property name="initialPoolSize">10</property>
    <property name="maxIdleTime">30</property>
    <property name="maxPoolSize">25</property>
    <property name="minPoolSize">10</property>
    <property name="maxStatements">0</property>
    <user-overrides user="swaldman">
    </user-overrides>
    </default-config>
    <named-config name="dumbTestConfig">
    <property name="maxStatements">200</property>
    <user-overrides user="poop">
    <property name="maxStatements">300</property>
    </user-overrides>
    </named-config>
    </c3p0-config>

    posted @ 2008-01-18 11:58 菠蘿 閱讀(242) | 評論 (0)編輯 收藏

    主站蜘蛛池模板: 国产亚洲综合一区二区三区| 精品成人免费自拍视频| 久久久久久A亚洲欧洲AV冫| a级毛片免费观看视频| 亚洲国产情侣一区二区三区| 成年大片免费视频| 精品97国产免费人成视频| 亚洲黄网在线观看| 亚洲精品456播放| 最近2019中文字幕免费直播 | 中文字幕一精品亚洲无线一区| 色欲色香天天天综合网站免费| 亚洲精品无码专区在线播放| 狠狠色伊人亚洲综合成人| 成人免费无码大片a毛片| 免费萌白酱国产一区二区三区| 亚洲入口无毒网址你懂的| 国产亚洲?V无码?V男人的天堂| 波多野结衣中文字幕免费视频 | 女人18毛片a级毛片免费视频| 中国一级特黄高清免费的大片中国一级黄色片 | 国产成人精品免费视频网页大全| 国产亚洲精品91| 亚洲人成777在线播放| 亚洲乱码国产乱码精品精| 日本午夜免费福利视频| 91久久成人免费| 成全在线观看免费观看大全| 亚洲精品9999久久久久无码| 亚洲福利在线视频| 国产亚洲精品拍拍拍拍拍| 国产高清视频在线免费观看| 久久久久久影院久久久久免费精品国产小说 | 日本免费一区二区在线观看| 一级**爱片免费视频| 久久亚洲中文字幕无码| 亚洲ts人妖网站| 亚洲视屏在线观看| 亚洲av日韩av高潮潮喷无码| 亚洲av手机在线观看| 日韩a级毛片免费视频|