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

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

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

    posts - 189,comments - 115,trackbacks - 0

    ?

    Java怎樣調用外部應用程序
    [轉貼]???

    import?java.io.*;
    class?Runtime1
    {
    ????public?Runtime1()?
    ????{
    ????????try{
    ????????Runtime.getRuntime().exec("C:\\Program?Files\\Microsoft?Visual?Studio\\Common\\MSDev98\\Bin\\MSDEV.EXE");????
    ????????}
    ????????catch(Exception?e)
    ????????{
    ????????}
    ????}
    ????public?static?void?main(String?[]args)
    ????{
    ????????new?Runtime1();
    ????}
    }
    //把代碼第7行exec后面的括號里里面換成你應用程序的路徑即可,注意路徑加雙斜桿.


    用Java?Socket開發小型服務器,支持上千個并發
    [轉]

    Java?Socket
    套接字(socket)為兩臺計算機之間的通信提供了一種機制,在James?Gosling注意到Java?語言之前,套接字就早已赫赫有名。該語言只是讓您不必了解底層操作系統的細節就能有效地使用套接字。
    1?客戶機/服務器模型
    在飯店里,菜單上各種具有異國情調的食品映入你的眼簾,于是你要了一份pizza。幾分鐘后,你用力咀嚼澆著融化的乳酪和其他你喜歡的配料的熱pizza。你不知道,也不想知道:侍者從那里弄來了pizza,在制作過程中加進了什么,以及配料是如何獲得的。
    上例中包含的實體有:美味的pizza、接受你定餐的侍者、制作pizza的廚房,當然還有你。你是定pizza的顧客或客戶。制作pizza的過程對于你而言是被封裝的。你的請求在廚房中被處理,pizza制作完成后,由侍者端給你。
    你所看到的就是一個客戶機/服務器模型。客戶機向服務器發送一個請求或命令。服務器處理客戶機的請求。客戶機和服務器之間的通訊是客戶機/服務器模型中的一個重要組成部分,通常通過網絡進行。
    客戶機/服務器模型是一個應用程序開發框架,該框架是為了將數據的表示與其內部的處理和存儲分離開來而設計的。客戶機請求服務,服務器為這些請求服務。請求通過網絡從客戶機傳遞到服務器。服務器所進行的處理對客戶機而言是隱藏的。一個服務器可以為多臺客戶機服務。
    ?多臺客戶機訪問服務器
    服務器和客戶機不一定是硬件組件。它們可以是工作啊同一機器或不同機器上的程序。、
    考慮一個航空定票系統中的數據輸入程序:數據----乘客名、航班號、飛行日期、目的地等可以被輸入到前端----客戶機的應用程序中。一旦數據輸入之后,客戶機將數據發送到后端----服務器端。服務器處理數據并在數據庫中保存數據。客戶機/服務器模型的重要性在于所有的數據都存放在同一地點。客戶機從不同的地方訪問同一數據源,服務器對所有的輸入數據應用同樣的檢驗規則。
    萬維網為‘為什么要將數據的表示與其存儲、處理分離開來’提供了一個很好的例子。在Web上,你無需控制最終用戶用來訪問你數據的平臺和軟件。你可以考慮編寫出適用與每一種潛在的目標平臺的應用程序。
    ‘客戶機/服務器應用程序的服務器部分’管理通過多個客戶機訪問服務器的、多個用戶共享的資源。表明‘客戶機/服務器程序的服務器部分’強大功能的最好例子應該是Web服務器,它通過Internet將HTML頁傳遞給不同的Web用戶。
    Java編程語言中最基本的特點是在Java中創建的程序的代碼的可移植性。因為具有其他語言所不具備的代碼可移植性,Java允許用戶只要編寫一次應用程序,就可以在任何客戶機系統上發布它,并可以讓客戶機系統解釋該程序。這意味著:你只要寫一次代碼,就能使其在任何平臺上運行。

    2?協議
    當你同朋友交談時,你們遵循一些暗含的規則(或協議)。例如:你們倆不能同時開始說話,或連續不間斷地說話。如果你們這樣作的話,誰也不能理解對方所說的東西。當你說話時,你的朋友傾聽,反之亦然。你們以雙方都能理解的語言和速度進行對話。
    當計算機之間進行通訊的時候,也需要遵循一定的規則。數據以包的形式從一臺機器發送到另一臺。這些規則管理數據打包、數據傳輸速度和重新?數據將其恢復成原始形式。這些規則被稱為網絡協議。網絡協議是通過網絡進行通訊的系統所遵循的一系列規則和慣例。連網軟件通常實現有高低層次之分的多層協議。網絡協議的例子有:TCP/IP、UDP、Apple?Talk和NetBEUI。
    Java提供了一個豐富的、支持網絡的類庫,這些類使得應用程序能方便地訪問網絡資源。Java提供了兩種通訊工具。它們是:使用用戶報文協議(UDP)的報文和使用傳輸控制協議/因特網協議(TCP/IP)的Sockets(套接字)。
    數據報包是一個字節數組從一個程序(發送程序)傳送到另一個(接受程序)。由于數據報遵守UDP,不保證發出的數據包必須到達目的地。數據報并不是可信賴的。因此,僅當傳送少量數據時才使用,而且發送者和接受者之間的距離間隔不大,假如是網絡交通高峰,或接受程序正處理來自其他程序的多個請求,就有機會出現數據報包的丟失。
    Sockets套接字用TCP來進行通訊。套接字模型同其他模型相比,優越性在于其不受客戶請求來自何處的影響。只要客戶機遵循TCP/IP協議,服務器就會對它的請求提供服務。這意味著客戶機可以是任何類型的計算機。客戶機不再局限為UNIX、Windows、DOS或Macintosh平臺,因此,網上所有遵循TCP/IP協議的計算機可以通過套接字互相通訊。

    3?Sockets套接字
    3.1?Sockets概況
    在客戶機/服務器應用程序中,服務器提供象處理數據庫查詢或修改數據庫中的數據之類的服務。發生在客戶機和服務器之間的通訊必須是可靠的,同時數據在客戶機上的次序應該和服務器發送出來的次序相同。
    什么是套接字??
    既然我們已經知道套接字扮演的角色,那么剩下的問題是:什么是套接字?Bruce?Eckel?在他的《Java?編程思想》一書中這樣描述套接字:套接字是一種軟件抽象,用于表達兩臺機器之間的連接“終端”。對于一個給定的連接,每臺機器上都有一個套接字,您也可以想象它們之間有一條虛擬的“電纜”,“電纜”的每一端都插入到套接字中。當然,機器之間的物理硬件和電纜連接都是完全未知的。抽象的全部目的是使我們無須知道不必知道的細節。?
    簡言之,一臺機器上的套接字與另一臺機器上的套接字交談就創建一條通信通道。程序員可以用該通道來在兩臺機器之間發送數據。當您發送數據時,TCP/IP?協議棧的每一層都會添加適當的報頭信息來包裝數據。這些報頭幫助協議棧把您的數據送到目的地。好消息是?Java?語言通過"流"為您的代碼提供數據,從而隱藏了所有這些細節,這也是為什么它們有時候被叫做流套接字(streaming?socket)的原因。
    把套接字想成兩端電話上的聽筒,我和您通過專用通道在我們的電話聽筒上講話和聆聽。直到我們決定掛斷電話,對話才會結束(除非我們在使用蜂窩電話)。而且我們各自的電話線路都占線,直到我們掛斷電話。
    如果想在沒有更高級機制如?ORB(以及?CORBA、RMI、IIOP?等等)開銷的情況下進行兩臺計算機之間的通信,那么套接字就適合您。套接字的低級細節相當棘手。幸運的是,Java?平臺給了您一些雖然簡單但卻強大的更高級抽象,使您可以容易地創建和使用套接字。
    傳輸控制協議(TCP)提供了一條可靠的、點對點的通訊通道,客戶機/服務器應用程序可以用該通道互相通訊。要通過TCP進行通訊,客戶機和服務器程序建立連接并綁定套接字。套接字用于處理通過網絡連接的應用程序之間的通訊。客戶機和服務器之間更深入的通訊通過套接字完成。
    Java被設計成一種連網語言。它通過將連接功能封裝到套接字類里而使得網絡編程更加容易。套接字類即Socket類(它創建一個客戶套接字)和ServerSocket類(它創建一個服務器套接字)。套接字類大致介紹如下:
    l????Socket是基類,它支持TCP協議。TCP是一個可靠的流網絡連接協議。Socket類提供了流輸入/輸出的方法,使得從套接字中讀出數據和往套接字中寫數據都很容易。該類對于編寫因特網上的通訊程序而言是必不可少的。
    l????ServerSocket是一個因特網服務程序用來監聽客戶請求的類。ServerSocket實際上并不執行服務;而是創建了一個Socket對象來代表客戶機。通訊由創建的對象來完成。
    3.2?IP地址和端口
    因特網服務器可以被認為是一組套接字類,它們提供了一般稱為服務的附加功能。服務的例子有:電子郵件、遠程登錄的Telnet、和通過網絡傳輸文件的文件傳輸協議(FTP)。每種服務都與一個端口相聯系。端口是一個數值地址,通過它來處理服務請求(就象請求Web頁一樣)。
    TCP協議需要兩個數據項:IP地址和端口號。因此,當鍵入http://www.jinnuo.com/時,你是如何進入金諾的主頁呢?
    因特網協議(IP)提供每一項網絡設備。這些設備都帶有一個稱為IP地址的邏輯地址。由因特網協議提供的IP地址具有特定的形式。每個IP地址都是32位的數值,表示4個范圍在0到255之間的8位數值金諾已經注冊了它的名字,分配給http://www.jinnuo.com/的IP地址為192.168.0.110。
    注意:域名服務或DNS服務是將http://www.jinnuo.com/翻譯成192.168.0.110的服務。這使你可以鍵入http://www.jinnuo.com/而不必記住IP地址。想象一下,怎么可能記住所有需要訪問的站點的IP地址!有趣的是一個網絡名可以映射到許多IP地址。對于經常訪問的站點可能需要這一功能,因為這些站點容納大量的信息,并需要多個IP地址來提供業務服務。例如:192.168.0.110的實際的內部名稱為http://www.jinnuo.com/。DNS可以將分配給jinnuo?Ltd.的一系列IP地址翻譯成http://www.jinnuo.com/
    如果沒有指明端口號,則使用服務文件中服務器的端口。每種協議有一個缺省的端口號,在端口號未指明時使用該缺省端口號。
    端口號????應用
    21????FTP.傳輸文件
    23????Telnet.提供遠程登錄
    25????SMTP.傳遞郵件信息
    67????BOOTP.在啟動時提供配置情況
    80????HTTP.傳輸Web頁
    109????POP.使用戶能訪問遠程系統中的郵箱
    讓我們再來看一下URL:http://www.jinnuo.com/
    URL的第一部分(http)意味著你正在使用超文本傳輸協議(HTTP),該協議處理Web文檔。如果沒有指明文件,大多數的Web服務器會取一個叫index.html文件。因此,IP地址和端口既可以通過明確指出URL各部分來決定,也可以由缺省值決定。
    4?創建Socket客戶
    我們將在本部分討論的示例將闡明在?Java?代碼中如何使用?Socket?和?ServerSocket。客戶機用?Socket?連接到服務器。服務器用?ServerSocket?在端口?1001?偵聽。客戶機請求服務器?C:?驅動器上的文件內容。
    創建?RemoteFileClient?類
    import?java.io.*;
    import?java.net.*;
    public?class?RemoteFileClient?{
    ????protected?BufferedReader?socketReader;
    ????protected?PrintWriter?socketWriter;
    ????protected?String?hostIp;
    ????protected?int?hostPort;
    ????//構造方法
    ????public?RemoteFileClient(String?hostIp,?int?hostPort)?{
    ????????this.hostIp?=?hostIp;
    ????????this.hostPort=hostPort;?
    ????}
    ????//向服務器請求文件的內容
    ????public?String?getFile(String?fileNameToGet)?{
    ????????StringBuffer?fileLines?=?new?StringBuffer();
    ????????try?{
    ????????????socketWriter.println(fileNameToGet);????????????
    ????????????socketWriter.flush();
    ????????????String?line?=?null;
    ????????????while((line=socketReader.readLine())!=null)
    ????????????????fileLines.append(line+"\n");
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println("Error?reading?from?file:?"+fileNameToGet);
    ????????}
    ????????return?fileLines.toString();
    ????}
    ????//連接到遠程服務器
    ????public?void?setUpConnection()?{
    ????????try?{
    ????????????Socket?client?=?new?Socket(hostIp,hostPort);
    ????????????socketReader?=?new?BufferedReader(new?InputStreamReader(client.getInputStream()));
    ????????????socketWriter?=?new?PrintWriter(client.getOutputStream());
    ????????}
    ????????catch(UnknownHostException?e)?{
    ????????????System.out.println("Error1?setting?up?socket?connection:?unknown?host?at?"+hostIp+":"+hostPort);
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println("Error2?setting?up?socket?connection:?"+e);
    ????????}
    ????}
    ????//斷開遠程服務器
    ????public?void?tearDownConnection()?{
    ????????try?{
    ????????????socketWriter.close();?
    ????????????socketReader.close();
    ????????}catch(IOException?e)?{????????????
    ????????????System.out.println("Error?tearing?down?socket?connection:?"+e);
    ????????}
    ????}
    ????public?static?void?main(String?args[])?{
    ????????RemoteFileClient?remoteFileClient?=?new?RemoteFileClient("127.0.0.1",1001);
    ????????remoteFileClient.setUpConnection();
    ????????StringBuffer?fileContents?=?new?StringBuffer();
    ????????fileContents.append(remoteFileClient.getFile("RemoteFileServer.java"));????????
    ????????//remoteFileClient.tearDownConnection();
    ????????System.out.println(fileContents);
    ????}
    }
    首先我們導入?java.net?和?java.io。java.net?包為您提供您需要的套接字工具。java.io?包為您提供對流進行讀寫的工具,這是您與?TCP?套接字通信的唯一途徑。
    我們給我們的類實例變量以支持對套接字流的讀寫和存儲我們將連接到的遠程主機的詳細信息。
    我們類的構造器有兩個參數:遠程主機的IP地址和端口號各一個,而且構造器將它們賦給實例變量。
    我們的類有一個?main()?方法和三個其它方法。稍后我們將探究這些方法的細節。現在您只需知道?setUpConnection()?將連接到遠程服務器,getFile()?將向遠程服務器請求?fileNameToGet?的內容以及?tearDownConnection()?將從遠程服務器上斷開。
    實現?main()
    這里我們實現?main()?方法,它將創建?RemoteFileClient?并用它來獲取遠程文件的內容,然后打印結果。main()?方法用主機的?IP?地址和端口號實例化一個新?RemoteFileClient(客戶機)。然后,我們告訴客戶機建立一個到主機的連接。接著,我們告訴客戶機獲取主機上一個指定文件的內容。最后,我們告訴客戶機斷開它到主機的連接。我們把文件內容打印到控制臺,只是為了證明一切都是按計劃進行的。
    建立連接
    這里我們實現?setUpConnection()?方法,它將創建我們的?Socket?并讓我們訪問該套接字的流:
    ????public?void?setUpConnection()?{
    ????????try?{
    ????????????Socket?client?=?new?Socket(hostIp,hostPort);
    ????????????socketReader?=?new?BufferedReader(new?InputStreamReader(client.getInputStream()));
    ????????????socketWriter?=?new?PrintWriter(client.getOutputStream());
    ????????}
    ????????catch(UnknownHostException?e)?{
    ????????????System.out.println("Error1?setting?up?socket?connection:?unknown?host?at?"+hostIp+":"+hostPort);
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println("Error2?setting?up?socket?connection:?"+e);
    ????????}
    ????}
    setUpConnection()?方法用主機的?IP?地址和端口號創建一個?Socket:
    Socket?client?=?new?Socket(hostIp,?hostPort);
    我們把?Socket?的?InputStream?包裝進?BufferedReader?以使我們能夠讀取流的行。然后,我們把?Socket?的?OutputStream?包裝進?PrintWriter?以使我們能夠發送文件請求到服務器:
    socketReader?=?new?BufferedReader(new?InputStreamReader(client.getInputStream()));socketWriter?=?new?PrintWriter(client.getOutputStream());
    請記住我們的客戶機和服務器只是來回傳送字節。客戶機和服務器都必須知道另一方即將發送的是什么以使它們能夠作出適當的響應。在這個案例中,服務器知道我們將發送一條有效的文件路徑。
    當您實例化一個?Socket?時,將拋出?UnknownHostException。這里我們不特別處理它,但我們打印一些信息到控制臺以告訴我們發生了什么錯誤。同樣地,當我們試圖獲取?Socket?的?InputStream?或?OutputStream?時,如果拋出了一個一般?IOException,我們也打印一些信息到控制臺。
    與主機交談
    這里我們實現?getFile()?方法,它將告訴服務器我們想要什么文件并在服務器傳回其內容時接收該內容。
    ????public?String?getFile(String?fileNameToGet)?{
    ????????StringBuffer?fileLines?=?new?StringBuffer();
    ????????try?{
    ????????????socketWriter.println(fileNameToGet);????????????
    ????????????socketWriter.flush();
    ????????????String?line?=?null;
    ????????????while((line=socketReader.readLine())!=null)
    ????????????????fileLines.append(line+"\n");
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println("Error?reading?from?file:?"+fileNameToGet);
    ????????}
    ????????return?fileLines.toString();
    ????}
    對getFile()方法的調用要求一個有效的文件路徑String。它首先創建名為fileLines的?StringBuffer,fileLines?用于存儲我們讀自服務器上的文件的每一行。
    StringBuffer?fileLines?=?new?StringBuffer();
    在?try{}catch{}?塊中,我們用?PrintWriter?把請求發送到主機,PrintWriter?是我們在創建連接期間建立的。
    ????socketWriter.println(fileNameToGet);????socketWriter.flush();
    請注意這里我們是?flush()?該?PrintWriter,而不是關閉它。這迫使數據被發送到服務器而不關閉?Socket。
    一旦我們已經寫到?Socket,我們就希望有一些響應。我們不得不在?Socket?的?InputStream?上等待它,我們通過在?while?循環中調用?BufferedReader?上的?readLine()?來達到這個目的。我們把每一個返回行附加到?fileLines?StringBuffer(帶有一個換行符以保護行):
    ????String?line?=?null;????while((line=socketReader.readLine())!=null)????????fileLines.append(line+"\n");
    斷開連接
    這里我們實現?tearDownConnection()?方法,它將在我們使用完畢連接后負責“清除”。tearDownConnection()方法只是分別關閉我們在Socket的InputStream和OutputStream上創建的?BufferedReader和PrintWriter。這樣做會關閉我們從Socket獲取的底層流,所以我們必須捕捉可能的?IOException。
    總結一下客戶機
    我們的類研究完了。在我們繼續往前討論服務器端的情況之前,讓我們回顧一下創建和使用?Socket?的步驟:
    1.????用您想連接的機器的?IP?地址和端口實例化?Socket(如有問題則拋出?Exception)。
    2.????獲取?Socket?上的流以進行讀寫。
    3.????把流包裝進?BufferedReader/PrintWriter?的實例,如果這樣做能使事情更簡單的話。
    4.????對?Socket?進行讀寫。
    5.????關閉打開的流。?
    5?創建服務器Socket
    創建?RemoteFileServer?類
    import?java.io.*;
    import?java.net.*;
    public?class?RemoteFileServer?{????
    ????int?listenPort;
    ????public?RemoteFileServer(int?listenPort)?{
    ????????this.listenPort=listenPort;
    ????}
    ????//允許客戶機連接到服務器,等待客戶機請求????
    ????public?void?acceptConnections()?{
    ????????try?{
    ????????????ServerSocket?server?=?new?ServerSocket(listenPort);
    ????????????Socket?incomingConnection?=?null;
    ????????????while(true)?{
    ????????????????incomingConnection?=?server.accept();
    ????????????????handleConnection(incomingConnection);
    ????????????}
    ????????}
    ????????catch(BindException?e)?{
    ????????????System.out.println("Unable?to?bind?to?port?"+listenPort);
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println("Unable?to?instantiate?a?ServerSocket?on?port:?"+listenPort);??
    ????????????
    ????????}
    ????}
    ????//與客戶機Socket交互以將客戶機所請求的文件的內容發送到客戶機
    ????public?void?handleConnection(Socket?incomingConnection)?{
    ????????try?{
    ????????????OutputStream?outputToSocket?=?incomingConnection.getOutputStream();?
    ????????????InputStream?inputFromSocket?=?incomingConnection.getInputStream();
    ????????????BufferedReader?streamReader?=?new?BufferedReader(new?InputStreamReader(inputFromSocket));
    ????????????FileReader?fileReader?=?new?FileReader(new?File(streamReader.readLine()));
    ????????????BufferedReader?bufferedFileReader?=?new?BufferedReader(fileReader);?
    ????????????PrintWriter?streamWriter?=?new?PrintWriter(incomingConnection.getOutputStream());
    ????????????String?line?=?null;
    ????????????while((line=bufferedFileReader.readLine())!=null){
    ????????????????streamWriter.println(line);
    ????????????}
    ????????????fileReader.close();
    ????????????streamWriter.close();
    ????????????streamReader.close();
    ????????}
    ????????catch(Exception?e)?{
    ????????????System.out.println("Error?handling?a?client:?"+e);
    ????????????e.printStackTrace();?
    ????????}
    ????}
    ????public?static?void?main(String?args[])?{
    ????????RemoteFileServer?server?=?new?RemoteFileServer(1001);
    ????????server.acceptConnections();
    ????}
    }
    跟客戶機中一樣,我們首先導入java.net的java.io。接著,我們給我們的類一個實例變量以保存端口,我們從該端口偵聽進入的連接。缺省情況下,端口是1001。
    我們的類有一個main()方法和兩個其它方法。稍后我們將探究這些方法的細節。現在您只需知道acceptConnections()將允許客戶機連接到服務器以及handleConnection()與客戶機Socket交互以將您所請求的文件的內容發送到客戶機。
    實現?main()
    這里我們實現main()方法,它將創建RemoteFileServer并告訴它接受連接:服務器端的main()方法中,我們實例化一個新RemoteFileServer,它將在偵聽端口(1001)上偵聽進入的連接請求。然后我們調用acceptConnections()來告訴該server進行偵聽。
    接受連接
    這里我們實現?acceptConnections()?方法,它將創建一個?ServerSocket?并等待連接請求:
    ????public?void?acceptConnections()?{
    ????????try?{
    ????????????ServerSocket?server?=?new?ServerSocket(listenPort);
    ????????????Socket?incomingConnection?=?null;
    ????????????while(true)?{
    ????????????????incomingConnection?=?server.accept();
    ????????????????handleConnection(incomingConnection);
    ????????????}
    ????????}
    ????????catch(BindException?e)?{
    ????????????System.out.println("Unable?to?bind?to?port?"+listenPort);
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println("Unable?to?instantiate?a?ServerSocket?on?port:?"+listenPort);??
    ????????????
    ????????}
    ????}
    acceptConnections()用欲偵聽的端口號來創建ServerSocket。然后我們通過調用該ServerSocket的accept()來告訴它開始偵聽。accept()方法將造成阻塞直到來了一個連接請求。此時,accept()返回一個新的Socket,這個Socket綁定到服務器上一個隨機指定的端口,返回的Socket被傳遞給handleConnection()。請注意我們在一個無限循環中處理對連接的接受。這里不支持任何關機。
    無論何時如果您創建了一個無法綁定到指定端口(可能是因為別的什么控制了該端口)的?ServerSocket,Java代碼都將拋出一個錯誤。所以這里我們必須捕捉可能的BindException。就跟在客戶機端上時一樣,我們必須捕捉IOException,當我們試圖在ServerSocket上接受連接時,它就會被拋出。請注意,您可以通過用毫秒數調用setSoTimeout()來為accept()調用設置超時,以避免實際長時間的等待。調用setSoTimeout()將使accept()經過指定占用時間后拋出IOException。
    處理連接
    這里我們實現handleConnection()方法,它將用連接的流來接收輸入和寫輸出:
    ????public?void?handleConnection(Socket?incomingConnection)?{
    ????????try?{
    ????????????OutputStream?outputToSocket?=?incomingConnection.getOutputStream();?
    ????????????InputStream?inputFromSocket?=?incomingConnection.getInputStream();
    ????????????BufferedReader?streamReader?=?new?BufferedReader(new?InputStreamReader(inputFromSocket));
    ????????????FileReader?fileReader?=?new?FileReader(new?File(streamReader.readLine()));
    ????????????BufferedReader?bufferedFileReader?=?new?BufferedReader(fileReader);?
    ????????????PrintWriter?streamWriter?=?new?PrintWriter(incomingConnection.getOutputStream());
    ????????????String?line?=?null;
    ????????????while((line=bufferedFileReader.readLine())!=null){
    ????????????????streamWriter.println(line);
    ????????????}
    ????????????fileReader.close();
    ????????????streamWriter.close();
    ????????????streamReader.close();
    ????????}
    ????????catch(Exception?e)?{
    ????????????System.out.println("Error?handling?a?client:?"+e);
    ????????????e.printStackTrace();?
    ????????}
    ????}
    跟在客戶機中一樣,我們用getOutputStream()和getInputStream()來獲取與我們剛創建的Socket相關聯的流。跟在客戶機端一樣,我們把InputStream包裝進BufferedReader,把OutputStream包裝進PrintWriter。在服務器端上,我們需要添加一些代碼,用來讀取目標文件和把內容逐行發送到客戶機。這里是重要的代碼:
    ????FileReader?fileReader?=?new?FileReader(new?File(streamReader.readLine()));????BufferedReader?bufferedFileReader?=?new?BufferedReader(fileReader);????String?line?=?null;????while((line=bufferedFileReader.readLine())!=null)?{????????streamWriter.println(line);????}
    這些代碼值得詳細解釋。讓我們一點一點來看:
    ????FileReader?fileReader?=?new?FileReader(new?File(streamReader.readLine()));
    首先,我們使用Socket?的InputStream的BufferedReader。我們應該獲取一條有效的文件路徑,所以我們用該路徑名構造一個新File。我們創建一個新FileReader來處理讀文件的操作。
    ????BufferedReader?bufferedFileReader?=?new?BufferedReader(fileReader);
    這里我們把FileReader包裝進BufferedReader以使我們能夠逐行地讀該文件。
    接著,我們調用BufferedReader的readLine()。這個調用將造成阻塞直到有字節到來。我們獲取一些字節之后就把它們放到本地的line變量中,然后再寫出到客戶機上。完成讀寫操作之后,我們就關閉打開的流。
    請注意我們在完成從Socket的讀操作之后關閉streamWriter和streamReader。您或許會問我們為什么不在讀取文件名之后立刻關閉streamReader。原因是當您這樣做時,您的客戶機將不會獲取任何數據。如果您在關閉streamWriter之前關閉streamReader,則您可以往Socket寫任何東西,但卻沒有任何數據能通過通道(通道被關閉了)。
    總結一下服務器
    在我們接著討論另一個更實際的示例之前,讓我們回顧一下創建和使用ServerSocket的步驟:
    1.????用一個您想讓它偵聽傳入客戶機連接的端口來實例化一個ServerSocket(如有問題則拋出?Exception)。
    2.????調用ServerSocket的accept()以在等待連接期間造成阻塞。
    3.????獲取位于該底層Socket的流以進行讀寫操作。
    4.????按使事情簡單化的原則包裝流。
    5.????對Socket進行讀寫。
    6.????關閉打開的流(并請記住,永遠不要在關閉Writer之前關閉Reader)。?
    6?創建多線程Socket服務器
    前面的示例教給您基礎知識,但并不能令您更深入。如果您到此就停止了,那么您一次只能處理一臺客戶機。原因是handleConnection()是一個阻塞方法。只有當它完成了對當前連接的處理時,服務器才能接受另一個客戶機。在多數時候,您將需要(也有必要)一個多線程服務器。
    創建?MultithreadedRemoteFileServer?類
    import?java.io.*;
    import?java.net.*;
    public?class?MultithreadedRemoteFileServer?{
    ????int?listenPort;
    ????public?MultithreadedRemoteFileServer(int?listenPort)?{????????
    ????????this.listenPort=listenPort;
    ????}
    ????//允許客戶機連接到服務器,等待客戶機請求????
    ????public?void?acceptConnections()?{
    ????????try?{
    ????????????ServerSocket?server?=?new?ServerSocket(listenPort,?5);
    ????????????Socket?incomingConnection?=?null;
    ????????????while(true)?{
    ????????????????incomingConnection?=?server.accept();?
    ????????????????handleConnection(incomingConnection);
    ????????????}
    ????????}????????
    ????????catch(BindException?e)?{
    ????????????System.out.println("Unable?to?bind?to?port?"+listenPort);
    ????????}?
    ????????catch(IOException?e)?{????????????
    ????????????System.out.println("Unable?to?instantiate?a?ServerSocket?on?port:?"+listenPort);????????
    ????????}
    ????}
    ????//與客戶機Socket交互以將客戶機所請求的文件的內容發送到客戶機????
    ????public?void?handleConnection(Socket?connectionToHandle)?{?
    ????????new?Thread(new?ConnectionHandler(connectionToHandle)).start();
    ????}
    ????public?static?void?main(String?args[])?{
    ????????MultithreadedRemoteFileServer?server?=?new?MultithreadedRemoteFileServer(1001);
    ????????server.acceptConnections();
    ????}
    }
    這里我們實現改動過acceptConnections()方法,它將創建一個能夠處理待發請求的ServerSocket,并告訴ServerSocket接受連接。
    新的?server?仍然需要acceptConnections(),所以這些代碼實際上是一樣的。突出顯示的行表示一個重大的不同。對這個多線程版,我們現在可以指定客戶機請求的最大數目,這些請求都能在實例化ServerSocket期間處于待發狀態。如果我們沒有指定客戶機請求的最大數目,則我們假設使用缺省值50。
    這里是它的工作機制。假設我們指定待發數(backlog?值)是5并且有五臺客戶機請求連接到我們的服務器。我們的服務器將著手處理第一個連接,但處理該連接需要很長時間。由于我們的待發值是5,所以我們一次可以放五個請求到隊列中。我們正在處理一個,所以這意味著還有其它五個正在等待。等待的和正在處理的一共有六個。當我們的服務器仍忙于接受一號連接(記住隊列中還有?2?6?號)時,如果有第七個客戶機提出連接申請,那么,該第七個客戶機將遭到拒絕。我們將在帶有連接池服務器示例中說明如何限定能同時連接的客戶機數目。
    處理連接:?
    ????public?void?handleConnection(Socket?connectionToHandle)?{?
    ????????new?Thread(new?ConnectionHandler(connectionToHandle)).start();
    ????}
    我們對RemoteFileServer所做的大改動就體現在這個方法上。我們仍然在服務器接受一個連接之后調用handleConnection(),但現在我們把該Socket傳遞給ConnectionHandler的一個實例,它是?Runnable的。我們用ConnectionHandler創建一個新?Thread?并啟動它。ConnectionHandler的run()方法包Socket讀/寫和讀File的代碼,這些代碼原來在RemoteFileServer的handleConnection()中。
    創建?ConnectionHandler?類
    import?java.io.*;
    import?java.net.*;
    public?class?ConnectionHandler?implements?Runnable?{
    ????protected?Socket?socketToHandle;
    ????public?ConnectionHandler(Socket?socketToHandle)?{
    ????????this.socketToHandle=socketToHandle;
    ????}
    ????public?void?run()?{
    ????????try?{
    ????????????PrintWriter?streamWriter?=?new?PrintWriter(socketToHandle.getOutputStream());?
    ????????????BufferedReader?streamReader?=?new?BufferedReader(new?InputStreamReader(socketToHandle.getInputStream()));
    ????????????String?fileToRead?=?streamReader.readLine();
    ????????????BufferedReader?fileReader?=?new?BufferedReader(new?FileReader(fileToRead));?
    ????????????String?line?=null;
    ????????????while((line=fileReader.readLine())!=null)?{
    ????????????????streamWriter.println(line);
    ????????????}
    ????????????fileReader.close();
    ????????????streamWriter.close();
    ????????????streamReader.close();
    ????????}
    ????????catch(Exception?e)?{
    ????????????System.out.println("Error?handling?a?client:?"+e);
    ????????e.printStackTrace();
    ????????}
    ????}
    }
    這個助手類相當簡單。跟我們到目前為止的其它類一樣,我們導入java.net和java.io。該類只有一個實例變量socketToHandle,它保存由該實例處理的Socket。
    類的構造器用一個Socket實例作參數并將它賦給socketToHandle。
    請注意該類實現了Runnable接口。實現這個接口的類都必須實現run()方法。這里我們實現run()方法,它將攫取我們的連接的流,用它來讀寫該連接,并在任務完成之后關閉它。ConnectionHandler的run()方法所做的事情就是RemoteFileServer上的handleConnection()所做的事情。首先,我們把InputStream和OutputStream分別包裝(用Socket的getOutputStream()和?getInputStream())進BufferedReader和PrintWriter。然后我們用這些代碼逐行地讀目標文件:
    PrintWriter?streamWriter?=?new?PrintWriter(socketToHandle.getOutputStream());?
    ????????????BufferedReader?streamReader?=?new?BufferedReader(new?InputStreamReader(socketToHandle.getInputStream()));
    ????????????String?fileToRead?=?streamReader.readLine();
    ????????????BufferedReader?fileReader?=?new?BufferedReader(new?FileReader(fileToRead));?
    ????????????String?line?=null;
    ????????????while((line=fileReader.readLine())!=null)?{
    ????????????????streamWriter.println(line);
    ????????????}
    請記住我們應該從客戶機獲取一條有效的文件路徑,這樣用該路徑名構造一個新File,把它包裝進FileReader以處理讀文件的操作,然后把它包裝進BufferedReader以讓我們逐行地讀該文件。我們while循環中調用BufferedReader上的readLine()直到不再有要讀的行。請記注,對readLine()的調用將造成阻塞,直到有字節來到為止。我們獲取一些字節之后就把它們放到本地的line變量中,然后寫出到客戶機上。完成讀寫操作之后,我們關閉打開的流。
    總結一下多線程服務器
    讓我們回顧一下創建和使用“多線程版”的服務器的步驟:
    1.????修改?acceptConnections()?以用缺省為?50(或任何您想要的大于?1?的指定數字)實例化?ServerSocket。
    2.????修改?ServerSocket?的?handleConnection()?以用?ConnectionHandler?的一個實例生成一個新的?Thread。
    3.????借用?RemoteFileServer?的?handleConnection()?方法的代碼實現?ConnectionHandler?類。?
    7?創建帶有連接池的Socket服務器
    我們現在已經擁有的?MultithreadedServer?每當有客戶機申請一個連接時都在一個新Thread中創建一個新ConnectionHandler。這意味著可能有一捆Thread“躺”在我們周圍。而且創建Thread的系統開銷并不是微不足道的。如果性能成為了問題(也請不要事到臨頭才意識到它),更高效地處理我們的服務器是件好事。那么,我們如何更高效地管理服務器端呢?我們可以維護一個進入的連接池,一定數量的ConnectionHandler將為它提供服務。這種設計能帶來以下好處:
    ?????它限定了允許同時連接的數目。?
    ?????我們只需啟動ConnectionHandler?Thread一次。?
    幸運的是,跟在我們的多線程示例中一樣,往代碼中添加“池”不需要來一個大改動。事實上,應用程序的客戶機端根本就不受影響。在服務器端,我們在服務器啟動時創建一定數量的?ConnectionHandler,我們把進入的連接放入“池”中并讓ConnectionHandler打理剩下的事情。這種設計中有很多我們不打算討論的可能存在的技巧。例如,我們可以通過限定允許在“池”中建立的連接的數目來拒絕客戶機。
    請注意:我們將不會再次討論acceptConnections()。這個方法跟前面示例中的完全一樣。它無限循環地調用ServerSocket上的?accept()?并把連接傳遞到handleConnection()。
    創建?PooledRemoteFileServer?類
    import?java.io.*;
    import?java.net.*;
    import?java.util.*;
    public?class?PooledRemoteFileServer?{
    ????protected?int?maxConnections;
    ????protected?int?listenPort;
    ????protected?ServerSocket?serverSocket;
    ????public?PooledRemoteFileServer(int?aListenPort,?int?maxConnections)?{
    ????????listenPort=?aListenPort;
    ????????this.maxConnections?=?maxConnections;
    ????}
    ????public?void?acceptConnections()?{
    ????????try?{
    ????????????ServerSocket?server?=?new?ServerSocket(listenPort,?5);
    ????????????Socket?incomingConnection?=?null;
    ????????????while(true)?{
    ????????????????incomingConnection?=?server.accept();
    ????????????????handleConnection(incomingConnection);
    ????????????}
    ????????}
    ????????catch(BindException?e)?{
    ????????????System.out.println("");
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println(""+listenPort);
    ????????}
    ????}
    ????protected?void?handleConnection(Socket?connectionToHandle)?{
    ????????PooledConnectionHandler.processRequest(connectionToHandle);
    ????}
    ????public?void?setUpHandlers()?{
    ????????for(int?i=0;?i<maxConnections;?i++)?{
    ????????????PooledConnectionHandler?currentHandler?=?new?PooledConnectionHandler();
    ????????????new?Thread(currentHandler,?"Handler?"?+?i).start();
    ????????}
    ????}
    ????public?static?void?main(String?args[])?{
    ????????PooledRemoteFileServer?server?=?new?PooledRemoteFileServer(1001,?3);
    ????????server.setUpHandlers();?
    ????????server.acceptConnections();
    ????}
    }
    請注意一下您現在應該熟悉了的?import?語句。我們給類以下實例變量以保存:
    ?????我們的服務器能同時處理的活動客戶機連接的最大數目
    ?????進入的連接的偵聽端口(我們沒有指定缺省值,但如果您想這樣做,并不會受到限制)
    ?????將接受客戶機連接請求的?ServerSocket?
    類的構造器用的參數是偵聽端口和連接的最大數目
    我們的類有一個?main()?方法和三個其它方法。稍后我們將探究這些方法的細節。現在只須知道setUpHandlers()創建數目為maxConnections的大量PooledConnectionHandler,而其它兩個方法則與我們前面已經看到的相似:acceptConnections()在ServerSocket上偵聽傳入的客戶機連接,而handleConnection則在客戶機連接一旦被建立后就實際處理它。
    實現?main()
    這里我們實現需作改動的main()方法,該方法將創建能夠處理給定數目的客戶機連接的PooledRemoteFileServer,并告訴它接受連接:
    ????public?static?void?main(String?args[])?{
    ????????PooledRemoteFileServer?server?=?new?PooledRemoteFileServer(1001,?3);
    ????????server.setUpHandlers();?
    ????????server.acceptConnections();
    ????}
    我們的main()方法很簡單。我們實例化一個新的PooledRemoteFileServer,它將通過調用setUpHandlers()來建立三個PooledConnectionHandler。一旦服務器就緒,我們就告訴它acceptConnections()。
    建立連接處理程序
    ???public?void?setUpHandlers()?{
    ????????for(int?i=0;?i<maxConnections;?i++)?{
    ????????????PooledConnectionHandler?currentHandler?=?new?PooledConnectionHandler();
    ????????????new?Thread(currentHandler,?"Handler?"?+?i).start();
    ????????}
    ????}
    setUpHandlers()方法創建maxConnections(例如?3)個PooledConnectionHandler并在新Thread中激活它們。用實現了Runnable的對象來創建Thread使我們可以在Thread調用start()并且可以期望在Runnable上調用了run()。換句話說,我們的PooledConnectionHandler將等著處理進入的連接,每個都在它自己的Thread中進行。我們在示例中只創建三個Thread,而且一旦服務器運行,這就不能被改變。
    處理連接
    這里我們實現需作改動的handleConnections()方法,它將委派PooledConnectionHandler處理連接:
    ????protected?void?handleConnection(Socket?connectionToHandle)?{
    ????????PooledConnectionHandler.processRequest(connectionToHandle);
    ????}
    我們現在叫?PooledConnectionHandler?處理所有進入的連接(processRequest()?是一個靜態方法)。
    創建?PooledRemoteFileServer?類
    import?java.io.*;
    import?java.net.*;
    import?java.util.*;
    public?class?PooledConnectionHandler?implements?Runnable?{
    ????protected?Socket?connection;
    ????protected?static?List?pool?=?new?LinkedList();
    ????public?PooledConnectionHandler()?{}
    ????public?void??handleConnection()?{
    ????????try?{
    ????????????PrintWriter?streamWriter?=?new?PrintWriter(connection.getOutputStream());?
    ????????????BufferedReader?streamReader?=?new?BufferedReader(new?InputStreamReader(connection.getInputStream()));?
    ????????????String?fileToRead?=?streamReader.readLine();
    ????????????BufferedReader?fileReader?=?new?BufferedReader(new?FileReader(fileToRead));
    ????????????String?line?=?null;
    ????????????while((line=fileReader.readLine())!=null)
    ????????????????streamWriter.println(line);?
    ????????????fileReader.close();
    ????????????streamWriter.close();
    ????????????streamReader.close();
    ????????}
    ????????catch(FileNotFoundException?e)?{
    ????????????System.out.println("");
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println(""+e);
    ????????}
    ????}
    ????public?static?void?processRequest(Socket?requestToHandle)?{
    ????????synchronized(pool)?{
    ????????????pool.add(pool.size(),?requestToHandle);
    ????????????pool.notifyAll();
    ????????}
    ????}
    ????public?void?run()?{
    ????????while(true)?{
    ????????????synchronized(pool)?{
    ????????????????while(pool.isEmpty())?{
    ????????????????????try?{
    ????????????????????????pool.wait();
    ????????????????????}
    ????????????????????catch(InterruptedException?e)?{
    ????????????????????????e.printStackTrace();?
    ????????????????????}
    ????????????????}
    ????????????????connection=?(Socket)pool.remove(0);?
    ????????????}
    ????????????handleConnection();
    ????????}
    ????}
    }
    這個助手類與?ConnectionHandler?非常相似,但它帶有處理連接池的手段。該類有兩個實例變量:
    ?????connection?是當前正在處理的?Socket?
    ?????名為?pool?的靜態?LinkedList?保存需被處理的連接?
    填充連接池
    這里我們實現PooledConnectionHandler上的processRequest()方法,它將把傳入請求添加到池中,并告訴其它正在等待的對象該池已經有一些內容:
    ????public?static?void?processRequest(Socket?requestToHandle)?{
    ????????synchronized(pool)?{
    ????????????pool.add(pool.size(),?requestToHandle);
    ????????????pool.notifyAll();
    ????????}
    ????}
    synchronized?塊是個稍微有些不同的東西。您可以同步任何對象上的一個塊,而不只是在本身的某個方法中含有該塊的對象。在我們的示例中,processRequest()?方法包含有一個?pool(請記住它是一個?LinkedList,保存等待處理的連接池)的?synchronized塊。我們這樣做的原因是確保沒有別人能跟我們同時修改連接池。
    既然我們已經保證了我們是唯一“涉水”池中的人,我們就可以把傳入的Socket添加到LinkedList的尾端。一旦我們添加了新的連接,我們就用以下代碼通知其它正在等待該池的Thread,池現在已經可用:
    ????pool.notifyAll();
    Object的所有子類都繼承這個notifyAll()方法。這個方法,連同我們下一屏將要討論的wait()方法一起,就使一個Thread能夠讓另一個Thread知道一些條件已經具備。這意味著該第二個Thread一定正在等待那些條件的滿足。
    從池中獲取連接
    這里我們實現PooledConnectionHandler上需作改動的run()方法,它將在連接池上等待,并且池中一有連接就處理它:
    ????public?void?run()?{
    ????????while(true)?{
    ????????????synchronized(pool)?{
    ????????????????while(pool.isEmpty())?{
    ????????????????????try?{
    ????????????????????????pool.wait();
    ????????????????????}
    ????????????????????catch(InterruptedException?e)?{
    ????????????????????????e.printStackTrace();?
    ????????????????????}
    ????????????????}
    ????????????????connection=?(Socket)pool.remove(0);?
    ????????????}
    ????????????handleConnection();
    ????????}
    ????}
    回想一下在前面講過的:一個Thread正在等待有人通知它連接池方面的條件已經滿足了。在我們的示例中,請記住我們有三個PooledConnectionHandler在等待使用池中的連接。每個PooledConnectionHandler都在它自已的Thread中運行,并通過調用pool.wait()產生阻塞。當我們的processRequest()在連接池上調用notifyAll()時,所有正在等待的PooledConnectionHandler都將得到“池已經可用”的通知。然后各自繼續前行調用pool.wait(),并重新檢查while(pool.isEmpty())循環條件。除了一個處理程序,其它池對所有處理程序都將是空的,因此,在調用pool.wait()時,除了一個處理程序,其它所有處理程序都將再次產生阻塞。恰巧碰上非空池的處理程序將跳出while(pool.isEmpty())循環并攫取池中的第一個連接:
    ????connection=?(Socket)pool.remove(0);
    處理程序一旦有一個連接可以使用,就調用?handleConnection()?處理它。
    在我們的示例中,池中可能永遠不會有多個連接,只是因為事情很快就被處理掉了。如果池中有一個以上連接,那么其它處理程序將不必等待新的連接被添加到池。當它們檢查pool.isEmpty()條件時,將發現其值為假,然后就從池中攫取一個連接并處理它。
    還有另一件事需注意。當run()擁有池的互斥鎖時,processRequest()如何能夠把連接放到池中呢?答案是對池上的wait()的調用釋放鎖,而wait()接著就在自己返回之前再次攫取該鎖。這就使得池對象的其它同步代碼可以獲取該鎖。
    處理連接:再一次
    這里我們實現需做改動的handleConnection()方法,該方法將攫取連接的流,使用它們,并在任務完成之后清除它們:
    ????public?void??handleConnection()?{
    ????????try?{
    ????????????PrintWriter?streamWriter?=?new?PrintWriter(connection.getOutputStream());?
    ????????????BufferedReader?streamReader?=?new?BufferedReader(new?InputStreamReader(connection.getInputStream()));?
    ????????????String?fileToRead?=?streamReader.readLine();
    ????????????BufferedReader?fileReader?=?new?BufferedReader(new?FileReader(fileToRead));
    ????????????String?line?=?null;
    ????????????while((line=fileReader.readLine())!=null)
    ????????????????streamWriter.println(line);?
    ????????????fileReader.close();
    ????????????streamWriter.close();
    ????????????streamReader.close();
    ????????}
    ????????catch(FileNotFoundException?e)?{
    ????????????System.out.println("");
    ????????}
    ????????catch(IOException?e)?{
    ????????????System.out.println(""+e);
    ????????}
    ????}
    跟在多線程服務器中不同,我們的PooledConnectionHandler有一個handleConnection()方法。這個方法的代碼跟非池式的ConnectionHandler上的run()方法的代碼完全一樣。首先,我們把OutputStream和InputStream分別包裝進(用Socket上的getOutputStream()和getInputStream())BufferedReader和PrintWriter。然后我們逐行讀目標文件,就象我們在多線程示例中做的那樣。再一次,我們獲取一些字節之后就把它們放到本地的line變量中,然后寫出到客戶機。完成讀寫操作之后,我們關閉FileReader和打開的流。
    總結一下帶有連接池的服務器
    讓我們回顧一下創建和使用“池版”服務器的步驟:
    1.????創建一個新種類的連接處理程序(我們稱之為?PooledConnectionHandler)來處理池中的連接。
    2.????修改服務器以創建和使用一組?PooledConnectionHandler。?

    Java?語言簡化了套接字在應用程序中的使用。它的基礎實際上是?java.net?包中的?Socket?和?ServerSocket?類。一旦您理解了表象背后發生的情況,就能容易地使用這些類。在現實生活中使用套接字只是這樣一件事,即通過貫徹優秀的?OO?設計原則來保護應用程序中各層間的封裝。我們為您展示了一些有幫助的類。這些類的結構對我們的應用程序隱藏了?Socket?交互作用的低級細節???使應用程序能只使用可插入的?ClientSocketFacade?和?ServerSocketFacade。在有些地方(在?Facade?內),您仍然必須管理稍顯雜亂的字節細節,但您只須做一次就可以了。更好的是,您可以在將來的項目中重用這些低級別的助手類。


    doc格式文件
    附件:Java_Socket.doc(121K)?


    Java?范型攻略篇
    yongbing 整理???更新:2006-12-08 21:37:43??版本: 1.0 ??

    在已發布的Java1.4中在核心代碼庫中增加了許多新的API(如Loging,正則表達式,NIO)等,在最新發布的JDK1.5和即將發布的JDK1.6中也新增了許多API,其中比較有重大意義的就是Generics(范型)。

    一.什么是Generics?

    Generics可以稱之為參數類型(parameterized?types),由編譯器來驗證從客戶端將一種類型傳送給某一對象的機制。如Java.util.ArrayList,

    編譯器可以用Generics來保證類型安全。
    在我們深入了解Generics之前,我們先來看一看當前的java?集合框架(Collection)。在j2SE1.4中所有集合的Root?Interface是Collection

    Collections?example?without?genericity:?Example?1

    1?protected?void?collectionsExample()?{
    2??ArrayList?list?=?new?ArrayList();
    3??list.add(new?String("test?string"));
    4??list.add(new?Integer(9));?//?purposely?placed?here?to?create?a?runtime?ClassCastException
    5??inspectCollection(list);
    6?}
    7
    8
    9?protected?void?inspectCollection(Collection?aCollection)?{
    10??Iterator?i?=?aCollection.iterator();
    11??while?(i.hasNext())?{
    12???String?element?=?(String)?i.next();
    13??}
    14?}


    以上的樣例程序包含的兩個方法,collectionExample方法建立了一個簡單的集合類型ArrayList,并在ArrayList中增加了一個String和一個Integer對象.而在inspecCollection方法中,我們迭代這個ArrayList用String進行Cast。我們看第二個方法,就出現了一個問題,Collection在內部用的是Object,而我們要取出Collection中的對象時,需要進行Cast,那么開發者必需用實際的類型進行Cast,像這種向下造型,編譯器無

    法進行檢查,如此一來我們就要冒在代碼在運行拋出ClassCastException的危險。我們看inspecCollection方法,編譯時沒有問題,但在運行時就會拋出ClassCastException異常。所以我們一定要遠離這個重大的運行時錯誤


    二.使用Generics
    從上一章節中的CassCastException這種異常,我們期望在代碼編譯時就能夠捕捉到,下面我們使用范型修改上一章的樣例程序。
    //Example?2
    1?protected?void?collectionsExample()?{
    2??ArrayList<String>?list?=?new?ArrayList<String>();
    3??list.add(new?String("test?string"));
    4??//?list.add(new?Integer(9));?this?no?longer?compiles
    5??inspectCollection(list);
    6?}
    7?
    8?
    9?protected?void?inspectCollection(Collection<String>?aCollection)?{
    10??Iterator<String>?i?=?aCollection.iterator();
    11??while(i.hasNext())?{
    12???String?element?=?i.next();
    13??}
    14?}


    從上面第2行我們在創建ArrayList時使用了新語法,在JDK1.5中所有的Collection都加入了Generics的聲明。例:
    //Example?3
    1?public?class?ArrayList<E>?extends?AbstractList<E>?{
    2??//?details?omitted...
    3??public?void?add(E?element)?{
    4???//?details?omitted
    5??}
    6??public?Iterator<E>?iterator()?{
    7???//?details?omitted
    8??}
    9?}


    這個E是一個類型變量,并沒有對它進行具體類型的定義,它只是在定義ArrayList時的類型占位符,在Example?2中的我們在定義ArrayList的實

    例時用String綁定在E上,當我們用add(E?element)方法向ArrayList中增加對象時,?那么就像下面的寫法一樣:?public?void?add(String?element);因為在ArrayList所有方法都會用String來替代E,無論是方法的參數還是返回值。這時我們在看Example?2中的第四行,編譯就會反映出編譯錯誤。
    所以在java中增加Generics主要的目的是為了增加類型安全。

    通過上面的簡單的例子我們看到使用Generics的好處有:
    1.在類型沒有變化時,Collection是類型安全的。
    2.內在的類型轉換優于在外部的人工造型。
    3.使Java?接口更加強壯,因為它增加了類型。
    4.類型的匹配錯誤在編譯階段就可以捕捉到,而不是在代碼運行時。

    受約束類型變量
    雖然許多Class被設計成Generics,但類型變量可以是受限的
    public?class?C1<T?extends?Number>?{?}
    public?class?C2<T?extends?Person?&?Comparable>?{?}?
    第一個T變量必須繼承Number,第二個T必須繼承Person和實現Comparable

    三.Generics?方法

    像Generics類一樣,方法和構造函數也可以有類型參數。方法的參數的返回值都可以有類型參數,進行Generics。
    //Example?4
    1?public?<T?extends?Comparable>?T?max(T?t1,?T?t2)?{
    2??if?(t1.compareTo(t2)?>?0)
    3???return?t1;
    4??else?return?t2;
    5?}


    這里,max方法的參數類型為單一的T類型,而T類型繼承了Comparable,max的參數和返回值都有相同的超類。下面的Example?5顯示了max方法的幾個約束。
    //Example?5 
    1?Integer?iresult?=?max(new?Integer(100),?new?Integer(200));
    2?String?sresult?=?max("AA",?"BB");
    3?Number?nresult?=?max(new?Integer(100),?"AAA");?//?does?not?compile


    在Example?5第1行參數都為Integer,所以返回值也是Integer,注意返回值沒有進行造型。
    在Example?5第2行參數都為String,所以返回值也是String,注意返回值沒有進行造型。以上都調用了同一個方法。
    在Example?5第3行產生以下編譯錯誤:
    Example.java:10:?incompatible?types
    found??:?java.lang.Object&java.io.Serializable&java.lang.Comparable<?>
    required:?java.lang.Number
    ????Number?nresult?=?max(new?Integer(100),?"AAA");

    這個錯誤發生是因為編譯器無法確定返回值類型,因為String和Integer都有相同的超類Object,注意就算我們修正了第三行,這行代碼在運行仍然會報錯,因為比較了不同的對象。

    四.向下兼容
    任何一個新的特色在新的JDK版本中出來后,我們首先關心的是如何于以前編寫的代碼兼容。也就是說我們編寫的Example?1程序不需要任何的改變就可以運行,但是編譯器會給出一個"ROW?TYPE"的警告。在JDK1.4中編寫的代碼如何在JVM1.5中完全兼容運行,我們要人工進行一個:Type?erasure處理過程

    五.通配符

    //Example?6
    List<String>?stringList?=?new?ArrayList<String>();?//1
    List<Object>?objectList?=?stringList?;//2
    objectList?.add(new?Object());?//?3
    String?s?=?stringList?.get(0);//4


    乍一看,Example?

    6是正確的。但stringList本意是存放String類型的ArrayList,而objectList中可以存入任何對象,當在第3行進行處理時,stringList也就無法保證是String類型的ArrayList,此時編譯器不允許這樣的事出現,所以第3行將無法編譯。

    //Example?7
    void?printCollection(Collection<Object>?c)?
    {?for?(Object?e?:?c)?{?
    System.out.println(e);
    }}


    Example?7的本意是打印所有Collection的對象,但是正如Example?6所說的,編譯會報錯,此時就可以用通配符“?”來修改Example?7

    //Example?8
    void?printCollection(Collection<?>?c)?
    {?for?(Object?e?:?c)?{?
    System.out.println(e);
    }}


    Example?8中所有Collection類型就可以方便的打印了

    有界通配符?<T?extends?Number>(上界)?<T?super?Number>(下界)?

    六.創建自己的范型
    以下代碼來自http://www.java2s.com/ExampleCode/Language-Basics
    1.一個參數的Generics
    //Example?9(沒有使用范型)
    class?NonGen?{??
    ??Object?ob;?//?ob?is?now?of?type?Object?
    ??//?Pass?the?constructor?a?reference?to???
    ??//?an?object?of?type?Object?
    ??NonGen(Object?o)?{??
    ????ob?=?o;??
    ??}??
    ??//?Return?type?Object.?
    ??Object?getob()?{??
    ????return?ob;??
    ??}??
    ??//?Show?type?of?ob.??
    ??void?showType()?{??
    ????System.out.println("Type?of?ob?is?"?+??
    ???????????????????????ob.getClass().getName());??
    ??}??
    }??
    //?Demonstrate?the?non-generic?class.??
    public?class?NonGenDemo?{??
    ??public?static?void?main(String?args[])?{??
    ????NonGen?iOb;???
    ????//?Create?NonGen?Object?and?store?
    ????//?an?Integer?in?it.?Autoboxing?still?occurs.?
    ????iOb?=?new?NonGen(88);??
    ????//?Show?the?type?of?data?used?by?iOb.?
    ????iOb.showType();?
    ????//?Get?the?value?of?iOb.?
    ????//?This?time,?a?cast?is?necessary.?
    ????int?v?=?(Integer)?iOb.getob();??
    ????System.out.println("value:?"?+?v);??
    ????System.out.println();??
    ????//?Create?another?NonGen?object?and??
    ????//?store?a?String?in?it.?
    ????NonGen?strOb?=?new?NonGen("Non-Generics?Test");??
    ????//?Show?the?type?of?data?used?by?strOb.?
    ????strOb.showType();?
    ????//?Get?the?value?of?strOb.?
    ????//?Again,?notice?that?a?cast?is?necessary.??
    ????String?str?=?(String)?strOb.getob();??
    ????System.out.println("value:?"?+?str);??
    ????//?This?compiles,?but?is?conceptually?wrong!?
    ????iOb?=?strOb;?
    ????v?=?(Integer)?iOb.getob();?//?runtime?error!?
    ??}??
    }
    ??

    //Example?10(使用范型)
    class?Example1<T>{
    ?private?T?t;
    ?Example1(T?o){
    ??this.t=o;
    ??}
    ?T?getOb(){
    ??return?t;
    ?}
    ?void?ShowObject(){
    ??System.out.println("對象的類型是:"+t.getClass().getName());
    ?}
    }
    public?class?GenericsExample1?{

    ?/**
    ??*?@param?args
    ??*/
    ?public?static?void?main(String[]?args)?{
    ??//?TODO?Auto-generated?method?stub
    ??Example1<Integer>?examplei=new?Example1<Integer>(100);
    ??examplei.ShowObject();
    ??System.out.println("對象是:"+examplei.getOb());
    ??Example1<String>?examples=new?Example1<String>("Bill");
    ??examples.ShowObject();
    ??System.out.println("對象是:"+examples.getOb());
    ?}

    }


    我們來看Example?9沒有使用范型,所以我們需要進行造型,而Example?10我們不需要任何的造型

    2.二個參數的Generics

    //Example?11
    class?TwoGen<T,?V>?{?
    ???T?ob1;?
    ???V?ob2;?
    ???//?Pass?the?constructor?a?reference?to??
    ???//?an?object?of?type?T.?
    ???TwoGen(T?o1,?V?o2)?{?
    ?????ob1?=?o1;?
    ?????ob2?=?o2;?
    ???}?
    ???//?Show?types?of?T?and?V.?
    ???void?showTypes()?{?
    ?????System.out.println("Type?of?T?is?"?+?
    ????????????????????????ob1.getClass().getName());?
    ?????System.out.println("Type?of?V?is?"?+?
    ????????????????????????ob2.getClass().getName());?
    ???}?
    ???T?getob1()?{?
    ?????return?ob1;?
    ???}?
    ???V?getob2()?{?
    ?????return?ob2;?
    ???}?
    ?}?

    public?class?GenericsExampleByTwoParam?{

    ?/**
    ??*?@param?args
    ??*/
    ?public?static?void?main(String[]?args)?{
    ??//?TODO?Auto-generated?method?stub
    ??TwoGen<Integer,?String>?tgObj?=?
    ???????new?TwoGen<Integer,?String>(88,?"Generics");?
    ?????//?Show?the?types.?
    ?????tgObj.showTypes();?
    ?????//?Obtain?and?show?values.?
    ?????int?v?=?tgObj.getob1();?
    ?????System.out.println("value:?"?+?v);?
    ?????String?str?=?tgObj.getob2();?
    ?????System.out.println("value:?"?+?str);?
    ???}?

    ?}


    3.Generics的Hierarchy

    //Example?12
    class?Stats<T?extends?Number>?{??
    ???T[]?nums;?//?array?of?Number?or?subclass?
    ???//?Pass?the?constructor?a?reference?to???
    ???//?an?array?of?type?Number?or?subclass.?
    ???Stats(T[]?o)?{??
    ?????nums?=?o;??
    ???}??
    ???//?Return?type?double?in?all?cases.?
    ???double?average()?{??
    ?????double?sum?=?0.0;?
    ?????for(int?i=0;?i?<?nums.length;?i++)??
    ???????sum?+=?nums[i].doubleValue();?
    ?????return?sum?/?nums.length;?
    ???}??
    ?}??
    public?class?GenericsExampleByHierarchy?{
    ?

    ?/**
    ??*?@param?args
    ??*/
    ?public?static?void?main(String[]?args)?{
    ??//?TODO?Auto-generated?method?stub

    ???Integer?inums[]?=?{?1,?2,?3,?4,?5?};?
    ?????Stats<Integer>?iob?=?new?Stats<Integer>(inums);???
    ?????double?v?=?iob.average();?
    ?????System.out.println("iob?average?is?"?+?v);?
    ?????Double?dnums[]?=?{?1.1,?2.2,?3.3,?4.4,?5.5?};?
    ?????Stats<Double>?dob?=?new?Stats<Double>(dnums);???
    ?????double?w?=?dob.average();?
    ?????System.out.println("dob?average?is?"?+?w);?
    ?????//?This?won't?compile?because?String?is?not?a?
    ?????//?subclass?of?Number.?
    //?????String?strs[]?=?{?"1",?"2",?"3",?"4",?"5"?};?
    //?????Stats<String>?strob?=?new?Stats<String>(strs);???
    //?????double?x?=?strob.average();?
    //?????System.out.println("strob?average?is?"?+?v);?
    ???}??
    ?}
    ??

    4.使用通配符
    //Example?14
    class?StatsWildCard<T?extends?Number>?{
    ?T[]?nums;?//?array?of?Number?or?subclass
    ?//?Pass?the?constructor?a?reference?to
    ?//?an?array?of?type?Number?or?subclass.
    ?StatsWildCard(T[]?o)?{
    ??nums?=?o;
    ?}
    ?//?Return?type?double?in?all?cases.
    ?double?average()?{
    ??double?sum?=?0.0;
    ??for?(int?i?=?0;?i?<?nums.length;?i++)
    ???sum?+=?nums[i].doubleValue();
    ??return?sum?/?nums.length;
    ?}
    ?//?Determine?if?two?averages?are?the?same.
    ?//?Notice?the?use?of?the?wildcard.
    ?boolean?sameAvg(StatsWildCard<?>?ob)?{
    ??if?(average()?==?ob.average())
    ???return?true;
    ??return?false;
    ?}
    }

    public?class?GenericsExampleByWildcard?{

    ?/**
    ??*?@param?args
    ??*/
    ?public?static?void?main(String[]?args)?{
    ??//?TODO?Auto-generated?method?stub
    ??Integer?inums[]?=?{?1,?2,?3,?4,?5?};
    ??StatsWildCard<Integer>?iob?=?new?StatsWildCard<Integer>(inums);
    ??double?v?=?iob.average();
    ??System.out.println("iob?average?is?"?+?v);
    ??Double?dnums[]?=?{?1.1,?2.2,?3.3,?4.4,?5.5?};
    ??StatsWildCard<Double>?dob?=?new?StatsWildCard<Double>(dnums);
    ??double?w?=?dob.average();
    ??System.out.println("dob?average?is?"?+?w);
    ??Float?fnums[]?=?{?1.0F,?2.0F,?3.0F,?4.0F,?5.0F?};
    ??StatsWildCard<Float>?fob?=?new?StatsWildCard<Float>(fnums);
    ??double?x?=?fob.average();
    ??System.out.println("fob?average?is?"?+?x);
    ??//?See?which?arrays?have?same?average.
    ??System.out.print("Averages?of?iob?and?dob?");
    ??if?(iob.sameAvg(dob))
    ???System.out.println("are?the?same.");
    ??else
    ???System.out.println("differ.");
    ??System.out.print("Averages?of?iob?and?fob?");
    ??if?(iob.sameAvg(fob))
    ???System.out.println("are?the?same.");
    ??else
    ???System.out.println("differ.");

    ?}

    }


    5.使用邊界通配符
    //Example?15
    class?TwoD?{?
    ??int?x,?y;?
    ??TwoD(int?a,?int?b)?{?
    ????x?=?a;?
    ????y?=?b;?
    ??}?
    }?
    //?Three-dimensional?coordinates.?
    class?ThreeD?extends?TwoD?{?
    ??int?z;?
    ??ThreeD(int?a,?int?b,?int?c)?{?
    ????super(a,?b);?
    ????z?=?c;?
    ??}?
    }?
    //?Four-dimensional?coordinates.?
    class?FourD?extends?ThreeD?{?
    ??int?t;?
    ??FourD(int?a,?int?b,?int?c,?int?d)?{?
    ????super(a,?b,?c);?
    ????t?=?d;??
    ??}?
    }?
    //?This?class?holds?an?array?of?coordinate?objects.?
    class?Coords<T?extends?TwoD>?{?
    ??T[]?coords;?
    ??Coords(T[]?o)?{?coords?=?o;?}?
    }?
    //?Demonstrate?a?bounded?wildcard.?
    public?class?BoundedWildcard?{?
    ??static?void?showXY(Coords<?>?c)?{?
    ????System.out.println("X?Y?Coordinates:");?
    ????for(int?i=0;?i?<?c.coords.length;?i++)?
    ??????System.out.println(c.coords[i].x?+?"?"?+?
    ?????????????????????????c.coords[i].y);?
    ????System.out.println();?
    ??}?
    ??static?void?showXYZ(Coords<??extends?ThreeD>?c)?{?
    ????System.out.println("X?Y?Z?Coordinates:");?
    ????for(int?i=0;?i?<?c.coords.length;?i++)?
    ??????System.out.println(c.coords[i].x?+?"?"?+?
    ?????????????????????????c.coords[i].y?+?"?"?+?
    ?????????????????????????c.coords[i].z);?
    ????System.out.println();?
    ??}?
    ??static?void?showAll(Coords<??extends?FourD>?c)?{?
    ????System.out.println("X?Y?Z?T?Coordinates:");?
    ????for(int?i=0;?i?<?c.coords.length;?i++)?
    ??????System.out.println(c.coords[i].x?+?"?"?+?
    ?????????????????????????c.coords[i].y?+?"?"?+?
    ?????????????????????????c.coords[i].z?+?"?"?+?
    ?????????????????????????c.coords[i].t);?
    ????System.out.println();?
    ??}?
    ??public?static?void?main(String?args[])?{?
    ????TwoD?td[]?=?{?
    ??????new?TwoD(0,?0),?
    ??????new?TwoD(7,?9),?
    ??????new?TwoD(18,?4),?
    ??????new?TwoD(-1,?-23)?
    ????};?
    ????Coords<TwoD>?tdlocs?=?new?Coords<TwoD>(td);?????
    ????System.out.println("Contents?of?tdlocs.");?
    ????showXY(tdlocs);?//?OK,?is?a?TwoD?
    //??showXYZ(tdlocs);?//?Error,?not?a?ThreeD?
    //??showAll(tdlocs);?//?Erorr,?not?a?FourD?
    ????//?Now,?create?some?FourD?objects.?
    ????FourD?fd[]?=?{?
    ??????new?FourD(1,?2,?3,?4),?
    ??????new?FourD(6,?8,?14,?8),?
    ??????new?FourD(22,?9,?4,?9),?
    ??????new?FourD(3,?-2,?-23,?17)?
    ????};?
    ????Coords<FourD>?fdlocs?=?new?Coords<FourD>(fd);?????
    ????System.out.println("Contents?of?fdlocs.");?
    ????//?These?are?all?OK.?
    ????showXY(fdlocs);??
    ????showXYZ(fdlocs);?
    ????showAll(fdlocs);?
    ??}?
    }?



    6.ArrayList的Generics
    //Example?16
    public?class?ArrayListGenericDemo?{
    ??public?static?void?main(String[]?args)?{
    ????ArrayList<String>?data?=?new?ArrayList<String>();
    ????data.add("hello");
    ????data.add("goodbye");

    ????//?data.add(new?Date());?This?won't?compile!

    ????Iterator<String>?it?=?data.iterator();
    ????while?(it.hasNext())?{
    ??????String?s?=?it.next();
    ??????System.out.println(s);
    ????}
    ??}
    }?


    7.HashMap的Generics
    //Example?17
    public?class?HashDemoGeneric?{
    ??public?static?void?main(String[]?args)?{
    ????HashMap<Integer,String>?map?=?new?HashMap<Integer,String>();

    ????map.put(1,?"Ian");
    ????map.put(42,?"Scott");
    ????map.put(123,?"Somebody?else");

    ????String?name?=?map.get(42);
    ????System.out.println(name);
    ??}
    }?


    8.接口的Generics
    //Example?18
    interface?MinMax<T?extends?Comparable<T>>?{?
    ??T?min();?
    ??T?max();?
    }?
    //?Now,?implement?MinMax?
    class?MyClass<T?extends?Comparable<T>>?implements?MinMax<T>?{?
    ??T[]?vals;?
    ??MyClass(T[]?o)?{?vals?=?o;?}?
    ??//?Return?the?minimum?value?in?vals.?
    ??public?T?min()?{?
    ????T?v?=?vals[0];?
    ????for(int?i=1;?i?<?vals.length;?i++)?
    ??????if(vals[i].compareTo(v)?<?0)?v?=?vals[i];?
    ????return?v;?
    ??}?
    ??//?Return?the?maximum?value?in?vals.?
    ??public?T?max()?{?
    ????T?v?=?vals[0];?
    ????for(int?i=1;?i?<?vals.length;?i++)?
    ??????if(vals[i].compareTo(v)?>?0)?v?=?vals[i];?
    ????return?v;?
    ??}?
    }?
    public?class?GenIFDemo?{?
    ??public?static?void?main(String?args[])?{?
    ????Integer?inums[]?=?{3,?6,?2,?8,?6?};?
    ????Character?chs[]?=?{'b',?'r',?'p',?'w'?};?
    ????MyClass<Integer>?iob?=?new?MyClass<Integer>(inums);?
    ????MyClass<Character>?cob?=?new?MyClass<Character>(chs);?
    ????System.out.println("Max?value?in?inums:?"?+?iob.max());?
    ????System.out.println("Min?value?in?inums:?"?+?iob.min());?
    ????System.out.println("Max?value?in?chs:?"?+?cob.max());?
    ????System.out.println("Min?value?in?chs:?"?+?cob.min());?
    ??}?
    }


    9.Exception的Generics
    //Example?20
    interface?Executor<E?extends?Exception>?{
    ????void?execute()?throws?E;
    }

    public?class?GenericExceptionTest?{
    ????public?static?void?main(String?args[])?{
    ????????try?{
    ????????????Executor<IOException>?e?=
    ????????????????new?Executor<IOException>()?{
    ????????????????public?void?execute()?throws?IOException
    ????????????????{
    ????????????????????//?code?here?that?may?throw?an
    ????????????????????//?IOException?or?a?subtype?of
    ????????????????????//?IOException
    ????????????????}
    ????????????};

    ????????????e.execute();
    ????????}?catch(IOException?ioe)?{
    ????????????System.out.println("IOException:?"?+?ioe);
    ????????????ioe.printStackTrace();
    ????????}
    ????}
    }??
    表現層框架Struts/Tapestry/JSF比較
    xuyy_cn 轉貼???更新:2006-02-27 18:55:13??版本: 1.0 ??

    ????Struts/Tapestry/JSF是目前J2EE表現層新老組合的框架技術。從誕生時間上看,Struts應該比較早,使用得非常廣泛,Tapestry??3.0逐漸引起廣泛的重視,正當Tapestry即將大顯身手時期,SUN推出JSF標準技術,雖然JSF一開始推出尚不成熟,留出了一段空白期,但是隨著JSF1.1標準推出,JSF開始正面出擊,粉面隆重登場了。??
      其實,JSF和Tapestry也并不是那種頭碰頭的相同競爭性技術,兩者還是各有側重點的,不過比較細微,但是這種細微點在實現一個大工程時可能帶來不同的感受和變化。

      首先,我們從一個高度來抽象一下表現層框架應有的技術架構,下圖可以說所有表現層框架技術都必須實現的功能架構圖:



      當然,我們不必廢話羅嗦MVC模式,MVC模式是基準模式,現在框架技術已經不必再拼是否是MVC模式了。??在上圖MVC模式基礎上,一個表現層框架無外乎要實現圖中的三個功能:

      1.在當前頁面能夠顯示一個組件對象的內容;而不是象純JSP那樣,需要在Jsp頁面寫入“調用對象方法”的Java代碼。

      2.當用戶按下頁面的提交按扭或鏈接后,事件發生,這時應該觸發服務器端并將當前頁面的參數提交給服務器。這種機制表現在Form表單提交和有參數的鏈接<a??href=""></a>

      3.從一個頁面視圖直接跳轉到另外一個頁面視圖,單純的導航作用。

      我們通過下表來比較這??三種框架在實現上圖各個功能時技術細節,從而得出他們的異同點和偏重點。

    ????Struts??Tapestry3.0??JSF??
    在View顯示的組件要求??組件必須繼承ActionForm
    ??分顯式調用和隱式調用
    組件必須繼承BaseComponent??普通POJO
    無需繼承
    Managed??Bean??
    組件在View顯示粒度??View頁面只能顯示與表單對應的ActionForm,配置中Action??ActionForm??頁面一般只能1:1:1關系。??可將組件嵌入頁面任何一行,對使用組件數量無限制。??同Tapestry??
    頁面分區tiles??使用Tiles標簽庫實現,需要另外tiles-def.xml配置文件??組件有自己的視圖頁面,通過調用組件即直接實現多個頁面組合。強大自然的頁面組合是其特點。??通過組件+標簽庫實現Subview,但如需重用Layout,還要結合Tiles.??
    頁面跳轉??使用標簽庫html:link中寫明目標URL,URL名稱需要對照配置文件的path命名,與組件Action耦合。??URL名稱是目標的組件名稱,不涉及URL和路徑等操作,方便穩固。??類似Struts,也需要在配置文件中查找,與組件分離。??
    參數傳遞??使用html:link時傳遞參數超過一個以上處理麻煩。??直接調用組件,直接賦予參數,沒有參數個數限制??參數分離傳遞給組件??
    事件觸發??通過表單提交submit激活,不能細化到表單里字段。??能夠給于表單每個字段貼一個事件,事件組件必須實現PageListener接口??同Tapestry,事件組件必須實習ActionListener??接口??
    ??

      Struts組件編程模型

      Struts實現組件編程時有一些復雜:經常為一個頁面中需要引入多個組件而頭疼,因為Struts中無法直接引入多個組件,必須繞一些圈子:

      一般分兩種情況:如果同一個Action就可以對付這些組件,那么在這種情況下有兩個辦法:

      1.將這多個組件裝入一個ActionForm中,如使用MapForm等機制;

      2.手工將多個組件裝入request/session等scope中,然后根據其名稱在jsp中獲得。

      這兩個方法都有缺點:??第一種辦法經常一個ActionForm弄得面目全非,變成一個大雜燴,違反了OO分派封裝的原則;第2種辦法其實又回到jsp編程;

      第二種情況,如果這些組件必須有預先由不同的Action來處理,每個組件必須經過Action??-->ActionForm流程,在這種情況下有兩種辦法:

      1.使用Tiles,??不同流程輸出到同一個頁面的不同區域。是一種并行處理方式。

      2.??對多個流程首尾相連,第一Action??forward結果是第二個Action,最后輸出一個Jsp,在這個jsp中就可以使用前面多個流程的多個ActionForm了,這屬于串行方式。

      Struts組件模型缺點

      Struts組件編程必須限定在Action/ActionForm/JSP這三個框框中做文章,難度相對比較大,而Tapestry/JSF則沒有太多這些技術框框限制,兩者在組件編程方面更讓編程者自由一些,方便一些,這也是組件型框架的優勢吧。

      Struts標簽庫

      在Struts中,經常需要使用標簽庫來顯示組件ActionForm中內容,這就涉及到一個結合的問題,標簽庫是別人寫的,參考Struts的標簽庫用法,而組件是自己的,難度和麻煩就體現在這個結合點上。

      JSF基本思路和Struts差不多,只不過換了不同標簽庫,也需要標簽庫+組件的結合思考,不過因為組件這里是通用組件,沒有什么限制,所以這樣比Struts要輕松一些。

      Tapestry使用了組件庫概念替代了標簽庫,沒有標簽庫概念,這樣就沒有標簽庫和自己的組件需要結合的問題,都是組件的使用,組件中分Tapestry標準組件和自己定義的組件,這也是接觸了Jsp體系的人學習Tapestry面臨的一個思路轉換。

      具體以頁面跳轉為例子,頁面跳轉是靠鏈接<a??href="目標"></a>??實現,鏈接是頁面經常使用的元素。

      Struts提供的html:link在頻繁使用就特別不方便,尤其在傳遞多個參數時:其中html:link的page值,是跳轉對方頁面或Action的path,這個path一般需要到struts-config.xml查找Action的相應path,一旦配置文件path值修改,涉及到這個所有相關頁面都要修改。

      JSF將鏈接概念劃分兩個方面:導航性質和事件激活,在導航方面還是需要到配置faces-config查詢Navigation的from-outcome的值。

      由于Tapestry沒有標簽庫概念,只有組件或頁面兩個概念,因此,鏈接跳轉目標要么是組件,要么是頁面,簡潔簡單,它沒有多余的path概念,就是組件名,也就是對象名稱,組件名稱和path名稱合二為一。

      總結

      JSF在很大程度上類似Struts,而不是類似Tapestry,可以說是一種Struts??2.0,都是采取標簽庫+組件的形式,只是JSF的組件概念沒有象Struts那樣必須繼承ActionForm的限制;JSF在事件粒度上要細膩,不象Struts那樣,一個表單一個事件,JSF可以細化到表單中的每個字段上。

      JSF只有在組件和事件機制這個概念上類似Tapestry,但是不似Tapestry那樣是一個完全組件的框架,所以,如果你做一個對頁面要求靈活度相當高的系統,選用Tapestry是第一考慮。

      Struts/JSF則適合在一般的數據頁面錄入的系統中,對于Struts和JSF的選用,我目前個人觀點是:如果你是一個新的系統,可以直接從JSF開始;如果你已經使用Struts,不必轉換,如果需要切換,可以將JSF和Tapestry一起考慮。

      另外,JSF/Tapestry不只是支持Html,也支持多種客戶端語言如WML或XUI等。

      這三者之間關系:如果說Struts是左派;那Tapestry則是右派;而JSF則是中間派,中庸主義是SUN聯盟的一貫策略。

      當然,你也可以發表你在實踐中這三者任何一個的使用感受,以使得后來者有一個比較。? ?
    當前流行的J2EE?WEB應用架構分析
    xuyy_cn 原創???更新:2006-09-14 13:04:12??版本: 1.0 ??

    1.?架構概述?


    J2EE體系包括java?server?pages(JSP)?,java?SERVLET,?enterprise?bean,WEB?service等技術。這些技術的出現給電子商務時代的WEB應用程序的開發提供了一個非常有競爭力的選擇。怎樣把這些技術組合起來形成一個適應項目需要的穩定架構是項目開發過程中一個非常重要的步驟。完成這個步驟可以形成一個主要里程碑基線。形成這個基線有很多好處:?


    各種因數初步確定?

    為了形成架構基線,架構設計師要對平臺(體系)中的技術進行篩選,各種利弊的權衡。往往架構設計師在這個過程中要閱讀大量的技術資料,聽取項目組成員的建議,考慮領域專家的需求,考慮贊助商成本(包括開發成本和運行維護成本)限額。一旦架構設計經過評審,這些因數初步地就有了在整個項目過程中的對項目起多大作用的定位。?

    定向技術培訓?

    一旦架構師設計的架構得到了批準形成了基線,項目開發和運行所采用的技術基本確定下來了。眾多的項目經理都會對預備項目組成員的技術功底感到擔心;他們需要培訓部門提供培訓,但就架構師面對的技術海洋,項目經理根本就提不出明確的技術培訓需求。怎不能夠對體系中所有技術都進行培訓吧!有了架構里程碑基線,項目經理能確定這個項目開發會采用什么技術,這是提出培訓需求應該是最精確的。不過在實際項目開發中,技術培訓可以在基線確定之前與架構設計并發進行。?

    角色分工?

    有了一個好的架構藍圖,我們就能準確劃分工作。如網頁設計,JSP?標簽處理類設計,SERVLET?設計,session?bean設計,還有各種實現。這些任務在架構藍圖上都可以清晰地標出位置,使得項目組成員能很好地定位自己的任務。一個好的架構藍圖同時也能規范化任務,能很好地把任務劃分為幾類,在同一類中的任務的工作量和性質相同或相似。這樣工作量估計起來有一個非常好的基礎。?

    運行維護?

    前面說過各個任務在架構圖上都有比較好的定位。任何人能借助它很快地熟悉整個項目的運行情況,錯誤出現時能比較快速地定位錯誤點。另外,有了清晰的架構圖,項目版本管理也有很好的版本樹軀干。?

    擴展性?

    架構猶如一顆參天大樹的軀干,只要軀干根系牢,樹干粗,長一些旁支,加一些樹葉輕而易舉無疑。同樣,有一個穩定的經得起考驗的架構,增加一兩個業務組件是非常快速和容易的。?

    大家都知道這些好處,一心想形成一個這樣的J2EE應用程序架構(就像在windows平臺中的MFC)。在這個路程中經歷了兩個大的階段:?


    1.1.?模型1?


    模型1其實不是一個什么穩定架構,甚至談不上形成了架構。模型1的基礎是JSP文件。它從HTTP的請求中提取參數,調用相應的業務邏輯,處理HTTP會話,最后生成HTTP文檔。一系列這樣的JSP文件形成一個完整的模型1應用,當然可能會有其他輔助類或文件。早期的ASP?和?PHP?技術就屬于這個情況。?


    總的看來,這個模型的好處是簡單,但是它把業務邏輯和表現混在一塊,對大應用來說,這個缺點是令人容忍不了的。?


    1.2.?模型2?


    在經過一番實踐,并廣泛借鑒和總結經驗教訓之后,J2EE應用程序終于迎來了MVC(模型-視圖-控制)模式。MVC模式并不是J2EE行業人士標新立異的,所以前面我談到廣發借鑒。MVC的核心就是做到三層甚至多層的松散耦合。這對基于組件的,所覆蓋的技術不斷膨脹的J2EE體系來說真是福音和救星。?


    它在瀏覽器(本文對客戶代理都稱瀏覽器)和JSP或SERVLET之間插入一個控制組件。這個控制組件集中了處理瀏覽器發過來的HTTP請求的分發邏輯,也就是說,它會根據HTTP請求的URL,輸入參數,和目前應用的內部狀態,把請求分發給相應的WEB?層的JSP?或SERVLET。另外它也負責選擇下一個視圖(在J2EE中,JSP,SERVLET會生成回給瀏覽器的html從而形成視圖)。集中的控制組件也有利于安全驗證,日志紀錄,有時也封裝請求數據給下面的WEB?tier層。這一套邏輯的實現形成了一個像MFC的應用框架,位置如圖:?


    1.3.?多層應用?


    下圖為J2EE體系中典型的多層應用模型。?


    Client?tier客戶層?

    一般為瀏覽器或其他應用。客戶層普遍地支持HTTP協議,也稱客戶代理。?

    WEB?tier?WEB應用層?

    在J2EE中,這一層由WEB?容器運行,它包括JSP,?SERVLET等WEB部件。?

    EJB?tier?企業組件層?

    企業組件層由EJB容器運行,支持EJB,?JMS,?JTA?等服務和技術。?

    EIS?tier?企業信息系統層?

    企業信息系統包含企業內傳統信息系統如財務,CRM等,特點是有數據庫系統的支持。?


    應用框架目前主要集中在WEB層,旨在規范這一層軟件的開發。其實企業組件層也可以實現這個模型,但目前主要以設計模式的形式存在。而且有些框架可以擴充,有了企業組件層組件的參與,框架會顯得更緊湊,更自然,效率會更高。?


    2.?候選方案?


    目前,實現模型2的框架也在不斷的涌現,下面列出比較有名的框架。?


    2.1.?Apache?Struts?


    Struts是一個免費的開源的WEB層的應用框架,apache軟件基金致力于struts的開發。Struts具是高可配置的性,和有一個不斷增長的特性列表。一個前端控制組件,一系列動作類,動作映射,處理XML的實用工具類,服務器端java?bean?的自動填充,支持驗證的WEB?表單,國際化支持,生成HTML,實現表現邏輯和模版組成了struts的靈魂。?


    2.1.1.?Struts和MVC?


    模型2的目的和MVC的目的是一樣的,所以模型2基本可以和MVC等同起來。下圖體現了Struts的運作機理:?


    2.1.1.1.?控制?


    如圖所示,它的主要部件是一個通用的控制組件。這個控制組件提供了處理所有發送到Struts?的HTTP請求的入口點。它截取和分發這些請求到相應的動作類(這些動作類都是Action類的子類)。另外控制組件也負責用相應的請求參數填充?From?bean,并傳給動作類。動作類實現核心商業邏輯,它可以通過訪問java?bean?或調用EJB。最后動作類把控制權傳給后續的JSP?文件,后者生成視圖。所有這些控制邏輯利用一個叫struts-config.xml文件來配置。?


    2.1.1.2.?模型?


    模型以一個或幾個java?bean的形式存在。這些bean分為三種:?


    Form?beans(表單Beans)?

    它保存了HTTP?post請求傳來的數據,在Struts里,所有的Form?beans都是?ActionFrom?類的子類。?

    業務邏輯beans?

    專門用來處理業務邏輯。?

    系統狀態beans?

    它保存了跨越多個HTTP?請求的單個客戶的會話信息,還有系統狀態。?

    2.1.1.3.?視圖?


    控制組件續傳HTTP請求給實現了視圖的JSP文件。JSP能訪問beans?并生成結果文檔反饋到客戶。Struts提供JSP?標簽庫:?Html,Bean,Logic,Template等來達到這個目的,并有利于分開表現邏輯和程序邏輯。?


    2.1.2.?Struts的細節分析?


    2.1.2.1.?視圖-控制-模型?


    用戶發出一個*.do的HTTP請求,控制組件接收到這個請求后,查找針對這個請求的動作映射,再檢查是否曾創建過相應的動作對象(action實例),如果沒有則調用actionmapping生成一個動作對象,控制組件會保存這個動作對象供以后使用。接著調用actionmapping的方法得到actionForm對象。之后把actionForm作為參數傳給動作對象的perform方法,這個方法結束之后會返回給控制組件一個?actionforward對象。控制組件接著從這個對象中獲取下一個視圖的路徑和重定向屬性。如果為重定向則調用HTTPSERVLETREPONSE的方法來顯示下一個視圖,否則相繼調用requestdispatcher,?SERVLETcontext的方法續傳HTTP請求到下一個視圖。?


    當動作對象運行perform方法時,可能出現錯誤信息。動作對象可以保存這些錯誤信息到一個error對象中,接著調用自身的saveerrors方法把這個錯誤保存到request對象的屬性中。接著動作對象調用actionmapping對象的getInput方法從動作映射中獲取input參數,也就是產生輸入的視圖,并以這個input為參數生成一個actionforward對象返回。這個input參數的JSP中一般有HTTP:errors定制標簽讀取這些錯誤信息并顯示在頁面上。?


    2.1.2.2.?模型到視圖?


    模型到視圖指視圖在顯示之前裝載系統數據到視圖的過程。系統數據一般為模型內java?bean的信息。示意圖表現了由控制組件forward過來的有html:form定制標簽的JSP?的處理邏輯。?


    html:form定制標簽處理對象從application?scope(通過查詢SERVLETCONTEXT對象的屬性來實現)獲取先前由控制組件actionSERVLET放在那里的動作映射等對象,由html:form?的action屬性查得actionform名字、類型和范圍等信息,在相應的范圍內查找actionform,如果有則利用它的信息填充html?form表單[實際填充動作在嵌套的html:text等定制標簽的處理對象中]。否則在相應范圍內創建一個actionform?對象。?


    2.1.3.?優缺點?


    優點:?


    一些開發商開始采用并推廣這個框架?

    作為開源項目,有很多先進的實現思想?

    對大型的應用支持的較好?

    有集中的網頁導航定義?

    缺點:?


    不是業屆標準?

    對開發工具的支持不夠?

    復雜的taglib,需要比較長的時間來掌握?

    html?form?和?actionform的搭配比較封閉,但這也是它的精華所在。?

    修改建議?

    把actionform屬性的設置器和訪問器修改成讀取或生成xml文檔的方法,然后?html?form和actionform之間用xml文檔進行數據交換,使之松散耦合,適應數據結構易變化的應用。?


    2.2.?JATO?


    JATO應用程序框架是iPlanet?應用程序框架的舊名。它是一個成熟的、強大的,基于J2EE標準的面向于開發WEB應用程序的應用框架。結合了顯示字段、應用程序事件、組件層次和以頁面為中心的開發方法、以及MVC和服務到工作者service-to-workers的設計模式等概念。JATO可適用于中、大、超大規模的WEB應用。但是它也不是一個企業層的應用框架,也就是說它不會直接提供創建EJB,?WEB?services等企業層組件的方法,但用它可以構造出訪問企業層組件的客戶應用。?


    這個框架功能主要有三部分組成:?


    iPlanet應用框架核心;?

    iPlanet應用框架組件;?

    iPlanet應用框架擴展。?

    應用框架核心定義了基本接口、對象協議、簡單組件,以及iPlanet應用框架程序的最小核心。包括視圖簡單組件、模型簡單組件、請求分發組件和可重用命令對象。iPlanet應用框架組件利用框架核心定義的基本接口、協議和組件向開發者提供高層的重用組件,這些組件既有與特定視覺效果無關的水平組件,同時也有適應特定實用環境、提高可用性而特意提供的垂直型組件。框架擴展實現了用框架相容的方法訪問非J2EE環境的方法。通常情況下,擴展被框架應用程序用來無縫訪問J2EE容器特定功能。JATO平臺棧圖很清楚地表達了這個情況。?


    JATO最大的威力在:對于快速開發用戶,你能利用框架組件和擴展提高生產率,對于要求更大靈活性的用戶,你能實現框架核心提供的接口來保持應用的框架兼容性。?

    此圖表示實現一個JATO應用程序,可以簡單地實現控制組件module1Servlet,視圖組件ListCustomersViewBean和模型組件CustomersModuleImpl,以及一個給客戶代理顯示界面的ListCustomers.jsp文件。并清楚地表明這些組件與JATO框架組件的繼承關系。?


    JATO標簽庫提供了VIEW對象與JSP文件的接口。庫中標簽處理程序負責實現VIEW對象和JSP產生地客戶端文檔的信息同步和交換。這個圖清楚地表達了這種對應關系?


    2.2.1.?MVC分析?


    前端控制組件接收用戶發來的任何請求,這個可在WEB.xml中指定請求分發組件負責視圖管理和導航,和前端控制組件封裝在ApplicationSERVLETBase一起實現。應用程序開發者需要為每一個子系統(人力資源,財務,CRM等)實現一個此類的繼承。?


    請求分發組件分發請求給工作者,工作者實現了command接口。應用開發者可以實現這個接口。JATO提供了一個缺省實現:DefaultRequestHandingCommand,這個實現會把請求傳給視圖組件的特定事件。?


    組合視圖是指視圖組件在顯示給用戶時的層次關系:根視圖是一個ViewBean類的對象字段是一個DisplayField類的對象,容器視圖是一個ContainerView類的對象。視圖組件類的層次關系如下圖:?


    2.2.2.?優缺點分析?


    優點:?


    這種框架的適應范圍大,即提供了底層接口,也有立即可用的組件?

    具有與客戶端RAD開發工具相似的開發概念如頁為中心(等同于VB的FORM),事件處理等.?

    對大型的應用支持較好?

    缺點:?


    不是業屆標準?

    目前還沒有開發工具的支持(然JATO已經為工具支持做好了準備)?

    沒有定義網頁導航,開發者在視圖中自己指定具體的導航URL?

    修改建議?

    把眾多的VIEW/MODEL對應修改成xml文檔傳遞數據,加上集中的網頁導航定義?


    2.3.?JSF(JavaServer?Faces)?


    JSF是一個包括SUN在內的專家組正在定義的開發WEB應用用戶界面的框架,JSF?技術包括:?


    一組API,它實現UI了組件,管理組件的狀態,處理事件,輸入校驗,定義頁面導航,支持國際化和訪問;?

    一個JSP定制標簽庫實現與JSP的接口。?

    JSF非常簡單,是一個定義良好的編程模型。利用這個技術,開發者通過在頁面內組合可重用的UI組件,在把這些組件和應用的數據源相連,路由客戶產生的事件到服務器端的事件處理器進行編程。JSP處理了所有幕后的復雜工作,使得開發者把關注重點放在應用代碼上。?


    2.3.1.?STRUTS、JATO和JSF比較?


    它們之間有部分重疊,但重點不一樣。?


    STRUTS和JATO都提供了一個MVC式的應用模型,而JSF只在用戶界面上提供編程接口。這意味著前兩者涉及的范圍比后者廣。JSF可以成為前兩者在UI開發的部分。?

    JSF的規范的發布版將在?2002年底發布,實現可能要比這個時間晚些。另外將會有工具支持這個框架的應用開發。?

    2.4.?WAF?


    WAF是WEB?APPLICATION?FRAMWORK的簡稱,是SUN藍皮書例子程序中提出的應用框架。它實現了?MVC和其他良好的設計模式。?


    2.4.1.?細節分析?



    2.4.2.?視圖-控制-模型?


    如圖所示,開發人員編寫的兩個xml配置文件定義了WAF的運作參數。Screendefinition.xml定義了一系列的屏幕(screen)。Mapping.xml則定義了某個動作之后應該顯示的屏幕,但沒有指定屏幕到哪里拿數據。?


    用戶發出一個HTTP請求(*.screen),由TemplateSERVLET屏幕前端控制組件接收,它提取請求信息,設置request對象CurrentScreen屬性,再把請求發到模版JSP。模版JSP收到請求后,JSP中的Template標簽察看這個當前屏幕,并從屏幕定義文件(Screendefinition.xml)中獲取這個屏幕的具體參數,再生成html返回給客戶。?


    假設返回給客戶的html中包括了html表單,用戶在輸入一定數據之后提交,發出一個HTTP請求(*.do)。這個請求被MainSERVLET接收,它提取請求信息,察看動作映射文件(mapping.xml),設置處理這個請求的動作對象(HTTPAction對象),交給requestprosessor對象處理。Requestprosessor對象調用動作對象完成任務,如果需要進一步處理,requestprosessor對象會調用WEBclientcontroler對象的事件處理機制。MainSERVLET在處理完請求之后,從屏幕流管理對象那里得到下一個屏幕,并把請求傳給這個屏幕的JSP文件。?


    值得一提的是WEBclientcontroler事件處理機制最終把HTTP請求的數據傳到了EJBAction對象那里處理。這樣HTTPAction對象和EJBAction對象形成了兩級處理機制,前一級與request對象緊密相關,把數據封裝起來形成一個Event對象,再傳給了EJBAction對象,后者與Request對象無關。這個方式可以形成一個session級別的數據處理機制。下圖顯示了這個方法。HTTPAction1對象處理一個請求,并把數據放到一個狀態SessionBean內,HTTPAction2也如此,當HTTPAction3接收到HTTP請求之后,把控制傳給EJBAction,?后者獲取狀態SessionBean數據,處理請求,成功后清控狀態SessionBean的內容。這個機制非常適應多個輸入頁面才能滿足一個業務的輸入數據的情況(比如購物車)。?


    2.4.3.?優缺點分析?


    優點?


    屏幕導航定義明確?

    為框架的擴展提供了一個空間?

    缺點?


    源碼比較亂,穩定性和可靠性沒人驗證。?

    只是一個框架軀干,沒有正式的model層,視圖的概念不強?

    沒有模型到視圖的定義?

    修改意見?

    只有一個框架軀干,正為實現自己的應用框架提供了靈活性。沒有僵化的視圖概念,提供了在網頁輸入到模型的擴充接口,比如插入XML數據交換。?
    Eclipse?RCP?的一些有用的資源及應用案例
    yipsilon 原創???更新:2006-12-08 18:39:06??版本: 1.0 ??

    初學者有用的參考網站:

    Eclipse官方:?http://www.eclipse.org

    中國Eclipse社區:?http://www.eclipseworld.org

    IBM?DeveloperWorks?Eclipse專題:?http://www-128.ibm.com/developerworks/cn/opensource/top-projects/eclipse.html

    Planet?Eclipse:?http://www.planeteclipse.org

    Eclipse?Zone:?http://www.eclipsezone.com

    下面是一些應用的截圖:

    點擊查看原圖
    地圖應用(1)

    點擊查看原圖
    地圖應用(2)

    點擊查看原圖
    辦公自動化

    點擊查看原圖
    空間任務管理

    點擊查看原圖
    生物化學

    點擊查看原圖
    證券交易

    點擊查看原圖
    ERP應用

    點擊查看原圖
    教育應用(課件)

    點擊查看原圖
    教育應用(管理)

    點擊查看原圖
    個人信息管理

    點擊查看原圖
    數學應用

    點擊查看原圖
    互聯網應用(BT客戶端)

    posted on 2007-03-31 20:20 MEYE 閱讀(3921) 評論(0)  編輯  收藏

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


    網站導航:
     
    主站蜘蛛池模板: 无套内射无矿码免费看黄| 免费人成视频在线| 国产精品亚洲一区二区三区久久 | 四虎在线播放免费永久视频| 18成禁人视频免费网站| 久久免费观看视频| 香蕉97碰碰视频免费| 亚洲日韩一中文字暮| 亚洲日本在线播放| 亚洲精品国产成人专区| 国产亚洲美女精品久久久2020| 国产麻豆剧传媒精品国产免费| 四虎永久在线观看免费网站网址| 无码一区二区三区免费| WWW免费视频在线观看播放| 色噜噜的亚洲男人的天堂| 亚洲最大成人网色香蕉| 久久久久亚洲AV无码专区体验| 国产亚洲人成无码网在线观看 | 亚洲精品无码专区在线播放| 亚洲美免无码中文字幕在线| 亚洲VA中文字幕无码毛片| 在线日韩日本国产亚洲| 亚洲国产av一区二区三区| 免费国产美女爽到喷出水来视频| 毛片免费在线观看网址| 久久精品免费一区二区喷潮| 一区二区无码免费视频网站| 和日本免费不卡在线v| 在线v片免费观看视频| 亚洲中文无码永久免费| 日韩免费a级毛片无码a∨| 无码国产精品一区二区免费I6| 成年在线观看网站免费| 免费一本色道久久一区| 嫩草视频在线免费观看| 性做久久久久免费观看| 国产伦精品一区二区三区免费迷| 国产成人免费手机在线观看视频| 国产无遮挡裸体免费视频| 亚洲av成人一区二区三区在线观看|