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 }


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 阅读(3361) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: