实体bean
实体bean的角色
实体bean用来代表底层的对象。最常用的是用实体bean代表关系库中的数据。一个简单的实体bean可以定义成代表数据库表的一个记录,也就是每一个实例代表一个特殊的记录。更复杂的实体bean可以代表数据库表间关联视图。在实体bean中还可以考虑包含厂商的增强功能,如对象--关系映射的集成。
通常用实体类代表一个数据库表比代表多个相关联的表更简单且更有效。反过来可以轻易地向实体类的定义中增加关联,这样可以最大地复用cache并减小旧数据的表现。
实体bean和对话bean的比较
看起来会话bean好象没什么用处,尤其对于数据驱动的应用程序。当然事实并不是这样。因为实体bean(譬如说)代表底层数据库的一行,则实体bean实例和数据库记录间就是一对一的关系。因为多个客户端必须访问底层记录,这意味着,不同于会话bean,客户端必须共享实体bean。因为是共享的,所以实体bean不允许保存每个客户端的信息。会话bean允许保存客户端的状态信息,客户端和会话bean实例间是一对一的。实体bean允许保存记录的信息,实体bean实例和记录间是一对一的。一个理想的情况是客户端通过会话bean连接服务器,然后会话bean通过实体bean访问数据库。这使得既可以保存客户端的信息又可以保存数据库记录的信息。
同时会话bean也不能提供在相同或不同的EJB类调用间进行全局的事务控制。没有会话bean,应用程序开发者(客户端开发者)就必须理解EJB类的事务要求,并使用客户端的事务划分来提供事务控制。EJB的主要好处就是应用开发者不需知道EJB类的事务需求。一个会话bean可以代表一个商业操作,进行事务控制,不需要客户端进行事务划分。
Finder方法
通过home或remote interface创建和删除bean的实例,对实体bean和会话bean来说有不同的含义。对会话bean来说,删除意味着从容器中删除,不能再使用,并且其状态信息也丢失了。对于实体bean,删除意味着底层数据库记录被删除了。因此,一般不把删除作为实体bean生命周期的一部分。
创建一个实体bean意味着一个记录被插进数据库中。与删除操作类似,创建操作通常也不作为实体bean生命周期的一部分。客户端访问实体bean需要先找到它。除了create()方法,一个实体bean的home interface还有finder方法。客户端需要根据应用程序的限制来识别一个特殊的数据库记录。例如:
public interface AccountHome extends EJBHome {
public Account findByFirstLast(String first, String last)
throws RemoteException,FinderException;
public Account findByAccountNumber(String acctNum)
throws RemoteException,FinderException;
}
当客户端调用home object的任何方法,容器把调用传递到实体bean的相应方法中。
Public class myEntityBean implements EntityBean {
…
public Obejct ejbFindByFirstLast(String first, String last) {
//runs appropriate singleton SELECT statement
//returns primary key for selected row
}
public Obejct ejbFindByAccountNumber(String acctNum) {
//runs appropriate singleton SELECT statement
//returns primary key for selected row
}
}
一个较好的方法是把finder方法当成数据库的SELECT语句,而动态SQL参数相当于方法的参数。注意home interface中的finder方法向客户端返回一个对EJBObject的远程引用。Bean中的Finder方法向容器返回一个唯一的标识符,称为主键。容器用这个主键实例化一个代表选定的记录的EJBObject。不论如何实现finder方法,容器都用这个主键代表这个选定的记录,由实体类来决定如何用唯一的标识符来代表记录。
由可能一个finder方法得到满足SELECT语句条件的多个记录。这种情况下bean的finder方法返回一个主键的枚举类型。Home interface的Finder方法定义成向客户端返回EJBObject引用的枚举类型。
Public interface AccountHome extends EJBHome {
…
public Enumeration findByCompany(String companyName)
throws RemoteException,FinderException;
}
public class myEntityBean implements EntityBean {
…
public Enumeration ejbFindByCompany(String companyName) {
//runs appropriate SELECT statement
//returns an Enumeration of primary keys
}
}
主键
主键这个词有可能被曲解。把它理解为唯一的标识符更恰当些。当实体bean代表一个数据库记录时,主键可能是该记录的组合键。对于每个实体bean的实例,有一个相应的EJBObject.当一个EJBObject与一个实体bean实例对应时,该实例的主键保存在EJBObject中。
这时说该实体bean的实例有一个标识符。当客户端调用home object的finder方法时,容器会用没有标识符的实体bean的实例来执行这个请求。容器可能为此维持一个甚至多个匿名的实例。不论如何实现finder方法,都必须向容器返回底层数据的主键,如数据库的记录。如果多个记录满足条件,那么就返回多个主键。当容器得到主键后,它会用该主键初始化一个EJBObject.容器也可以初始化一个与每个EJBObject关联的实体bean的实例。因为底层记录的标识符在EJBObject中保存,因此在bean实例中没有状态。因此,容器可以在EJBObject上调用商业方法时再实例化bean,以节省内存资源。
当finder方法向容器返回主键时,容器首先会检查该主键的EJBObject是否已经存在。如果该主键的EJBObject已经存在,那么容器不会创建一个新的EJBObject,而是向客户端返回这个已存在的EJBObject的引用。这样就保证了每个记录只有一个EJBObject的实例,所有的客户端共享EJBObject.
主键只是在该类中唯一地标识bean的实例,容器负责保证其范围。应该明确finder方法只是从数据库中取出数据的主键,而不包含其它的数据项。也可能调用finder方法后不产生任何实体bean的实例,只产生包含该主键的EJBObject,当客户端调用EJBObject的方法时在产生并导入实体bean的实例。 Home object保证客户端可以访问以下方法:
public myRem findByPrimaryKey(Obejct key) throws …;
EJBObject提供以下方法的一个实现:
Public Object getPrimaryKey();
客户端能在任何时候获得实体bean的主键,并且以后可以使用该主键通过home interface重建对实体的引用。主键类的类型在部署描述符中指定。Bean开发者可以用任何类类型来表示主键。唯一的要求是类必须实现serializable,因为主键可能在客户和服务器间传递。
Enterprise JavaBeans导论一
Enterprise JavaBeans导论二
Enterprise JavaBeans导论三
Enterprise JavaBeans导论四
Enterprise JavaBeans导论五
Enterprise JavaBeans导论六
Enterprise JavaBeans导论七
<
RMI(Remote Method Invocation)
02:15
什么是RMI
分布式计算系统要求运行在不同地址空间不同主机上的对象互相调用。各种分布式系统都有自己的调用协议,如CORBA的IIOP(Internet InterORB Protocol), MTS的DCOM。那么EJB组件呢?在Java里提供了完整的sockets通讯接口,但sockets要求客户端和服务端必须进行应用级协议的编码交换数据,采用sockets是非常麻烦的。
一个代替Sockets的协议是RPC(Remote Procedure Call), 它抽象出了通讯接口用于过程调用,使得编程者调用一个远程过程和调用本地过程同样方便。RPC 系统采用XDR来编码远程调用的参数和返回值。
但RPC 并不支持对象,而EJB构造的是完全面向对象的分布式系统,所以,面向对象的远程调用RMI(Remote Method Invocation)成为必然选择。采用RMI,调用远程对象和调用本地对象同样方便。RMI采用JRMP(Java Remote Method Protocol)通讯协议,是构建在TCP/IP协议上的一种远程调用方法。
RMI调用机制
RMI 采用stubs 和 skeletons 来进行远程对象(remote object)的通讯。stub 充当远程对象的客户端代理,有着和远程对象相同的远程接口,远程对象的调用实际是通过调用该对象的客户端代理对象stub来完成的。
stub
每个远程对象都包含一个代理对象stub,当运行在本地Java虚拟机上的程序调用运行在远程Java虚拟机上的对象方法时,它首先在本地创建该对象的代理对象stub, 然后调用代理对象上匹配的方法,代理对象会作如下工作:
与远程对象所在的虚拟机建立连接
打包(marshal)参数并发送到远程虚拟机
等待执行结果
解包(unmarshal)返回值或返回的错误
返回调用结果给调用程序
stub 对象负责调用参数和返回值的流化(serialization)、打包解包,以及网络层的通讯过程。
skeleton
每一个远程对象同时也包含一个skeleton对象,skeleton运行在远程对象所在的虚拟机上,接受来自stub对象的调用。当skeleton接收到来自stub对象的调用请求后,skeleton会作如下工作:
解包stub传来的参数
调用远程对象匹配的方法
打包返回值或错误发送给stub对象
远程对象的stub和skeleton对象都是由rmic编译工具产生的。
RMI-IIOP
RMI能够很好解决Java语言中分布式对象的调用问题,但RMI不是一个标准的调用协议,所以RMI不能调用非Java语言编写的对象。
IIOP(Internet Inter-ORB Protocol)是CORBA的通讯协议。CORBA是由OMG(Object Management Group)组织定义的一种分布式组件标准,通过和各种编程语言相匹配的IDL(Interface Definition Language),CORBA可以作到和语言无关,也就是说,用不同编程语言编写的CORBA对象可以互相调用。
JavaIDL定义了Java语言到CORBA之间的匹配,通过JavaIDL,用Java语言编写的应用程序可以和任何CORBA对象通讯。
RMI-IIOP结合了RMI的易用性和CORBA/IIOP的语言无关性,通过RMI-IIOP,RMI对象可以采用IIOP协议和CORBA对象通讯。RMI-IIOP对RMI的调用参数作了一些很轻微的限制,在调用CORBA对象时,必须遵循这些限制。JDK1.3已经提供对RMI-IIOP的支持。
Apusic Application Server对RMI-IIOP的支持
Apusic Application Server目前采用RMI,对RMI-IIOP的支持正在开发中,预计不久即会推出完全支持RMI-IIOP的新版本。
<
小小豆叮
JNDI(Java Naming Directory Interface)
02:15
命名服务(naming service)
命名服务是计算机系统中的一个基本功能。命名服务是将名字和计算机中的一个对象相关联,通过名字可以方便地找到对应的对象。例如,计算机中的文件系统就包含了一个命名服务,你可以通过文件名找到对应的文件对象。
目录服务(directory service)
目录服务是命名服务的延伸,目录服务不只保存对象和对象名的匹配,而且保存这个对象的各种属性,你可以对这些属性进行查询、修改、增加、删除操作。
JNDI(Java Naming Directory Interface)
JNDI是一些标准API接口,Java程序通过这些API可以访问命名目录服务。JNDI的定义不依赖于任何独立的命名目录服务器,对于各种命名目录服务器,都可通过统一的JNDI接口调用。JNDI结构如下:
JNDI结构包括两组API:JNDI API(Application Programming Interface)和JNDI SPI(Service Provider Interface)。Java应用程序通过JNDI API访问各种命名目录服务。JNDI SPI使得各种命名目录服务透明地加入到JNDI结构中,如上图中的LDAP、DNS、NIS等服务通过JNDI SPI加入到JNDI结构中,从而使Java应用程序能够通过JNDI API访问这些服务。
JNDI Context
Context是一系列命名到对象绑定的集合,Context提供以下主要接口访问命名服务:
bind(Name name, Object obj) 建立一个命名到一个对象的匹配关系,也叫绑定
lookup(String name) 通过命名查找其对应的对象
rebind(Name name, Object obj) 重新绑定一个命名到对象,覆盖原来的绑定
unbind(Name name) 解除这个命名到其匹配对象的绑定关系
... ...
Apusic JNDI 应用举例
Apusic Application Server包含一个完全遵循JNDI标准的命名服务器,下面我们举例说明如何通过JNDI接口找到你需要调用的EJB Home接口。
Context ctx = null;
try {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.apusic.jndi.InitialContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:6888");
env.put(Context.SECURITY_PRINCIPAL, null);
env.put(Context.SECURITY_CREDENTIALS, null);
ctx = new InitialContext(env);
} catch (Exception e) {
System.err.println("Cannot get initial context: " + e);
System.exit(1);
}
AccountHome home = null;
try {
home = (AccountHome) ctx.lookup("CMAccountHome");
} catch (Exception e) {
System.err.println("Cannot lookup CMAccountHome" + e);
System.exit(1);
}
首先,通过Hashtable env传递JNDI调用参数,取得JNDI Context ctx,各参数说明如下:
Context.INITIAL_CONTEXT_FACTORY: 指定jndi环境的工厂,在Apusic中必须为 com.apusic.jndi.InitialContextFactory
Context.PROVIDER_URL: 指定命名服务提供者的URL,格式为:rmi://localhost:6888
Context.SECURITY_PRINCIPAL: 指定用户身份,null为“匿名用户”
Context.SECURITY_CREDENTIALS: 指定用户密码,同上
然后,通过JNDI Context ctx 的lookup方法找到"CMAccountHome"绑定的CMAccountBean的Home接口。
<
小小豆叮
Enterprise JavaBeans导论一
02:14
服务框架
EJB并不是一个产品。它是Java服务器端服务框架的规范,软件厂商根据它来实现EJB服务器。应用程序开发者可以专注于支持应用所需的商业逻辑,而不用担心周围框架的实现问题。
EJB规范详细地解释了一些最小但是必须的服务,如事务,安全和名字等。软件厂商根据这些规范要求以保证一个enterprise bean能使用某个必需的服务。规范并没有说明厂商如何实现这些服务。这使得通过阅读规范来学习EJB更加困难,因为它允许厂商在不牺牲核心服务的可移植性的前提下来提供一些增强功能。
JavaBeans和Enterprise JavaBeans
JavaBeans是Java的组件模型。在JavaBeans规范中定义了事件和属性等特征。Enterprise JavaBeans也定义了一个Java组件模型,但是Enterprise JavaBeans组件模型和JavaBeans组件模型是不同的。 JavaBeans重点是允许开发者在开发工具中可视化的操纵组件。JavaBeans规范详细地解释了组件间事件登记、传递、识别和属性使用、定制和持久化的应用编程接口和语意。 Enterprise JavaBeans的侧重点是详细地定义了一个可以portably地部署Java组件的服务框架模型。因此,其中并没提及事件,因为enterprise bean通常不发送和接受事件。同样也没有提及属性------属性定制并不是在开发时进行,而是在运行时(实际上在部署时)通过一个部署描述符来描述。
不要寻找JavaBeans和Enterprise JavaBeans之间的相似性。他们都是组件模型规范,但是前者说明了开发工具中应用程序组装的问题,而后者则侧重于部署组件的服务框架的细节。不要错误地认为JavaBeans是用于客户端的开发,Enterprise JavaBeans是用于服务器端的开发。JavaBeans也可作为进行非图形化服务器端Java应用开发的组件模型。区别是当你使用JavaBeans创建服务器应用时,你还得设计整个的服务框架。用Enterprise Javabeans框架是现成的,你只需遵守它的APIs.对于复杂的服务器端应用程序,显然使用Enterprise JavaBeans比重新开发更简单。
Enterprise JavaBeans体系结构
EJB服务器是管理EJB容器的高端进程或应用程序,并提供对系统服务的访问。EJB服务器也可以提供厂商自己的特性,如优化的数据库访问接口,对其他服务(如CORBA服务)的访问,对SSL 3.0的支持等。一个EJB服务器必须提供对可访问JNDI的名字服务和事务服务支持。一些可能的EJB服务器的例子如:
·数据库服务器
·应用服务器
·中间件服务器
EJB容器是一个管理一个或多个EJB类/实例的抽象。它通过规范中定义的接口使EJB类访问所需的服务。容器厂商也可以在容器或服务器中提供额外服务的接口。
现在没有EJB服务器和EJB容器间接口的规范。因为目前容器通常由EJB服务器来提供,所以一旦接口标准化了,厂商就可能提供可以在任何兼容的EJB服务器上运行的容器。
Home接口列出了所有定位、创建、删除EJB 类实例的方法。Home对象是home接口的实现。EJB类开发者必须定义home接口。容器厂商应该提供从home接口中产生home对象实现的方法。
远程接口(remote interface)列出了EJB类中的商业方法。EJBObject实现远程接口,并且客户端通过它访问EJB实例的商业方法。EJB类开发者定义远程接口,容器开发商提供产生相应的EJBObject的方法。客户端不能得到EJB实例的引用,只能得到它的EJBObject实例的引用。当客户端调用一个方法,EJBObject接受请求并把它传给EJB实例,同时提供进程中必要的包装功能。客户端应用程序通过home对象来定位、创建、删除EJB类的实例,通过EJBObject来调用实例中的商业方法。客户端可以用Java来编程,通过Java RMI来访问访问home对象和EJBObject,或用其他语言编程并通过CORBA/IIOP访问,使得部署的服务器端组件可以通过CORBA接口来访问。
上图是Enterprise JavaBeans体系结构的一个描述。下一节详细讨论了每个组件的细节。
服务框架
EJB并不是一个产品。它是Java服务器端服务框架的规范,软件厂商根据它来实现EJB服务器。应用程序开发者可以专注于支持应用所需的商业逻辑,而不用担心周围框架的实现问题。
EJB规范详细地解释了一些最小但是必须的服务,如事务,安全和名字等。软件厂商根据这些规范要求以保证一个enterprise bean能使用某个必需的服务。规范并没有说明厂商如何实现这些服务。这使得通过阅读规范来学习EJB更加困难,因为它允许厂商在不牺牲核心服务的可移植性的前提下来提供一些增强功能。
JavaBeans和Enterprise JavaBeans
JavaBeans是Java的组件模型。在JavaBeans规范中定义了事件和属性等特征。Enterprise JavaBeans也定义了一个Java组件模型,但是Enterprise JavaBeans组件模型和JavaBeans组件模型是不同的。 JavaBeans重点是允许开发者在开发工具中可视化的操纵组件。JavaBeans规范详细地解释了组件间事件登记、传递、识别和属性使用、定制和持久化的应用编程接口和语意。 Enterprise JavaBeans的侧重点是详细地定义了一个可以portably地部署Java组件的服务框架模型。因此,其中并没提及事件,因为enterprise bean通常不发送和接受事件。同样也没有提及属性------属性定制并不是在开发时进行,而是在运行时(实际上在部署时)通过一个部署描述符来描述。
不要寻找JavaBeans和Enterprise JavaBeans之间的相似性。他们都是组件模型规范,但是前者说明了开发工具中应用程序组装的问题,而后者则侧重于部署组件的服务框架的细节。不要错误地认为JavaBeans是用于客户端的开发,Enterprise JavaBeans是用于服务器端的开发。JavaBeans也可作为进行非图形化服务器端Java应用开发的组件模型。区别是当你使用JavaBeans创建服务器应用时,你还得设计整个的服务框架。用Enterprise Javabeans框架是现成的,你只需遵守它的APIs.对于复杂的服务器端应用程序,显然使用Enterprise JavaBeans比重新开发更简单。
Enterprise JavaBeans体系结构
EJB服务器是管理EJB容器的高端进程或应用程序,并提供对系统服务的访问。EJB服务器也可以提供厂商自己的特性,如优化的数据库访问接口,对其他服务(如CORBA服务)的访问,对SSL 3.0的支持等。一个EJB服务器必须提供对可访问JNDI的名字服务和事务服务支持。一些可能的EJB服务器的例子如:
·数据库服务器
·应用服务器
·中间件服务器
EJB容器是一个管理一个或多个EJB类/实例的抽象。它通过规范中定义的接口使EJB类访问所需的服务。容器厂商也可以在容器或服务器中提供额外服务的接口。
现在没有EJB服务器和EJB容器间接口的规范。因为目前容器通常由EJB服务器来提供,所以一旦接口标准化了,厂商就可能提供可以在任何兼容的EJB服务器上运行的容器。
Home接口列出了所有定位、创建、删除EJB 类实例的方法。Home对象是home接口的实现。EJB类开发者必须定义home接口。容器厂商应该提供从home接口中产生home对象实现的方法。
远程接口(remote interface)列出了EJB类中的商业方法。EJBObject实现远程接口,并且客户端通过它访问EJB实例的商业方法。EJB类开发者定义远程接口,容器开发商提供产生相应的EJBObject的方法。客户端不能得到EJB实例的引用,只能得到它的EJBObject实例的引用。当客户端调用一个方法,EJBObject接受请求并把它传给EJB实例,同时提供进程中必要的包装功能。客户端应用程序通过home对象来定位、创建、删除EJB类的实例,通过EJBObject来调用实例中的商业方法。客户端可以用Java来编程,通过Java RMI来访问访问home对象和EJBObject,或用其他语言编程并通过CORBA/IIOP访问,使得部署的服务器端组件可以通过CORBA接口来访问。
上图是Enterprise JavaBeans体系结构的一个描述。下一节详细讨论了每个组件的细节。
Enterprise JavaBeans导论一
Enterprise JavaBeans导论二
Enterprise JavaBeans导论三
Enterprise JavaBeans导论四
Enterprise JavaBeans导论五
Enterprise JavaBeans导论六
Enterprise JavaBeans导论七
<
小小豆叮
利用Java实现zip压缩/解压缩
02:14
由于网络带宽有限,所以数据文件的压缩有利于数据在Internet上的快速传输,同时也节
省服务器的外存空间。
Java 1.1实现了I/O数据流与网络数据流的单一接口,因此数据的压缩、网络传输和解
压缩的实现比较容易,下面介绍利用ZipEntry、ZipInputStream和ZipOutputStream三个Java
类实现zip数据压缩方式的编程方法。
zip压缩文件结构:一个zip文件由多个entry组成,每个entry有一个唯一的名称,entry的
数据项存储压缩数据。
与zip文件有关的几个Java类
·类ZipEntry
public ZipEntry(String name);
name为指定的数据项名。
·类ZipOutputStream
ZipOutputStream实现了zip压缩文件的写输出流,支持压缩和非压缩entry。下面是它的
几个函数:
public ZipOutputStream(OutputStream out);
∥利用输出流out构造一个ZIP输出流。
public void setMethod(int method);
∥设置entry压缩方法,缺省值为DEFLATED。
public void putNextEntry(ZipEntry newe);
∥如果当前的entry存在且处于激活状态时,关闭它,在zip文件中写入新的entry-newe
并将数据流定位于entry数据项的起始位置,压缩方法为setMethod指定的方法。
·类ZipInputStream
ZipInputStream实现了zip压缩文件的读输入流,支持压缩和非压缩entry。下面是它的
几个函数:
public ZipInputStream(InputStream in);
∥利用输入流in构造一个ZIP输出流。
public ZipEntry getNextEntry();
∥返回ZIP文件中的下一个entry,并将输出流定位在此entry数据项的起始位置。
public void closeEntry();
∥关闭当前的zip entry,并将数据流定位于下一个entry的起始位置。
程序代码及其注释
下列的程序实现了数据文件zip方式的压缩和解压缩方法。randomData()函数随机生成
50个double数据,并放在doc字符串变量中;openFile()函数读取ZIP压缩文件;saveFile()函数
将随机生成的数据存到ZIP格式的压缩文件中。
import java.util.zip.*;
import java.awt.event.*;
import java.awt.*;
import java.lang.Math;
import java.io.*;
public class TestZip extends Frame implements ActionListener {
TextArea textarea; ∥显示数据文件的多行文本显示域
TextField infotip; ∥显示数据文件未压缩大小及压缩大小单行文本显示域
String doc; ∥存储随机生成的数据
long doczipsize = 0;∥压缩数据文件的大小
public TestZip(){
∥生成菜单
MenuBar menubar = new MenuBar();
setMenuBar(menubar);
Menu file = new Menu("File",true);
menubar.add(file);
MenuItem neww= new MenuItem("New");
neww.addActionListener(this);
file.add(neww);
MenuItem open=new MenuItem("Open");
open.addActionListener(this);
file.add(open);
MenuItem save=new MenuItem("Save");
save.addActionListener(this);
file.add(save);
MenuItem exit=new MenuItem("Exit");
exit.addActionListener(this);
file.add(exit);
∥随机生成的数据文件的多行文本显示域
add("Center",textarea = new TextArea());
∥提示文本原始大小、压缩大小的单行文本显示域
add("South",infotip = new TextField());
}
public static void main(String args[]){
TestZip ok=new TestZip();
ok.setTitle("zip sample");
ok.setSize(600,300);
ok.show();
}
private void randomData(){
∥随机生成50个double数据,并放在doc字符串变量中。
doc="";
for(int i=1;i<51;i++){
double rdm=Math.random()*10;
doc=doc+new Double(rdm).toString();
if(i%5 == 0) doc=doc+"\n";
else doc=doc+" ";
}
doczipsize = 0;
showTextandInfo();
}
private void openFile(){
∥打开zip文件,将文件内容读入doc字符串变量中。
FileDialog dlg=new FileDialog(this,"Open",FileDialog.LOA D);
dlg.show();
String filename=dlg.getDirectory()+dlg.getFile();
try{
∥创建一个文件实例
File f=new File(filename);
if(!f.exists()) return; ∥文件不存在,则返回
∥用文件输入流构建ZIP压缩输入流
ZipInputStream zipis=new ZipInputStream(new FileInputStream(f));
zipis.getNextEntry();
∥将输入流定位在当前entry数据项位置
DataInputStream dis=new DataInputStream(zipis);
∥用ZIP输入流构建DataInputStream
doc=dis.readUTF();∥读取文件内容
dis.close();∥关闭文件
doczipsize = f.length();∥获取ZIP文件长度
showTextandInfo();∥显示数据
}
catch(IOException ioe){
System.out.println(ioe);
}
}
private void saveFile(){
∥打开zip文件,将doc字符串变量写入zip文件中。
FileDialog dlg=new FileDialog(this,"Save",FileDialog.SAVE);
dlg.show();
String filename=dlg.getDirectory()+dlg.getFile();
try{
∥创建一个文件实例
File f=new File(filename);
if(!f.exists()) return; ∥文件不存在,则返回
∥用文件输出流构建ZIP压缩输出流
ZipOutputStream zipos=new ZipOutputStream(new FileOutputStream(f));
zipos.setMethod(ZipOutputStream.DEFLATED); ∥设置压缩方法
zipos.putNextEntry(new ZipEntry("zip"));
∥生成一个ZIP entry,写入文件输出流中,并将输出流定位于entry起始处。
DataOutputStream os=new DataOutputStream(zipos);
∥用ZIP输出流构建DataOutputStream;
os.writeUTF(doc);∥将随机生成的数据写入文件中
os.close();∥关闭数据流
doczipsize = f.length();
∥获取压缩文件的长度
showTextandInfo();∥显示数据
}
catch(IOException ioe){
System.out.println(ioe);
}
}
private void showTextandInfo(){
∥显示数据文件和压缩信息
textarea.replaceRange(doc,0,textarea.getText().length());
infotip.setText("uncompressed size: "+doc.length()+"compressed size: "+dc zipsize);
}
public void actionPerformed(ActionEvent e){
String arg = e.getActionCommand();
if ("New".equals(arg)) randomData();
else if ("Open".equals(arg)) openFile();
else if ("Save".equals(arg)) saveFile();
else if ("Exit".equals(arg)){
dispose();∥关闭窗口
System.exit(0);∥关闭程序
}
else {
System.out.println("no this command!");
}
}
}
<
小小豆叮
Java何以保网络安全
02:14
Java是惟一一种从设计的开始就考虑安全性的移动代码。虽然并不完美,Java却在程序的功能性和保护主机的安全性两方面达到了最大的平衡,并且Java非常适合将代码从一个平台向另一个平台的移植。
Java采用一个称为“sandbox”的模型来运行移动代码。不被信任的移动代码只能在sandbox中运行而不能进行多数针对主机的操作,比如读、写或删除文件,监听或接受网络连接等等。
当浏览器加载含有Java applet的页面时,它从Web服务器上取Java的字节代码,然后将代码传给称为字节代码检验器的Java部件。检验器确保字节代码有正确的格式,不会超出内部堆栈的边界,这就防止了程序的崩溃。第二个Java部件——类加载器,决定一个Java applet在何时、以何种方式将代码加入正在运行的Java环境,保证此applet不会取代任何系统级别的内容。(每一个Java程序由一个或多个类、数据对象以及如何操作数据的方法所组成。)
最后,还有第三个部件称为安全管理器,当一个有潜在危险的方法试图运行时,安全管理器就会起作用。是否运行这种有潜在危险的方法取决于需求此种方法的类来源于何处。举例来说,内部本身的类比通过网络获取的类具有更大的特权。(因为这一原因,要注意千万不要将不明出处的类通过将其路径放在系统的CLASSPATH下而成为内部本身的类。)
由此可见,字节编码检验器、类加载器和安全管理器使得Java程序高效而安全。即使这样,任何一个部件中的编程错误都会导致安全问题。这就是为什么某些攻击只是针对某一个特定厂商的JVM,这是因为并不是Java本身的安全模型出了问题,而是因为实现这一模型的方法出了问题。
随着Java程序的复杂化、实用化,必须要脱离“sandbox”运行。这可以通过Java开发工具包JDK(Java Development Kit )1.1来实现,JDK在系统中扩展了加密的API,并且支持数字签名。在Java文档中(.JAR文件)的applet可以通过签名来保护,允许最终用户知道程序是来自可信任的地方,并且未经过任何修改。通过JDK1.1,如果用户通过签名确信applet代码来自可信任的地方,便通知浏览器和JVM将获取的代码与本地代码同样对待,获取完全的权限。
Java 1.2(后来更名为Java 2)在安全方面更进一步,它提供权限不同的模型来运行不同的代码,也就是说,不管是本地代码、下载的信任代码或下载的非信任代码都可以在不同的特权环境中运行。总的说来,Java的安全漏洞较少,尤其是Java 2 ,在安全和性能两方面都有很大提高。
<
小小豆叮
企业内部网中使用Policy文件来设置Java的安全策略
02:13
---- 众所周知,Java语言具有完善的安全框架,从编程语言,编译器、解释程序到Java虚拟机,都能确保Java系统不被无效的代码或敌对的编译器暗中破坏,基本上,它们保证了Java代码按预定的规则运作。但是,当我们需要逾越这些限制时,例如,读写文件,监听和读写Socket,退出Java系统等,就必须使用数字签名或安全策略文件(*.Policy)。
---- 在企业内部网中,本文提出了使用安全策略文件来设置java程序权限的一种简单的方法。由于企业内部网中各台计算机的位置、用途和安全性明确,更适于使用安全策略文件来设置java的权限,软件的安装、设置、升级和迁移都非常的方便,并且,还可以和数字签名配合使用,更重要的是,可以细分每个java程序的权限,使用起来灵活方便。
一. Java中安全策略的概念
---- Java应用程序环境的安全策略,详细说明了对于不同的代码所拥有的不同资源的许可,它由一个Policy对象来表达。为了让applet(或者运行在SecurityManager下的一个应用程序)能够执行受保护的行为,例如读写文件,applet(或Java应用程序)必须获得那项操作的许可,安全策略文件就是用来实现这些许可。
---- Policy对象可能有多个实体,虽然任何时候只能有一个起作用。当前安装的Policy对象,在程序中可以通过调用getPolicy方法得到,也可以通过调用setPolicy方法改变。Policy对象评估整个策略,返回一个适当的Permissions对象,详细说明那些代码可以访问那些资源。
---- 策略文件可以储存在无格式的ASCII文件,或Policy类的二进制文件,或数据库中。本文仅讨论无格式的ASCII文件的形式。
二. Policy文件的格式
---- 为了能够更好地理解下面的内容,建议在阅读时参照\jdk1.2\jre\lib\security\java.policy文件和\jdk1.2\jre\lib\security\java.security文件的内容。
---- 1. Policy文件的语法格式与说明
---- 一个Policy文件实质上是一个记录列表,它可能含有一个“keystore”记录,以及含有零个或多个“grant”记录。其格式如下:
keystore "some_keystore_url",
"keystore_type";
grant [SignedBy "signer_names"]
[, CodeBase "URL"] {
Permission permission_class_name
[ "target_name" ]
[, "action"] [, SignedBy "signer_names"];
Permission ...
};
---- 1.1"keystore"记录
---- 一个keystore是一个私有密钥(private keys)数据库和相应的数字签名,例如X.509证书。Policy文件中可能只有一条keystore记录(也可能不含有该记录),它可以出现在文件中grant记录以外的任何地方。Policy配置文件中指定的keystores用于寻找grant记录中指定的、签名者的公共密钥(public keys),如果任何grant记录指定签名者(signer_names),那么,keystore记录必须出现在policy配置文件中。
---- "some_keystore_url"是指keystore的URL位置,"keystore_type"是指keystore的类型。第二个选项是可选项,如果没有指定,该类型则假定由安全属性文件(java.security)中的"keystore.type"属性来确定。keystore类型定义了keystore信息的存储和数据格式,用于保护keystore中的私有密钥和keystore完整性的算法。Sun Microsystems支持的缺省类型为“JKS”。
---- 1.2"grant"记录
---- 在Policy文件中的每一个grant记录含有一个CodeSource(一个指定的代码)及其permission(许可)。
---- Policy文件中的每一条grant记录遵循下面的格式,以保留字“grant”开头,表示一条新的记录的开始,“Permission”是另一个保留字,在记录中用来标记一个新的许可的开始。每一个grant记录授予一个指定的代码(CodeBase)一套许可(Permissions)。
---- permission_class_name必须是一个合格并存在的类名,例如java.io.FilePermission,不能使用缩写(例如,FilePermission)。
---- target_name用来指定目标类的位置,action用于指定目标类拥有的权限。
---- target_name可以直接指定类名(可以是绝对或相对路径),目录名,也可以是下面的通配符:
directory/* 目录下的所有文件
*当前目录的所有文件
directory/-目录下的所有文件,包括子目录
- 当前目录下的所有文件,包括子目录
《ALL FILES》文件系统中的所有文件
对于java.io.FilePermission,action可以是:
read, write, delete和execute。
对于java.net.SocketPermission,action可以是:
listen,accept,connect,read,write。
---- 1.3 Policy文件中的属性扩展(Property Expansion)
---- 属性扩展与shell中使用的变量扩展类似,它的格式为:
"${some.property}"
实际使用的例子为:
permission java.io.FilePermission
"${user.home}", "read";
"${user.home}"的值为"d:\Project",
因此,下面的语句和上面的语句是一样的:
permission java.io.FilePermission "
d:\Project ", "read";
三. 实例
---- 当初始化Policy时,首先装载系统Policy,然后再增加用户Policy,如果两者都不存在,则使用缺省的Policy,即原始的沙箱模型。
---- 系统Policy文件的缺省位置为:
{java.home}/lib/security/java.policy (Solaris)
{java.home}\lib\security\java.policy (Windows)
用户Policy文件的缺省位置为:
{user.home}/.java.policy (Solaris)
{user.home}\.java.policy (Windows)
---- 其实,在实际使用中,我们可能不会象上面介绍的那么复杂,特别是在不使用数字签名时。这时,我们完全可以借鉴JDK 1.2提供给我们的现成的\jdk1.2\jre\lib\security\java.policy文件,根据我们的需要作相应的修改,本文就针对不使用数字签名情况详细说明安全策略文件的用法。
---- 下面,是一个完整的在Windows 95/98/NT下使用的.java.policy文件。在文件中,分别使用注释的形式说明了每个“permission”记录的用途。
// For LanServerTalk.java and LanClientTalk.java
grant {
//对系统和用户目录“读”的权限
permission java.util.PropertyPermission
"user.dir", "read";
permission java.util.PropertyPermission
"user.home", "read";
permission java.util.PropertyPermission
"java.home", "read";
permission java.util.PropertyPermission
"java.class.path", "read";
permission java.util.PropertyPermission
"user.name", "read";
//对线程和线程组的操作权限
permission java.lang.RuntimePermission
"modifyThread";
permission java.lang.RuntimePermission
"modifyThreadGroup";
//操作Socket端口的各种权限
permission java.net.SocketPermission
"-", "listen";
permission java.net.SocketPermission
"-", "accept";
permission java.net.SocketPermission
"-", "connect";
permission java.net.SocketPermission "-", "read";
permission java.net.SocketPermission "-", "write";
//读写文件的权限
permission java.io.FilePermission "-", "read";
permission java.io.FilePermission "-", "write";
//退出系统的权限,例如System.exit(0)
permission java.lang.RuntimePermission "exitVM";
};
四. java.policy文件的使用
---- 对于windows 95/98/NT,使用.java.policy文件的方法主要有下面两种。
---- 1. 使用缺省目录
---- 我们可以简单地将编辑好的.java.policy文件拷贝到windows 95/98/NT的HOME目录,这时,所有的applet(或Java应用程序)可能都拥有某些相同的权限,使用起来简单,但不灵活(例如:对于java.io.FilePermission ,其目标类的target_name必须使用绝对路径),如果不是在企业内部网中使用,还可能存在一定安全隐患。
---- 2. 在命令行中指定
---- 在命令行,如果我们希望传递一个Policy文件给appletviewer,还可以使用"-J-Djava.security.policy"参数来指定policy的位置:
appletviewer -J-Djava.security.
policy=pURL myApplet
---- pURL为Policy文件的位置。下面,是一个实际的例子,以当前目录的.java.policy文件所指定的安全策略运行当前目录的LanServerTalk.html(文件中装载并运行LanServerTalk.java):
appletviewer -J-Djava.security.policy
=.java.policy LanServerTalk.html
---- 这种方法使用灵活,特别是作为一个软件包在企业内部网中发布时,安装、设置和迁移软件,基本无须修改Policy文件的内容,使用起来相当简单,而且,安全许可的范围控制较精细
<
小小豆叮
Apache 安装手册(Linux)
02:13
安装手册将按由前至后的步骤, 逐步的引导您完成从 Apache 源码的下载到源码编译的整个过程. 请您按照以下的步骤进行, 如果执行的过程中出现了错误的提示信息, 或者任何的未指定的信息. 请您与我们的注册工程师联系, 我们的注册工程师将会尽快的与您联系并提供相应的解决方法.
现在开始我们的安装过程:
1. 到 http://www.apache.org 下载最新版本的 Apache, 本手册所使用的版本为 1.3.9, 至本手册完成时 Apache 最新稳定版本为 1.3.12, 2.0 版正处于测试阶段. 我们这里假设您是在 WinX 里面下载文件的(如果您熟悉使用 Linux 下载文件的话, 直接在 Linux 里面也是可以的), 下载安成后, 假设您将其放置于 c:\, 而 c: 对应于 /dev/hda1.
2. 进入 Linux, 并以 root 的身份登录(因为其他用户在正常的情况下无权挂接文件系统).
3. 在 /mnt 里面创建一个新的文件夹 diskc(如果原来已经存在, 可以省略此步). mkdir /mnt/diskc.
4. 因为我们将 apache 的源码放置于 dos/win 的 c:\, 所以我们现在要先把 dos/win 的 c:\ 挂接至我们新建的目录 /mnt/diskc 里面, 假设您的 dos/win 的 c: 在磁盘中的位置为 /dev/hda1 则, mount -t vfat /dev/hda1 /mnt/diskc (mount 的说明, 请您参见我们的相关文章, 或者直接与我们的注册工程师联系).
5. 成功把 dos/win 的 c:\ 挂至 /mnt/diskc 后, 我们就要把源码的文件复制至 Linux 的目录中. 为什么要把源码复制至 Linux 的系统中而不要放置在 /mnt/diskc (c:\)中呢? 原因是方便我们以后对系统进行维护. 同时我们强烈的建议您把所有的源码文件(当然包括 Apache 的源码文件), 都放置于 /usr/local/src 中, 这样将方便于我们对所有服务器/软件的源码进行有效的管理. 相应的命令为: cp /mnt/diskc/apache_1.3.9.tar.gz /usr/local/src. (这里, Apache 源码的文件名为 apache_1.3.9.tar.gz 如果您使用的是其它版本的 Apache 的话, 需要注意版本吧, 另外 Linux 下所有的文件名都是区分大小写的.)
6. 把源码文件复制至 /usr/local/src 后, 因为所有的源码都经打包压缩的, 我们就要把源码从压缩包中解压缩出来. 相应的命令为: cd /usr/local/src; tar zxvf apache_1.3.9.tar.gz.
7. 解压缩后, 我们进入源码的目录并使用配置脚本进行环境的设置. 相应的命令为: cd /usr/local/src/apache_1.3.9; ./configure.
8. 在执行 ./configure 之后, 配置脚本会自动生成 Makefile. 如果在设置的过程中没有任何的错误, 我们就可以开始编译源码了. 相应的命令为: make.
9. 在源码编译完成后, 我们就要使用 make install 安装 Apache 至缺省的目录(/usr/local/apache)下.
10. 启动 Apache 服务器, cd /usr/local/apache/bin; ./apachectl start.
命令列表:
login as root
#mkdir /mnt/diskc
#mount -t vfat /dev/hda1 /mnt/diskc
#cp /mnt/diskc/apache_1.3.9.tar.gz /usr/local/src
#cd /usr/local/src
#tar zxvf apache_1.3.9.tar.gz
#cd apache_1.3.9
#./configure
#make
#make install
#cd /usr/local/apache/bin
#./apachectl start
以上为 Apache 的简短安装过程
<
小小豆叮
EJB的编程限制
02:13
Enterprise JavaBeans(EJB)是一个开发和部署分布式服务器端的、带事务处理的、安全的商业组件的规范和结构。EJB的体系结构是J2EE的基础和核心,J2EE定义了整个标准的应用开发体系结构和一个部署环境。在这个体系结构中,应用开发者的注意力集中在封装商业逻辑和商业规则上,一切与基础结构服务相关的问题和底层分配问题都由应用程序容器或服务器来处理。
甚至,从属于事务、持久化、安全等等方面的应用组件的运行时属性都可以使用高度灵活的声明方法在部署的环境中定制。这个体系结构定义了一个容器和一个服务器模型--容器是应用组件生存和执行的环境,而这个容器却又寄居在一个服务器之中。J2EE平台提供了一个简化的开发模型,它具有工业强度的可扩展性、支持合理的集成和灵活的部署,与开发商和应用服务器无关,这一切使得一些专用的应用服务器和专用的分布式对象框架变得古旧了。
EJB的角色和责任
EJB规范定义了几个标准的角色和责任者,如下:
1.EJB服务器提供商提供的应用服务器应是一个在分布式事务处理、系统服务等方面的专家。
2.EJB容器提供商提供EJB组件实例运行环境和部署工具。EJB 服务器/容器提供商是典型的操作系统开发商、数据库开发商或者是应用服务器开发商。EJB的服务器和EJB的容器应是同一个开发商提供,因为无论是在现在的EJB1.1规范(最终版)还是EJB2.0公共草稿版(正在修改)中都没有定义两者之间的接口。
3.Bean的提供商或者EJB开发者开发的EJB组件都包涵商业逻辑及商业功能。EJB开发者提供的每一个EJB组件都应满足以下条件:EJB的实现中应包括所有必须有的组件-容器合同方法(Contract method),如:ejbCreate(),ejbRemove()等等和一些商业方法(business method);Home接口;Remote接口;如需要还应有帮助类。Home接口为创建、删除和查找EJB实例的方法提供签名,Remote接口定义了商业方法的签名。
4.应用程序组装器把一些由Bean提供商开发的EJB组件组装成一个完整的J2EE应用程序。
5.部署器在应用程序部署的目标产品环境中是专家,它在应用服务器中安装应用组件并配置它们的事务、持久化和安全方面。这样你就可以管理复杂的问题了,诸如:事务处理、并发处理、持久化以及安全。
6.系统管理者负责服务器的配置和管理、运行监控和负载平衡。
7.应用程序的用户界面开发者负责用户界面和表示逻辑。
这篇文章的焦点集中在Bean提供商/EJB开发者方面和EJB组件实现代码的限制方面。
EJB组件的约束
EJB的开发者并不需要在EJB的组件实现代码中编写系统级的服务,EJB提供商/开发者需知道并且严格地遵守一些限制,这些限制与开发稳定的和可移植的EJB组件的利益有关。
以下是你应该回避使用的一些Java特色,并且在你的EJB组件的实现代码中要严格限制它们的使用:
1.使用static,非final 字段。建议你在EJB组件中把所有的static字段都声明为final型的。这样可以保证前后一致的运行期语义,使得EJB容器有可以在多个Java虚拟机之间分发组件实例的灵活性。
2.使用线程同步原语来同步多个组件实例的运行。避免这个问题,你就可以使EJB容器灵活的在多个Java虚拟机之间分发组件实例。
3.使用AWT函数完成键盘的输入和显示输出。约束它的原因是服务器方的商业组件意味着提供商业功能而不包括用户界面和键盘的I/O功能。
4.使用文件访问/java.io 操作。EJB商业组件意味着使用资源管理器如JDBC来存储和检索数据而不是使用文件系统API。同时,部署工具提供了在部署描述器(descriptor)中存储环境实体,以至于EJB组件可以通过环境命名上下文用一种标准的方法进行环境实体查询。所以,使用文件系统的需求基本上是被排除了。
5.监听和接收socket连接,或者用socket进行多路发送。EJB组件并不意味着提供网络socket服务器功能,但是,这个体系结构使得EJB组件可以作为socket客户或是RMI客户并且可以和容器所管理的环境外面的代码进行通讯。
6.使用映象API查询EJB组件由于安全规则所不能访问的类。这个约束加强了Java平台的安全性。
7.欲创建或获得一个类的加载器,设置或创建一个新的安全管理器,停止Java虚拟机,改变输入、输出和出错流。这个约束加强了安全性同时保留了EJB容器管理运行环境的能力。
8.设置socket工厂被URL's ServerSocket,Socket和Stream handler使用。避免这个特点,可以加强安全性同时保留了EJB容器管理运行环境的能力。
9.使用任何方法启动、停止和管理线程。这个约束消除了与EJB容器管理死锁、线程和并发问题的责任相冲突的可能性。
通过限制使用10-16几个特点,你的目标是堵上一个潜在的安全漏洞:
10.直接读写文件描述符。
11.为一段特定的代码获得安全策略信息。
12.加载原始的类库。
13.访问Java一般角色所不能访问的包和类。
14.在包中定义一个类。
15.访问或修改安全配置对象(策略、安全、提供者、签名者和实体)。
16.使用Java序列化特点中的细分类和对象替代。
17.传递this引用指针作为一个参数或者作为返回值返回this引用指针。你必须使用SessionContext或EntityContext中的getEJBObject()的结果。
Java2平台的安全策略
以上所列的特点事实上正是Java编程语言和Java2标准版中的标准的、强有力的特色。EJB容器允许从J2SE中使用一些或全部的受限制的特色,尽管对于EJB组件是不可用的,但需通过J2SE的安全机制来使用而不是通过直接使用J2SE的API。
Java2平台为EJB1.1规范中的EJB容器所制定的安全策略定义了安全许可集,这些许可在EJB组件的编程限制中出现。通过这个策略,定义了一些许可诸如:java.io.FilePermission,java.net.NetPermission,java.io.reflect.ReflectPermission,
java.lang.security.SecurityPermission,以便加强先前所列出的编程限制。
许多EJB容器没有加强这些限制,他们希望EJB组件开发者能遵守这些编程限制或者是带有冒险想法违背了这些限制。违背这些限制的EJB组件,比标准方法依赖过多或过少的安全许可,都将很少能在多个EJB容器间移植。另外,代码中都将隐藏着一些不确定的、难以预测的问题。所有这些都足以使EJB组件开发者应该知道这些编程限制,同时也应该认真地遵守它们。
任何违背了这些编程限制的EJB组件的实现代码在编译时都不能检查出来,因为这些特点都是Java语言和J2SE中不可缺少的部分。
对于EJB组件的这些限制同样适用于EJB组件所使用的帮助/访问(helper/access)类,J2EE应用程序使用Java文档(jar)文件格式打包到一个带.ear(代表Enterprise Archive)扩展名的文件中,这个ear文件对于发送给文件部署器来说是标准的格式。ear文件中包括在一个或多个ejb-jar文件中的EJB组件,还可能有ejb-jar所依赖的库文件。所有ear文件中的代码都是经过深思熟虑开发的应用程序并且都遵守编程限制和访问许可集。
未来版本的规范可能会指定通过部署工具来定制安全许可的能力,通过这种方法指定了一个合法的组件应授予的许可权限,也指定了一个标准方法的需求:如从文件系统中读文件应有哪些要求。一些EJB容器/服务器目前在它们的部署工具中都提供了比标准权限或多或少的许可权限,这些并不是EJB1.1规范中所需要的。
理解这些约束
EJB容器是EJB组件生存和执行的运行期环境,EJB容器为EJB组件实例提供了一些服务如:事务管理、安全持久化、资源访问、客户端连接。EJB容器也负责EJB组件实例整个生命期的管理、扩展问题以及并发处理。所以,EJB组件就这样寄居在一个被管理的执行环境中--即EJB容器。
EJB容器也是EJB组件和外部世界的中间者,它提供了客户连接服务来允许应用程序客户访问和使用EJB组件所提供的功能,EJB容器通过bean的Remote和Home接口介入每一个对EJB对象方法的调用。
EJB容器也是EJB组件和访问其它各种资源和服务的中间人,因为EJB容器介入应用组件和J2EE服务,它可以透明地引入组件部署描述符所定义的服务,如:事务管理、安全、持久化、并发处理和状态管理。
资源就是一个封装了访问资源管理器的对象,因为一个资源工厂就是一个用来建造资源的对象。例如,一个JDBC连接代表一个实现了java.sql.Connection接口的对象,它是用来提供访问数据库管理系统的资源,并且实现了javax.sql.DataSource接口的对象是一个这样JDBC连接的资源工厂。同样,定义了许多获得JMS、JavaMail以及URL连接的资源工厂,目前除此之外没有其它的资源工厂了。
(J2EE连接体系结构,目前正在修改,将期盼着包括J2EE未来版本的规范,这个连接体系结构定义了标准的资源适配器和依附于连接、事务、安全管理的合同,所以应用服务器将以标准和统一的方式插入各种企业信息系统,包括ERP(如SAP R/3),主框架事务处理系统和数据库系统。
因为EJB容器完全负责EJB组件的生命期、并发处理、资源访问、安全等等,所以与容器本身的锁定和并发管理相冲突的可能性就需要消除,许多限制都需要使用来填上潜在的安全漏洞。除了与EJB容器责任与安全冲突的问题,EJB组件还意味着仅仅聚焦于商务逻辑,它依赖于EJB容器所提供的服务而不是自己来直接解决底层的系统层的问题。
可能的问题
通常,EJB组件在容器之间的移植不可避免地与如下问题相关:
1.它需要依靠的受限制的特点在特定EJB容器中没有得到加强。
2.它需要依靠的非标准的服务从容器中可获得。
为了保证EJB组件的可移植性和一致的行为,你应该使用一个具有与Java2平台安全策略集相一致的策略集的容器来测试EJB组件,并且其加强了前述的编程限制。
总结
EJB组件开发者应该知道这些推荐的关于EJB组件的编程限制,明白它们的重要性,并且从组件的稳定性和可移植性利益方面考虑来遵循它们。因为这些编程限制能阻止你使用标准的Java语言的特点,违背了这些编程限制在编译时不会知道,并且加强这些限制也不是EJB容器的责任。所有这些原因都使你应很小心地遵守这些编程限制,这些限制在组件的合同中已经成为了一个条款,并且它们对于建造可靠的、可移植的组件是非常重要的。
<
小小豆叮
有关BMP和CMP的使用
02:12
EJB 1.1规范中的CMP只能对同一数据源的同一个表操作,EJB 2.0规范草案对CMP做了很大的改进,可以完成多表关联操作,我们以后的版本会支持这一点。
在目前的情况下,如果要完成多表关联操作,比较简单的方法是使用BMP,也就是说你自己编写SQL语句完成对数据库的更新。
用CMP同样也可以做到这一点,但需要你仔细设计EJB,将数据库表上关联体现到EJB之间对象的关联上。例如客户调用了B1的某个方法时,B1再去调用与它相关联的其他Bean的方法,并且这些方法都组织成一个单独的事务,由于每个Bean都更新了和它相对应的数据库表,这样当事务提交时就完成了多表关联操作。
<
小小豆叮
Servlet开发初步
02:12
Servlet是Java语言在WEB服务器端的一种应用技术,未来Servlet将可能彻底取代CGI。本讲座将具体介绍Servlet的概念、开发、调试以及相应的应用实例。
一、什么是Servlet?
·Servlet是一种独立于平台和协议的服务器端的Java应用程序,可以生成动态的Web页面。
·Servlet是位于Web 服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。
·Servlet与Web服务器的关系类似于Applet与Web浏览器的关系(这也是为什么Servlet技术被称为Servlet的原因),我们可以将Servlet想象成没有前端界面(faceless)的Applet。与Applet不同的是,由于Servlet运行在Web服务器端,因此它是一个可信赖的程序,不受到Java安全性的限制,拥有和普通Java应用程序一样的权限。
·Servlet是CGI Script的一种替代技术,由于Servlet在性能、可移植性、代码重用等方面比CGI具有显著的优势,因此在未来的技术发展过程中,Servlet有可能彻底取代CGI。
二、编写Servlet所需要的开发环境
进行Servlet开发所需要的基本环境是JSDK以及一个支持Servlet的Web服务器。
1.JSDK(Java Servlet Development Kit)
JSDK包含了编译Servlet应用程序所需要的Java类库以及相关的文档。对于利用Java 1.1进行开发的用户,必须安装JSDK。JSDK已经被集成进Java 1.2 Beta版中,因此如果利用Java 1.2进行开发,则不必安装JSDK。
JSDK可以在Javasoft公司的站点免费下载,其地址是
http://jserv.javasoft.com/products/java-server/downloads/index.html
2.支持Servlet的Web服务器
Servlet需要运行在支持Servlet的Web服务器上。目前支持Servlet的Web服务器首推SUN公司的Java Web Server。如果现有的Web服务器不支持Servlet,则可以利用一些第三方厂商的服务器增加件(add-ons)来使Web服务器支持Servlet,这其中Live Software公司(http://www.livesoftware.com)提供了一种称为JRun的产品,通过安装JRun的相应版本,可以使Microsoft IIS和Netscape Web Server支持Servlet。
三、开发Servlet的过程
本文将以编写一个简单的Servlet(我们称之为HelloServlet)为例说明开发Servlet的过程。
1.编写Servlet代码
Java Servlet API是一个标准的Java扩展程序包,包含两个Package∶javax.servlet和javax.servlet.http。对于想开发基于客户自定义协议的开发者,应该使用javax.servlet包中的类与界面;对于仅利用HTTP协议与客户端进行交互的开发者,则只需要使用javax.servlet.http包中的类与界面进行开发即可。
下面是一个典型的servlet的程序代码∶
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class HelloServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
String clientIPAddress = req.getRemoteAddr();
res.setContentType(″text/html″);
ServletOutputStream out = res.getOutputStream();
out.println(″〈html〉″);
out.println(″〈head〉〈title〉Hello World〈/title〉〈/head〉″);
out.println(″〈body〉″);
out.println(″〈h1〉Hello,You come from ″+clientIPAddress+″〈/h1〉″);
out.println(″〈/body〉〈/html〉″);
}
}
该servlet实现如下功能∶当用户通过浏览器访问该servlet时,该servlet向客户端浏览器返回一个HTML页面∶
Hello, You come from 192.168.0.11
其中192.168.0.11是客户端IP地址。程序代码的要点如下∶
·基于HTTP协议的servlet必须引入javax.servlet和javax.servlet.http包;
·HelloServlet从类HttpServlet派生,HttpServlet是GenericServlet的一个派生类,通过GenericServlet实现了Servlet界面。HttpServlet为基于HTTP协议的servlet提供了基本的支持;
·service()方法是servlet程序的入口点,当用户从浏览器调用servlet时,servlet将进入该方法。service()包含两个参数,HttpServletRequest对象包含了客户端请求的信息,可以通过该参数取得客户端的一些信息(例如IP地址、浏览器类型等)以及HTTP请求类型(例如GET、HEAD、POST、PUT等);HttpServletResponse对象用于完成Servlet与客户端的交互,通过调用HttpServletResponse.getOutputStream()客户取得向客户端进行输出的输出流,向客户端发送HTML页面。
2.编译Servlet代码
利用JDK 1.1对Servlet代码进行编译(假设Web服务器采用Java Web Server),其命令行为:
c:\> javac -d c:\JavaWebServer\servlets HelloServlet.java
进行编译时必须保证JSDK的Java Servlet类已经包含在CLASSPTH中,上述命令将编译后的.class代码放置在Java Web Server的Servlets目录下(如果你使用其他的Web Server,需要将.class代码放置在该Web Server指定的目录下)。
3.将Servlet添加进Web Server
由于Servlet是通过Web Server进行调用的,因此必须将其在Web Server中进行注册,以便Web Server能够正确的找到Servlet代码。对于Java Web Server来说,它提供了一个系统管理Applet,通过该Applet对我们的HelloServlet进行注册(在这里,我们将HelloServlet命名为firstServelt)。
3.测试Servlet
现在可以对HelloServlet进行测试了,打开浏览器,键入
http://192.168.0.9/servlet/firstServlet
其中192.168.0.9是安装有Java Web Server的机器IP地址。
如果一切正常,在浏览器中将返回一个页面,输出“Hello,You come from 192.168.0.11”。其中192.168.0.11是运行浏览器的机器IP地址。
<
小小豆叮
如何从JAR和ZIP包中析取Java源文件
02:12
发信人:consultant(zz),信区:Java
标题:[转载]如何从JAR和ZIP包中析取Java源文件
发信站:网易虚拟社区(FriFeb1809:18:272000),转信
如何从JAR和ZIP包中析取Java源文件
录入/江湖小子
1999.08.07
摘要:
将Java源文件打包成JAR文件,可以减少下载时、增强安全性和易管理性。本文将讨论如何从JAR文件中提取Java源文件。
绝大多数Java程序员非常善于利用JAR文件的优势将各种包含Java解决方案的资源进行打包。面对JAR文件,人们询问的一个普遍问题是:“我何从一个JAR文件中
提取出一个图象文件?”。本文将回答这个问题,并且提供一个Java类,它可以容易地从一个JAR文件中析取出任意一个文件。
加载一个GIF文件
假设我们有一个包含一些.GIF图象文件的JAR文件,这些图象文件将在应用程序中被使用。以下是我们使用JarResources类从JAR文件中析取一个图象文件的代码:
JarResourcesjar=newJarResources("Images.jar");
Imagelogo=
Toolkit.getDefaultToolkit().createImage(jar.getResource("logo.gif");
上面代码的含义是:我们建立一个JavaResources对象,并将它初始化为一个包含我们感兴趣资源的JAR文件--Images.jar。然后我们使用JavaResources类的getResource()方法从logo.gif取出原始数据,然后调用AWT工具箱的createImage()方法从原始数据建立一个Image对象实例。
有关命名的注释
JarResource是一个相当容易理解的例子,它解释了如何使用Java1.1提供的各种功能处理JAR和ZIP压缩文档文件。
关于命名的的简单解释。Java对压缩文档的支持实际上起源于使用一般的ZIP压缩文档格式。因此,Java中实现压缩档案操作的类都被放入java.util.zip包中;这些
类一般以“Zip.”开始。但是Java升级到了1.1版以后,压缩文档的命名变得以Java为中心了。从此,我们所说的JAR压缩文档基本上还是zip文件。
代码具体的工作流程
JavaResources类中的一些重要字段用来跟踪和存储指定JAR文件的内容。
publicfinalclassJarResources{
publicbooleandebugOn=false;
privateHashtablehtSizes=newHashtable();
privateHashtablehtJarContents=newHashtable();
privateStringjarFileName;
在类进行实例化时设置JAR的文件名,然后调用init()方法完成所有的初始化工作。
publicJarResources(StringjarFileName){
this.jarFileName=jarFileName;
init();
}
现在,init()方法把指定JAR文件的全部内容装入到一个杂凑表(hashtable)中(杂凑表名可以从资源名进行访问)
init()是一个功能相当强大的方法,让我们来逐步理解它的功能。ZipFile类使我们基本上能访问JAR/Zip压缩文档的头部信息。这和一个文件系统的目录信息相似。在这里我们列出Zip文件的所有条目(entry),并且按照文档中的每个资源的尺寸创建htSizes杂凑表(hashtable)。
privatevoidinit(){
try{
ZipFilezf=newZipFile(jarFileName);
Enumeratione=zf.entries();
while(e.hasMoreElements()){
ZipEntryze=(ZipEntry)e.nextElement();
if(debugOn){
System.out.println(dumpZipEntry(ze));
}
htSizes.put(ze.getName(),newInteger((int)ze.getSize()));
}
zf.close();
下一步,我们使用ZipInputStream类访问压缩文档。ZipInputStream类完成了所有的工作以使我们能读出压缩文档中任意一个资源。我们从包含每个资源的压缩文档中读出准确数目的字节,并将它们存储在一个可由资源名访问的htJarContents杂凑表中。
FileInputStreamfis=newFileInputStream(jarFileName);
BufferedInputStreambis=newBufferedInputStream(fis);
ZipInputStreamzis=newZipInputStream(bis);
ZipEntryze=null;
while((ze=zis.getNextEntry())!=null){
if(ze.isDirectory()){
continue;
}
if(debugOn){
System.out.println(
"ze.getName()="+ze.getName()+","+"getSize()="+ze.getSize()
);
}
intsize=(int)ze.getSize();
//-1meansunknownsize.
if(size==-1){
size=((Integer)htSizes.get(ze.getName())).intValue();
}
byte[]b=newbyte[(int)size];
intrb=0;
intchunk=0;
while(((int)size-rb)>0){
chunk=zis.read(b,rb,(int)size-rb);
if(chunk==-1){
break;
}
rb+=chunk;
}
//addtointernalresourcehashtable
htJarContents.put(ze.getName(),b);
if(debugOn){
System.out.println(
ze.getName()+"rb="+rb+
",size="+size+
",csize="+ze.getCompressedSize()
);
}
}
}catch(NullPointerExceptione){
System.out.println("done.");
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
}
注意,用来确定每个资源的名字是压缩文档中资源的实际路径名,而不是包(package)中类的名字。也就是说,java.util.zip包中的ZipEntry类的名字应为“java/util
/zip.ZipEntry”,而不是"java.util.zip.ZipEntry"。
这段代码最后一个重要部分是一个简单测试程序。这个测试程序能提取一个压缩文档名和资源名。它寻找压缩文档中的资源并报告运行成功与否。
publicstaticvoidmain(String[]args)throwsIOException{
if(args.length!=2){
System.err.println(
"usage:javaJarResources"
);
System.exit(1);
}
JarResourcesjr=newJarResources(args[0]);
byte[]buff=jr.getResource(args[1]);
if(buff==null){
System.out.println("Couldnotfind"+args[1]+".");
}else{
System.out.println("Found"+args[1]+"(length="+buff.length+").");
}
}
}//EndofJarResourcesclass.
现在你知道怎么做了。这个易于使用的类隐藏了所有使用压缩文档中的资源的复杂实现细节。
结论
如果你急于想知道怎样从一个JAR压缩文档中解出一个图象,那么现在你已拥有了一种方法。你不仅能处理和JAR文件相关的图象,而且你还能使用这篇文章提供了新类,在JAR中的任何资源上表演你的解压魔术。
--
有求皆苦,无欲则刚
※修改:.consultant于Feb1809:20:18修改本文.[FROM:210.78.138.191]
※来源:.网易虚拟社区http://club.netease.com.[FROM:210.78.138.191]
<
小小豆叮
jsp的安装,何志强篇
02:11
jsp的安装,何志强篇
版本:1.0
作者:何志强(hhzqq@sina.com)
时间:2000.03.16
摘自:http://go.163.com/~laoda/(小滕网络实验室)
注:所有软件,本站均有,欢迎到JAVA>>JAVA工具下载。
本文只讨论如何在Windows NT 4.0上安装Apache+Servlet+jsp。
本文中的配置情况如下:
Windows NT 4.0
jdk1_2_2-001-win.exe
apache_1_3_12_win32.exe
ApacheModuleJServ.dll
tomcat.zip(3.0)
一、软件下载
JDK
http://java.sun.com/products/jdk/1.2/
Apache Server
http://www.apache.org/dist/binaries/win32/apache_1_3_12_win32.exe
Apache JServ
http://jakarta.apache.org/builds/tomcat/release/v3.0/win32/ApacheModuleJServ.dll
Tomcat
http://jakarta.apache.org/builds/tomcat/release/v3.0/tomcat.zip
二、软件安装
(一)JDK
1、双击jdk1_2_2-001-win.exe文件进行安装,使用缺省配置进行安装,JDK的缺省安装目录为
C:\\jdk1.2.2,JRE的缺省安装目录为C:\\Program Files\\JavaSoft\\JRE\\1.2;
2、重启计算机;
3、更新下列环境变量:把C:\\jdk1.2.2\\bin目录追加到PATH中;把
.;C:\\jdk1.2.2\\lib\\tools.jar;C:\\jdk1.2.2\\lib\\dt.jar加入到CLASSPATH中。更新
方法:控制面板->系统->环境->系统变量;
4、测试applet:
1)打开command窗口;
2)切换到C:\\jdk1.2.2\\demo\\applets\\TicTacToe目录;
3)运行appletviewer example1.htm;
4)一切正常;
5、测试application:
1)在我的D:\\HZQ\\Java目录下创建一个test.java文件,内容如下:
public class test
{
public static void main(String arc[]){
System.out.println(\"JDK安装成功了,朋友!\");
}
};
2)打开command窗口;
3)切换到D:\\HZQ\\Java目录;
4)运行javac test.java进行编译;
5)运行java test运行这个程序;
6)一切正常。
(二)Apache Server
1、双击apache_1_3_12_win32.exe文件进行安装,使用缺省配置进行安装,缺省安装目录为
C:\\Program Files\\Apache Group\\Apache,我用$APACHE_ROOT来代替安装目的地;
2、修改$APACHE_ROOT\\conf\\httpd.conf:
1)PORT
设置Apache Web Server运行时使用的端口号,由于我这里的IIS也在运行,IIS使
用了80端口号,于是我把它改成Port 8080,以后在浏览器上输入
http://localhost:8080/才能访问到Apache Web Server服务器,输入
http://localhost/时能访问到IIS,这样两个Web Server都可以用了哦,我用
$APACHE_PORT标识;
2)DocumentRoot
设置文档根目录,当您在浏览器上输入http://localhost:$APACHE_PORT/时,服
务器会从文档根目录读取数据,由于我的程序都放在D:\\HZQ下,所以我把
DocumentRoot设置为D:/HZQ;
3、Apache安装完后在\"开始->程序\"菜单组中多了Apache Web Server菜单组,运行其中的
Install Apache as a service,这样的话在\"开始->设置->控制面板->服务\"中就
多了一个名为Apache的服务,您可以用它来启动或停止Apache服务;
4、打开\"开始->设置->控制面板->服务\",选中Apache这个服务,按\"开始\"启动Apache
服务;
5、我在浏览器上输入http://localhost:$APACHE_PORT/,就看到了D:\\HZQ下的所有文件,
这时表明Apache服务已经安装成功。
(三)Apache JServ
1、把ApacheModuleJServ.dll文件拷贝到$APACHE_ROOT\\modules目录下;
2、修改$APACHE_ROOT\\conf\\httpd.conf:
在该文件中加上LoadModule jserv_module modules/ApacheModuleJServ.dll以
启动Apache JServ;
3、重新启动Apache服务。
(四)Tomcat
1、用WinZIP等解压缩软件把tomcat.zip解压缩到一个目录下,我把它解压缩到C:\\,它会自动
创建tomcat子目录,这样在C盘就多了一个目录C:/tomcat,我用$TOMCAT标识;
2、打开$APACHE_ROOT\\conf\\httpd.conf文件,在该文件最后加上类似这样一句话:
Include $TOMCAT/etc/tomcat.conf
在我这里为Include C:/tomcat/etc/tomcat.conf;
3、修改Tomcat运行的端口号,注意Tomcat自己有一个独立的HTTP服务器,它必须使用一个还未
被使用的端口号,我这里的8081还未被占用,我用$TOMCAT_PORT标识,分配给Tomcat:
1)打开$TOMCAT/server.xml;
2)修改ContextManager:
4、双击$TOMCAT目录下的startup.bat来启动Tomcat;
5、在浏览器上输入http://localhost:$TOMCAT_PORT/,能看到Tomcat Version 3.0这一页
就表示Tomcat安装成功了;
6、在浏览器上输入http://localhost:$APACHE_PORT/examples/servlets/,能看到
Servlet Examples with Code这一页就表示Apache+Servlet也成功了;
7、在浏览器上输入http://localhost:$APACHE_PORT/examples/jsp/,能看到jsp Samples
这一页就表示Apache+jsp也成功了。
注:所有软件,本站均有,欢迎到JAVA>>JAVA工具下载。
<
小小豆叮
在已投产的J2EE应用服务器中,BEA稳居市场第一
02:11
2001年9月,META Group发布了其针对基础结构软件市场所做的调查报告,该报告显示,在已投产的J2EE应用服务器市场中,BEA份额为37%,比第二名高出近70%。
该项调查的对象包括来自《财富》2000强企业中的200多位IT决策人士,主要是负责选择和部署J2EE应用服务器的经理和主管人员。调查结果显示:在已投产的J2EE应用服务器方面,BEA份额为37%,IBM为22%;在部署EJB应用方面,BEA为52%,IBM为14%。
META Group调查的目的是确定哪些J2EE应用服务器被实际投入使用。此外,META Group还专门调查了采用J2EE应用服务器开发EJB应用的情况。由于EJB应用用于复杂的关键任务应用,它为业务逻辑、交易管理以及对大规模企业应用至关重要的数据库访问提供了基本的基础结构,因此,EJB部署是相当重要的。
META集团应用开发战略部项目总监托马斯Ÿ墨菲说:“META Group发现用户所喜欢的电子商务平台是既能够解决当前的主要问题,又能为增长创造商机,并便于调整。我们认为,随着厂商努力地加强各自产品的功能和性能,应用服务器的功能将不断地扩展,最终会影响整个电子商务环境。”
BEA高级副总裁托德·尼尔森说:“有许多研究机构采用许可证收入估价和服务收入拆分的方法来衡量应用服务器市场份额,这样的结果不能准确地反映市场实际状况。META Group针对实际用于关键任务应用中的应用服务器进行调查,是找到了问题的核心。其结果表明市场对BEA的欢迎程度,这个来自第三方的结果也验证了我们11000家用户和2100家合作伙伴的声音:BEA是企业级电子商务基础结构方面最受欢迎的选择。”
META Group调查了《财富》2000强中的企业。他们通过电话,直接与中高层IT决策人对话。在参加调查的公司中,有53%的美国国内企业,其余为在美的跨国企业。
META集团是全球领先的研究和咨询公司,专注于信息技术和业务变革策略方面的研究。META Group 通过其客观的、连续的指导,帮助各种组织更快、更有效地创新。公司网址:www.metagroup.com。
<
小小豆叮
如何用Java实现Web服务器
02:10
一、HTTP协议的作用原理
WWW是以Internet作为传输媒介的一个应用系统,WWW网上最基本的传输单位是Web网页。WWW的工作基于客户机/服务器计算模型,由Web 浏览器(客户机)和Web服务器(服务器)构成,两者之间采用超文本传送协议(HTTP)进行通信。HTTP协议是基于TCP/IP协议之上的协议,是Web浏览器和Web服务器之间的应用层协议,是通用的、无状态的、面向对象的协议。HTTP协议的作用原理包括四个步骤:
(1) 连接:Web浏览器与Web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。
(2) 请求:Web浏览器通过socket向Web服务器提交请求。HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命令的格式为:
GET 路径/文件名 HTTP/1.0
文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。
(3) 应答:Web浏览器提交请求后,通过HTTP协议传送给Web服务器。Web服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面。
例:假设客户机与www.mycompany.com:8080/mydir/index.html建立了连接,就会发送GET命令:GET /mydir/index.html HTTP/1.0。主机名为www.mycompany.com的Web服务器从它的文档空间中搜索子目录mydir的文件index.html。如果找到该文件,Web服务器把该文件内容传送给相应的Web浏览器。
为了告知 Web浏览器传送内容的类型,Web服务器首先传送一些HTTP头信息,然后传送具体内容(即HTTP体信息),HTTP头信息和HTTP体信息之间用一个空行分开。
常用的HTTP头信息有:
① HTTP 1.0 200 OK
这是Web服务器应答的第一行,列出服务器正在运行的HTTP版本号和应答代码。代码“200 OK”表示请求完成。
② MIME_Version:1.0
它指示MIME类型的版本。
③ content_type:类型
这个头信息非常重要,它指示HTTP体信息的MIME类型。如:content_type:text/html指示传送的数据是HTML文档。
④ content_length:长度值
它指示HTTP体信息的长度(字节)。
(4) 关闭连接:当应答结束后,Web浏览器与Web服务器必须断开,以保证其它Web浏览器能够与Web服务器建立连接。
二、Java实现Web服务器功能的程序设计
根据上述HTTP协议的作用原理,实现GET请求的Web服务器程序的方法如下:
(1) 创建ServerSocket类对象,监听端口8080。这是为了区别于HTTP的标准TCP/IP端口80而取的;
(2) 等待、接受客户机连接到端口8080,得到与客户机连接的socket;
(3) 创建与socket字相关联的输入流instream和输出流outstream;
(4) 从与socket关联的输入流instream中读取一行客户机提交的请求信息,请求信息的格式为:GET 路径/文件名 HTTP/1.0
(5) 从请求信息中获取请求类型。如果请求类型是GET,则从请求信息中获取所访问的HTML文件名。没有HTML文件名时,则以index.html作为文件名;
(6) 如果HTML文件存在,则打开HTML文件,把HTTP头信息和HTML文件内容通过socket传回给Web浏览器,然后关闭文件。否则发送错误信息给Web浏览器;
(7) 关闭与相应Web浏览器连接的socket字。
下面的程序是根据上述方法编写的、可实现多线程的Web服务器,以保证多个客户机能同时与该Web服务器连接。
程序1:WebServer.java文件
//WebServer.java 用JAVA编写Web服务器
import java.io.*;
import java.net.*;
public class WebServer {
public static void main(String args[]) {
int i=1, PORT=8080;
ServerSocket server=null;
Socket client=null;
try {
server=new ServerSocket(PORT);
System.out.println("Web Server is listening on port "+server.getLocalPort());
for (;;) {
client=server.accept(); //接受客户机的连接请求
new ConnectionThread(client,i).start();
i++;
}
} catch (Exception e) {System.out.println(e);}
}
}
/* ConnnectionThread类完成与一个Web浏览器的通信 */
class ConnectionThread extends Thread {
Socket client; //连接Web浏览器的socket字
int counter; //计数器
public ConnectionThread(Socket cl,int c) {
client=cl;
counter=c;
}
public void run() //线程体
{
try {
String destIP=client.getInetAddress().toString(); //客户机IP地址
int destport=client.getPort(); //客户机端口号
System.out.println("Connection "+counter+":connected to "+destIP+" on port "+destport+".");
PrintStream outstream=new PrintStream(client.getOutputStream());
DataInputStream instream=new DataInputStream(client.getInputStream());
String inline=instream.readLine(); //读取Web浏览器提交的请求信息
System.out.println("Received:"+inline);
if (getrequest(inline)) { //如果是GET请求
String filename=getfilename(inline);
File file=new File(filename);
if (file.exists()) { //若文件存在,则将文件送给Web浏览器
System.out.println(filename+" requested.");
outstream.println("HTTP/1.0 200 OK");
outstream.println("MIME_version:1.0");
outstream.println("Content_Type:text/html");
int len=(int)file.length();
outstream.println("Content_Length:"+len);
outstream.println("");
sendfile(outstream,file); //发送文件
outstream.flush();
} else { //文件不存在时
String notfound="
Error 404-file not found
";
outstream.println("HTTP/1.0 404 no found");
outstream.println("Content_Type:text/html");
outstream.println("Content_Length:"+notfound.length()+2);
outstream.println("");
outstream.println(notfound);
outstream.flush();
}
}
long m1=1;
while (m1<11100000) {m1++;} //延时
client.close();
} catch (IOException e) {
System.out.println("Exception:"+e);
}
}
/* 获取请求类型是否为“GET” */
boolean getrequest(String s) {
if (s.length()>0)
{
if (s.substring(0,3).equalsIgnoreCase("GET")) return true;
}
return false;
}
/* 获取要访问的文件名 */
String getfilename(String s) {
String f=s.substring(s.indexOf(' ')+1);
f=f.substring(0,f.indexOf(' '));
try {
if (f.charAt(0)=='/')
f=f.substring(1);
} catch (StringIndexOutOfBoundsException e) {
System.out.println("Exception:"+e);
}
if (f.equals("")) f="index.html";
return f;
}
/*把指定文件发送给Web浏览器 */
void sendfile(PrintStream outs,File file) {
try {
DataInputStream in=new DataInputStream(new FileInputStream(file));
int len=(int)file.length();
byte buf[]=new byte[len];
in.readFully(buf);
outs.write(buf,0,len);
outs.flush();
in.close();
} catch (Exception e) {
System.out.println("Error retrieving file.");
System.exit(1);
}
}
}
程序中的ConnectionThread线程子类用来分析一个Web浏览器提交的请求,并将应答信息传回给Web浏览器。其中,getrequest()方法用来检测客户的请求是否为“GET”;getfilename(s)方法是从客户请求信息s中获取要访问的HTML文件名;sendfile()方法把指定文件内容通过socket传回给Web浏览器。
对上述程序的getrequest()方法和相关部分作修改,也能对POST请求进行处理。
三、运行实例
为了测试上述程序的正确性,将编译后的WebServer.class、ConnectionThread.class和下面的index.html文件置于网络的某台主机的同一目录中(如:主机NT40SRV的C:\JWEB目录)。
程序2:index.html文件
这是用JAVA写出的WEB服务器主页
1998年8月28日
--------------------------------------------------------------------------------
首先在该主机上用java命令运行WebServer.class:
C:\jweb>java webserver
然后在客户机运行浏览器软件,在URL处输入WebServer程序所属的URL地址(如:http://nt40srv:8080/index.html),就在浏览器窗口显示出指定的HTML文档。
注意,不能缺省端口号8080,如缺省,则运行该主机的正常WEB服务器。
说明,不具备网络条件的可在安装了Windows 95的单机上进行测试,方法是用localhost或127.0.0.1代替URL地址的域名部分,即URL地址为http://localhost:8080。
<
小小豆叮
对JAVA语言的十个常见误解
02:10
JAVA 语 言 自 从 应 用 于 Internet, 迅 速 成 为 全 球 热 点。 它 的 平 台 无 关 性 仿 佛 成 为 解 决 互 易 操 作 性 和 可 移 植 性 的 灵 丹 妙 药。 然 而 对 于 JAVA 语 言 的 认 识 仍 有 不 少 误 解。
1.JAVA 是 HTML 的 扩 充
JAVA 是 一 个 编 程 语 言, HTML 是 一 个 页 面 描 述 语 言。 除 了 新 版 本 HTML 可 以 在 WEB 页 中 插 入 JA-VA 小 应 用 外, 它 们 之 间 没 有 任 何 相 同 之 处。
2.JAVA 是 一 种 很 容 易 学 会 的 编 程 语 言
没 有 一 种 和 JAVA 同 样 有 力 的 语 言 是 简 单 的。 当 它 写 演 示 小 程 序 时, 非 常 容 易; 但 当 它 真 正 做 一 些 重 要 的 工 作 时, 却 很 费 力。 JAVA 库 有 150 多 种 类 和 接 口, 虽 然 对 许 多 程 序 用 户 不 需 要 整 个 类 库, 但 每 个 项 目 都 需 要 类。
3.JAVA 是 一 个 简 单 的 编 程 环 境
一 些 人 喜 欢 只 用 vi 编 辑 器 和 dbx 调 试 器 来 写 程 序。 但 是 PC 和 MAC 编 程 者 已 习 惯 用 类 似 VB 风 格 的 拖 拉 式 表 格 设 计 工 具 或 集 成 开 发 环 境, 这 对 他 们 来 说 是 一 团 麻。 但 是, 一 旦 类 似 于 Syman-tec 公 司 的 Cafe 工 具 或 Borland 公 司 的 Latte 工 具 问 世, JAVA 的 开 发 时 间 就 会 大 幅 度 减 少。
4.JAVA 将 成 为 所 有 平 台 的 统 一 编 程 语 言
在 理 论 上, 这 是 可 能 的。 但 这 并 不 是 说 用 户 就 已 得 到 应 用 开 发 的 最 方 便 途 径。 JAVA 应 用 的 开 发 并 不 像 (可 能 永 远 不 像) 用 MFC 或 VB 开 发 的 Windows 应 用 那 样 好, 而 且 JAVA 提 供 的 图 形 开 发 工 具 功 能 也 太 简 单。 我 们 希 望 JAVA 类 库 很 快 变 丰 富。
5.JAVA 是 解 释 型 的, 它 对 于 特 殊 平 台 的 重 要 应 用 太 慢 了
许 多 编 程 者 在 类 似 于 用 户 界 面 方 面 花 了 太 多 的 时 间。 所 有 程 序, 无 论 用 什 么 语 言 编 写, 都 会 有 足 够 的 时 间 对 鼠 标 进 行 检 测。 当 然, 目 前 我 们 尚 未 用 当 前 的 JAVA 版 本 来 做 CPU 密 集 的 任 务。 但 是, 开 发 一 个 将 JAVA 中 间 码 转 换 成 本 地 码 的 编 译 器 是 非 常 容 易 的。
6. 所 有 的 JAVA 程 序 都 在 WEB 页 中 运 行
所 有 的 JAVA 小 程 序 (applet) 都 在 WEB 页 中 运 行。 在 浏 览 器 中 运 行 的 JAVA 程 序, 这 是 applet 的 定 义。 但 编 写 不 依 赖 于 WEB 浏 览 器 运 行 的 JAVA 程 序 是 可 能 的, 也 是 非 常 有 用 的。 这 些 程 序 完 全 可 以 移 植, 而 且 因 为 JAVA 比 C++ 更 方 便、 出 错 更 少, 它 是 编 程 的 一 个 很 好 的 选 择, 也 是 学 习 编 程 的 首 选 入 门 语 言。 一 旦 它 和 界 面 工 具 以 及 数 据 库 存 取 工 具 结 合, 将 更 有 竞 争 力。
7.JAVA 消 除 了 CGI 编 程 的 需 要
绝 对 不。 在 今 天 的 技 术 下, CGI 仍 是 ap-plet 和 服 务 器 之 间 最 方 便 的 通 讯 手 段。 服 务 器 仍 将 需 要 CGI 语 言 来 处 理 applet 发 送 的 信 息。 当 然, 用 户 可 以 用 JAVA 语 言 来 写 CGI, 如 同 Perl 或 C 那 样 简 单。
8.JAVA 将 彻 底 改 变 客 户 / 服 务 器 计 算
这 是 可 能 的。 SUN 公 司 已 公 布 了 各 种 数 据 库 类 库 的 计 划, 这 将 使 得 用 JAVA 开 发 客 户 / 服 务 器 应 用 和 用 JAVA 的 网 络 类 库 开 发 网 络 程 序 一 样 简 单。
9. 使 用 JAVA, 用 户 可 以 用 500 美 元 的 In ┐ ternet 设 备 来 代 替 现 在 的 计 算 机
这 种 认 为 人 们 会 放 弃 功 能 丰 富、 使 用 方 便 的 台 式 机 来 追 求 没 有 外 存 且 功 能 有 限 的 机 器, 是 完 全 不 合 理 的。 但 是 Internet 设 备 可 以 作 为 台 式 机 的 便 携 式 助 手。 如 果 价 格 合 理, 用 户 当 然 愿 意 用 一 台 Internet 浏 览 器 在 用 餐 时 自 由 地 选 择 阅 读 新 闻。 这 就 是 JAVA 的 魅 力。
10.JAVA 将 允 许 放 弃 基 于 部 件 的 计 算 模 式
当 人 们 谈 到 部 件, 有 许 多 不 同 的 含 义。 就 可 视 控 制 (Visual Control), 例 如 能 插 入 GUI 程 序 的 OCX 部 件, JAVA 还 没 有 设 定 一 个 标 准。 就 使 用 CORBA 接 口 及 OpenDoc 和 分 布 计 算 模 型 进 行 合 作 的 能 力, 这 不 久 就 将 开 始。 目 前 网 上 已 有 CORBA 接 口 的 测 试 版。
<
小小豆叮
J2EE平台架构上开发CRM的技术过程控制
02:09
内容提要:
第一章:概述
第二章 CRM
2.1 CRM概述
2.2 CRM应用系统模块划分
2.3 CRM应用系统模块内在关系
2.4 CRM应用系统各模块的技术要求
第三章 J2EE
3.1 J2EE概述
3.2 J2EE组成部分
3.3 J2EE各组成部分在开发CRM应用系统中的脚色
3.4 J2EE各技术实现CRM应用系统的特点
第四章:J2EE平台架构开发CRM的内容
第五章:技术层面控制J2EE平台架构开发CRM的过程
第六章:CRM应用系统各个模块的具体技术实现
第七章:国内CRM系统目前存在的问题以及采用J2EE技术进行的解决方案
第一章 概述
本文阐述了关于在J2EE平台上开发CRM应用系统的各方面内容,包括高辉本人对于CRM系统的理解,利用J2EE平台开发过程中要注意的一些技术深层的问题,开发分析中要注意的原则等等。这些都是作者在实际的工作中通过经验与教训所得来的。在工作中,我深刻的体会到系统分析员的重要性,尤其是对于以组件为主要开发对象的工程项目,系统分析员的技术与业务素质对于整个项目的成功与否起着非常关键性的作用。
需要说明的是,这并非作者工作文档,而仅仅是一篇分享经验与教训的交流文档,因此,其中关于一些涉及到具体的系统设计问题,我仅仅写了标题,敬请谅解。
第二章 CRM
2.1 CRM概述
科学技术在不断的进步,市场竞争日益激烈,对于企业来说,越来越强烈的感觉到客户资源是他获胜的最重要的资源之一:首先企业竞争的优势不仅仅是产品本身,先进的服务手段已成为关键;现代竞争其实就是客户的全面争夺而客户对企业的信任程度往往是从其消费过程中所得到的体验,如何做到最大程度的满足客户是非常重要的内容,因此,客户关系管理系统(CRM)应运而生,并成为近年来西方市场的热点和大买点。
实际上,CRM就是企业与客户的一种一对一的需求关系管理。这样,对待客户的视角就从过去的部门级别提升到了企业的层次,各个部门共享客户资源,以一个统一的对外接口来与客户交流,因此,这就要求能够将与客户通过各种方式如传真、邮件、电话、网页等交流所获得的所有信息有机的整合。
在设计CRM应用系统的过程中,我们首先要注意数据结构的格式:CRM应用系统的实施关键是以客户为数据结构的核心,这其中包括客户的基本信息、客户所购买的商品列表、客户抱怨、客户建议、客户服务记录、客户潜在需求、客户对企业的忠诚度等等。这样设计的原因一是使CRM应用系统有一个对于企业易于理解,易于操作的用户接口,二是对于CRM应用系统的设计开发可以比较轻易地拓展,具有良好的开发接口与开发弹性,对于项目负责人、系统分析员能够更加易于控制整个的开发过程,减少项目开发的风险。
另外,我们仔细研究过国内的数家公司的CRM应用系统的产品,从中学到了很多的东西,但同时也看到了这些产品的不足,在本文的后面部分我们将提到,我们发现,造成这种不足的原因在很大程度上是因为技术的原因,因此,经过反复的论证,我们最终还是选择了在我熟悉的J2EE平台上进行开发,这样就很大程度上克服了那些不足。
2.2 CRM应用系统模块划分
CRM应用系统主要由市场管理(Marketing)、销售管理(Sales)、服务管理(Service)、呼叫中心(Call Center)、电子商务(E_Business)五部分组成。
市场管理:提供易于使用的界面与工具,使操作人员能够彻底的分析市场、客户,策划和跟踪市场策略,分析竞争对手的市场策略等等,以便更加有效的拓展市场。在这个模块中,通过客户资料中的诸如地域、消费层次,消费习惯与方式、潜在需求、忠诚度、已购买产品列表等等有价值的信息来从不同的角度彻底的进行市场的策略分析,同时还可以评估和跟踪目前已经进行或者正在进行的营销策略,以及通过对自己和竞争对手的数据进行详细的分析,策划更加有效的销售策略。
销售管理:管理用户信息、商业机会以及销售渠道等等各方面的内容,从而能够使销售人员可以不受地域限制及时掌握资源以及企业的最新的价格信息,并可以向客户提供最新的和最感兴趣的商品列表以及价格信息。本模块包括机会、账户、合同等的管理,销售队伍组成、销售队伍成员以及资源重新调配的管理,有效跟踪销售业绩,同时提供个体的销售方式与过程参考,灵活进行产品配置、报价、打折、生成销售订单等。另外,本模块还应该和电子商务模块整合,以便达到多方位、多层次的销售,同时减少销售成本。
服务管理:本模块通过动态建立知识库,使客户服务代表能够有效的提高服务质量,增加客户的满意程度,并且捕捉和跟踪服务中出现的商业机会、产品质量信息、客户需求等等,并能够适时的向客户建议其他的产品和服务。
呼叫中心:呼叫中心是实施CRM应用系统的重要的组成部分,他实际上是将销售模块和服务模块进行了一个高度的集成,使一般的业务代表就可以进行实时的销售和服务。它通过管理账户、合同等等信息,并通过知识库的支持,就可以最大程度的满足客户的多方面的需求。呼叫中心提供当今最全面的计算机电话集成技术(CTI),通过对已拨号码识别服务(DNIS),自动号吗识别(ANI),交互式语音应答系统(IVR)得全面支持,通过采用系统预制的计算机电话集成技术,可以在用户拨叫的过程中业务代表已经可以获得客户的资料,就灵活的进行业务处理。
电子商务:电子商务模块是以上所有模块的一个逻辑集成,它提供了一个个性化、人性化、高度集成以及易于使用的用户界面,在这个用户界面上客户可以进行几乎所有的需求,诸如购买、付款、寻求服务支持、查询产品与服务目录、查询订单状态等等,甚至可以与呼叫中心联系在一起,最大程度的满足客户需求。
由于我们采用J2EE架构平台的开发方式,所以可以很容易的将我们在市场管理、销售管理、服务管理以及呼叫中心模块的内容集成到电子商务模块中,同时呼叫中心的大部分功能也可以并且也应该使用销售管理、服务管理模块中开发的组件。因此,这就要求我们在开发过程中,要充分利用J2EE平台的优点,组件的高度可重用性,减少开发的成本,加快开发的进度,并同时可以控制开发的质量。在实际的开发中,对于EJB 、Servlet的质量要求非常严格,项目负责人、系统分析员必须把握好质量。
2.3 CRM应用系统模块内在关系
在前面已经提到,在开发CRM应用系统的数据结构时一定要以客户信息为核心,一方面是为企业提供一个良好的易于操作的用户界面,另一方面是提高开发的可控性,减少开发成本与风险。以客户信息为核心,所有的模块的内容都是围绕客户,这样也使得应用系统的可拓展性大为提高,维护性加强。对于开发人员,尤其是系统分析员,所有的功能内容对于他来说都是一种“插件”,各个模块功能之间的耦合性大为降低,很显然会使整个的开发过程更加易于控制。
在各个模块的具体开发实施中,销售模块是基础,他负责管理账户、机会等信息,并且经过销售人员的销售活动的信息支持,对于客户的信息(如需求、购买行为等)的补充,就可以实时的给与市场人员以信息支持,从而随时把握销售策略,便于及时调整。从某种意义上讲,应该说销售管理、服务管理、呼叫中心以及电子商务这四个模块都是作为市场模块的信息支持,同时反过来,市场管理策略也给与其余企业活动以策略支持。要实现这一点,就必须在数据结构地设计上以客户信息为核心数据。
CRM应用系统各个模块之间的关系在企业业务上关系非常紧密,但是作为一个非常庞大和复杂的系统,我们不能按照一般传统的软件工程观念,在各个模块之间通过接口通信,这样会带来应用系统在开发、扩充以及维护方面等很多的问题。正确合理的方案是将客户视做一个对象,将客户资源作为操作的核心。
2.4 CRM应用系统各模块的技术要求
基于客户对于CRM应用系统的实际需求以及考虑到系统的未来拓展性、可维护性,CRM应用系统各个模块中除了呼叫中心可以做成客户/服务器(C/S)体系模式之外,其他模块都应该做成瘦客户端(浏览器)/服务器(B/S)模式。经过了反复的论证并且通过与别的技术方式的比较,我们最终确定采用在J2EE平台上开发CRM应用系统的技术方案。一方面是因为作为一种比较成熟的技术规范,相对于微软的.NET来说,它的开发要更加安全、成本更低,另一方面,我从很久就一直跟踪分布式开发的技术,对于J2EE的开发非常熟悉。(当时还没有相关的中文版书籍)。因此,比较了几种开发平台之后,我们决定采用J2EE。在这种开发平台上,我们将业务逻辑抽象出来,写成组件,然后将其发布到服务器上,再通过前台程序的开发程序员开发前台界面,调用后台的商务逻辑。
市场管理、销售管理、服务管理之所以采用分布式的开发,一方面是满足业务人员的办公需要,可以不受地域的限制,随时随地地办公,另一方面也是为了降低开发的成本与维护成本。因为我们看到,在呼叫中心模块中有销售管理、服务管理等内容,同时电子商务模块中又有其余模块的商务逻辑,我们将其抽象出来,一是组件复用,二是减少开发工作量同时减少风险。
第三章:J2EE
3.1 J2EE概述
任何一个有经验的Java平台开发人员,都会知道这个平台具有非常强大的功能和非常高的综合程度,并且发展非常迅速。Java平台的许多应用程序接口(API)为各种应用程序设计和系统级别程序设计提供了丰富的功能。J2EE是一种技术规范,他给开发人员提供了一种工作平台,它定义了整个标准的应用开发体系结构和一个部署环境,在这个体系结构中,应用开发者的注意力集中在封装商业逻辑和商业规则上,一切与基础结构服务相关的问题以及底层分配问题都由应用程序容器或者服务器来处理。甚至,从属于事务、持久化、安全等等方面的应用组件的运行时属性都可以使用高度灵活的声明方法在部署环境中定制(一般采用XML)。这个平台提供了一个简化的开发模型,它具有工业强度的可拓展性,支持合理的集成和灵活的部署,与开发商和应用服务器无关。
3.2 J2EE组成部分
对于开发人员来说,J2EE平台提供给他们的就是三种,Jsp、Servlet、EJB这三种开发方式。
Jsp
Jsp其实是一种高层的Servlet。他与以往的其他网页编写脚本有很大的相似性,但是只是在执行时有一些不同。Jsp引擎将它和它所在的HTML文件一起合成Servlet的代码,然后它的执行就和Servlet的一样了:先编译成.class文件,然后由支持java虚拟机的服务器来执行,然后输出结果。
我们在使用Jsp中可以使用JavaBean来进行更加灵活的处理。
Servlet
Servlet可以被看作是服务器端的applet,它通过ServletResponse以及ServletRequest这两个对象来输出和接收用户传递的参数,然后在内部的方法中执行操作,如访问数据库、访问别的Servlet方法、调用EJB等等,然后将处理结果返回给客户端。可以通过集成化的开发工具来进行开发。在一般的工具中都已经构建好一个框架,程序员只需要熟悉html标签以及熟悉一般的java语言就可以进行开发了。
EJB
EJB如果除去它的语言特点外,我想对于大多数有比较丰富编程经验的开发人员来说应该可以轻松理解,他非常类似于微软的DCOM。他有一个自己要存活要活动的一个容器,为了可以让客户进行透明调用,而不必关心位置,他还必须有一个本地和远程接口,同时还应该有一个相关的配置文件,以便告诉容器她要怎样的活法。对于开发人员来说,如果采用一种集成化的开发工具,如JBuilder,就可以大大减少工作量。在JBuilder中通过配置相关的服务器路径、容器信息,我们可以通过它的模板来完成一个EJB组件的开发以及分发,非常方便也非常简单。
在开发过程中,建议的开发方式是在会话bean内部调用实体bean,因为实体bean没有状态但是对数据库的亲和,而会话bean中有我们为了控制程序而需要的上下文信息,因此,我们可以结合这两种bean的所有优点,来比较轻松的进行开发。比如在会话bean中用实体bean进行数据库的访问同时会话bean用来保存客户的上下文信息。
3.3 J2EE各组成部分在开发CRM应用系统中的脚色
我们已经提到过,开发一个健壮的、可拓展的CRM应用系统中的各个模块,除了呼叫中心外我们都将采用浏览器/服务器模式。因此,下面的模式是除了呼叫中心模块之外的方式:
浏览器--------〉Jsp脚本文件--------调用---------〉Servlet------调用--------〉EJB------访问数据库---------〉处理返回。
其中Jsp属于前台开发人员进行的开发内容,也就是提供给客户的用户界面,要求是美观,使用性强,便于操作;
Servlet、EJB为后台开发人员开发的具有可以重用性的包含商务逻辑的组件,也就是说,他们主要是进行企业的商务逻辑的处理。要求是开发的程序一定要健壮,充分注意到业务逻辑的独立性与组合性。
在开发CRM系统时,前面已经说过,系统分析员自身对于J2EE技术的把握深度,对于CRM系统业务的理解程度将极大的决定了系统的成功与否。就是在做系统分析时一定要做到将功能完全细化到Servlet、EJB组件所封装的商务逻辑中去,并且要反复论证其合理性与独立性。
3.4 J2EE各技术实现CRM应用系统的特点
Jsp相对来说比较简单,但是在开发过程中系统分析员一定要注意尽可能少地将商务逻辑放到Jsp文件中,有几个原因,一是Jsp文件本身的可维护性比较差,尤其是如果不采用的方式的开发,将会极大的增加开发与维护成本。因此,在前台的Jsp开发中首先要划分出版面,然后将版面分割成不同的部分,用不同的被包含文件来最终组成用户界面。另外要注意的一点是某些与程序逻辑实现无关的动态内容最好放在数据库中,而不要放在文件中。所以在开发前台的Jsp文件时系统分析员要注意下面的几个问题:
1、划分版面的界面逻辑,用包含文件的方式给程序员确定开发代码;
2、尽量不将商务逻辑放在Jsp文件中,所有的业务处理都要调用后台的组件;
3、当涉及到的界面逻辑较多的时候,要给程序员设计JavaBean来进行处理,而不是在Jsp文件中直接嵌入java代码,否则会造成Jsp文件的可读性非常差,维护与调试异常困难。
Servlet作为在服务器后台进行处理的组件,除了业务上商务逻辑要独立、完整、可组合的、准确等的要求外,还有一个很重要的要求:就是线程安全性。显然,我们都知道Servlet相比通用网关接口CGI有着明显的优点就是可以维护一个线程池,不用每一次都要创建一个新的线程,但是可能很多程序员都会意识不到一个经常会遇到的问题:实例变量在所有的线程之间是共享的,并且如果存在着Servlet链互调时,就会发生数据错误。因此系统分析员一定要鼓励程序员多注意利用Java提供的方法(如声明自己的类实现了Runnable接口或者采用同步synchronized技术等)解决线程的问题,另外还要注意的是数据库的连接问题,因为如果频繁的访问数据库会造成数据库服务器的负担同时使客户端的回馈速度变慢,因此要注意利用预先分配连接并在释放以后能够回收的连接池。所以,在开发Servlet也要注意下面的3个问题:
1、鼓励程序员关注线程安全问题(如采用声明自己的类实现了Runnable接口或者采用同步synchronized技术等解决线程的问题);
2、数据库的访问要充分利用JDBC技术的预先分配连接并在释放以后能够回收的连接池;
3、鼓励系统分析员将商务逻辑划分成单个的独立的可通用的可重用的商务逻辑组件,在实际的程序中通过Servlet链来完成某项商务逻辑。
EJB实际上单就程序的写作方面要比Servlet简单的多,它使程序员只需要关心要实现的是甚麽就可以了,而不必关心事务的处理,底层的操作等等问题。但是也还是有一些编程方面的要求:
1、最好能够在程序中将所有的static字段都声明为final型的,这样可以保证多个实例出现时语义的不一致问题;
2、注意线程问题,同Servlet;
3、不使用文件系统。EJB组件可以通过环境命名上下文用一种标准的方法进行环境实体查询,基本上是不用文件系统。
4、禁用socket来进行监听和接收连接,或者用其进行多路发送。
5、不可能用awt函数来完成键盘的输入和输出,如果有的话,应该是向控制台输出控制信息,因为组件是用来在服务器端完成某一项商务逻辑的。
第四章:J2EE平台架构开发CRM的内容
本章的内容是一个非常大的部分,他所涵盖的就是具体的开发方案,其中包括使用案例图,运行图等等。因为某种原因,这儿就不写了,请谅解。
第五章:技术层面控制J2EE平台架构开发CRM的过程
在J2EE平台上开发CRM应用系统,是一个非常优秀的方案,一方面J2EE是一项比较成熟的技术规范,各大IT服务器、中间件厂商也都大力推崇并支持,尽管微软大力推出.NET,但是毕竟.NET是一项新的技术规范,如果在其上进行开发的话,风险显然要大得多,而J2EE目前却正是在走向成熟。
正像任何事情一样,在先进的J2EE平台上开发CRM应用系统必须要有一个良好的实施过程,在这个过程当中,有一个非常重要的脚色:系统分析员。系统分析员自身对于J2EE技术的掌握深度、对CRM应用系统的业务理解程度很大得影响了开发的过程,甚至可以毫不夸张的说,系统分析员自身的素质决定了开发的成功与否,这是一个非常关键的因素。 首先是系统分析员对于客户的需求的理解程度,只有深入的理解了客户的需求,才能够将商务逻辑很好的划分;其次是系统分析员的思维是否严密,是否严谨,是否具有很强的逻辑思维能力,因为这涉及到将商务逻辑进一步细化成独立的可重用的业务逻辑与使用逻辑。第三是其是否对于J2EE技术有着非常深的掌握,这是实施CRM的另外一个重要之处,因为在整个的开发过程中,其实重心就在服务器端组件的开发上,一个系统能否稳定,高效的运行,很大程度上取决于开发技术上是否规范与合理,而系统分析员在实施编码阶段的主要职责就是负责检查程序员的程序代码
在开发过程中另外一个注意的是开发人员的分工。在J2EE平台上的开发与一般的软件开发概念不同,它有着严格的分工:
1、系统分析员;
2、后台组件开发程序员(主要是Servlet与EJB);
3、后台服务器实施技术人员(主要负责组件的管理);
4、后台组件测试人员;
5、前台用户界面程序员(主要是jsp程序员+美工);
6、前台测试技术人员;
在实际的实施过程中,后台服务器实施技术人员可以充当后台组件测试人员的脚色,同样,前台用户界面程序员可以充当前台测试技术人员,因为他的页面中所包含的逻辑比较少。
总结一下,关键的几点:1、商务逻辑一定要划分的非常合理,原则是一个组件中应该只含有一种商务逻辑,一般的商务逻辑应该是通过几个组件的协同合作来实现的(如何划分,如何组合就是看系统分析员的功底了!)2、分工一定要明确,除了上面所列出的脚色充当之外,尽量避免前台程序员与后台程序员的脚色互换,否则很可能造成商务逻辑组件之间的耦合,而这是绝对不允许的,否则随着开发过程的进行,就会发现越来越难以控制应用的开发。所以在开发过程中一定要注意组件的商务逻辑的独立性与唯一性,系统分析员和项目负责人一定要严格把关,这一点非常非常重要。
第六章:CRM应用系统各个模块的具体技术实现
应用系统都是开发商基于对某项业务的深刻理解而产生的,不同的行业有不同的商务逻辑,即便是同一行业也有不同,因此,要根据客户的实际需求来做。但是,作为一个CRM系统她都有一个共同的框架,就是上面所提到的,因为,一套完整和实用的CRM系统可以看作是: 框架+具体商务逻辑。而框架部分则就是上面要求系统分析员所做的工作:将通用的商务逻辑抽象出来做成组件,以备复用。
本章应该是一个详细的模块设计,其中包括组件组合使用图,流程图以及其他的文档等等。出于和第四章同样的原因,请谅解。
第七章:国内CRM系统目前存在的问题以及采用J2EE技术进行的解决方案
我们研究过国内几家CRM系统,学习到很多的东西,但同时也发现一些问题,现在举几个例子:
1、 大而全,但是各个功能做的太过于简单,无法实用。
2、 缺乏集成能力,无法将网页、电邮、电话、传真等集成。
3、 没有与客户的互动渠道。
就这三个原因,是因为在整个的设计上偏离了以客户为中心的原则,没有将客户的需求详细、完整的划分成个体的、独立的功能组件,没有将各个功能做成是以客户为核心的插件,再加上如开发成本的压力等。而如果是采用J2EE,并且严格的按照合理划分的组件的方式来进行开发,就会解决或者避免或者减轻这些问题。比如,在开发完销售模块与服务模块的组件之后,电子商务模块基本上也就完成,只需要少许的其它组件就可以完成一个电子商务模块。
------------------------
Email:kouvin@21cn.com
Mobile:13925097814
<
小小豆叮
解决Java和Sql Server的汉字问题
02:08
用Jdbc-Odbc和Sql Server7.0打交道,查询和插入会出现掉汉字的情况。
要解决,很简单,去www.freetds.org下载freetds最新驱程0.5版。再也
不用把编码转来转去那么麻烦。
源程序如下:
String url = "jdbc:freetds:sqlserver://ServerName:port/DataBase";
String login = "UserName";
String password = "PassWord";
try
{
Class.forName("com.internetcds.jdbc.tds.Driver").newInstance();
Connection connection = DriverManager.getConnection(url,login,password);
Statement st = connection.createStatement();
ResultSet rs = st.executeQuery("select * from JoltData");
while (rs.next())
{
for(int j=1; j<=rs.getMetaData().getColumnCount(); j++)
{
System.out.print( rs.getObject(j)+"\t");
}
System.out.println();
}
st.close();
connection.close();
} catch(Exception e)
{
System.out.println(e.toString());
}
<
小小豆叮
JavaPCTM Software 之二
02:07
- 将瘦客户计算和JavaTM 技术的益处引至基于PC的设备
- JavaPC软件的主要特性
- 关于JavaPCTM 软件的常见问题解答
- 有关JavaPC软件的新闻与问题讨论
- 尝试一下JavaPC软件是容易的
JavaPC软件的主要特性
| 特性 | 益处 |
| 可在DOS上运行 | 可以访问大量的现有DOS设备驱动程序,以连接PC外围设备,如视频适配器(显示卡)、网络适配器(网卡)、键盘和指示设备等。 |
| JavaTM 通信API (javax.comm) | 为串行或并行接口设备(如扫描仪、条形码阅读器和打印机)提供了标准接口。 |
| DOS TSR API | 使用DOS TSR API,基于Java的应用程序可以访问实模式内存(real mode memory)和驱动程序,在DOS上所做的早期开发工作进一步发挥了作用。 |
| 特性 | 益处 |
| 支持与JDKTM1.1 API兼容的应用程序和applets | 交叉平台支持。减少了开发、支持和培训成本。 |
| 应用程序可从HTTP服务器或文件服务器下载。 | 降低了升级支持成本, 从而保证用户总是可以访问特定应用程序的最新版本。 |
| 应用程序高速缓存 | 应用程序一旦被下载后, 可以被缓存于每个用户的本地硬盘驱动器上, 从而可减少网络拥塞和应用程序的装载时间。 |
| 特性 | 益处 |
| 支持各种网络配置(从Stand Alone模式, 到NC模式)。 | 提供了灵活简便的集成。 |
| 集中化管理支持。应用程序、系统属性和用户简档(profiles)可被存储在网络并按命令下载。 | 大大减小了管理和支持成本。 |
| 自动升级。当在每一个具有驱动的客户上进行了初始安装后, JavaPC软件的新版本可从中央服务器动态部署。 | 减少了升级工作并减少了对任何安装了基地址(base)的大的客户(large client)的升级所用时间。 |
| 支持标准DHCP和HTTP服务器(Solaris、Microsoft Windows NT、Novell NetWare等)。 | 不需要另外购买硬件或软件就可以在标准网络环境中对JavaPC进行集中化管理。 |
| NetraTM j 服务器软件支持 | JavaPC 客户和JavaStationTM 系统都可以从相同的基于Web管理的用户接口进行管理。从而降低了混合(mixed)环境中的管理费用。 |
小小豆叮
JavaPCTM Software 之三
02:07
- 将瘦客户计算和JavaTM 技术的益处引至基于PC的设备
- JavaPC软件的主要特性
- 关于JavaPCTM 软件的常见问题解答
- 有关JavaPC软件的新闻与问题讨论
- 尝试一下JavaPC软件是容易的
关于JavaPCTM 软件的常见问题解答
- 一般问题
- 系统要求
- 应用程序支持
- JavaPCTM 软件和Microsoft Windows
<
小小豆叮
SUN:编写高级应用程序
02:07
新的JavaTM 虚拟机(VMs)具有能够提高性能的特点, 并且你可以使用许多工具来提高应用程序的性能或减小一般类文件的尺寸。这种Java虚拟机的特性和工具可使你在不改变应用程序、或对应用程序仅做很小改动的情况下,
提高应用程序的性能。
Java2与过去的版本相比, 性能已有很大提高, 其中包括更快的内存分配、类尺寸的减小、垃圾收集的改善、最新型的监控器和作为标准的内联JIT技术。当使用新的Java2虚拟机时,
你会看到这种性能的改善; 然而, 如果你能够理解速度是如何提高的, 你就能够调整你的应用程序, 以充分挖掘每一点性能潜力。
Java虚拟机的Java2版可在运行时自动内联简单方法。在一个未优化的Java虚拟机中,每调用一次新的方法,就创建一个新的堆栈帧(stack
frame)。创建一个新的堆栈帧需要一些额外的资源以及该栈的某些再映射(re-mapping),其结果将导致系统开销的少许增加。
由于方法内联可在你的程序中减少方法调用的次数,因而可提高性能。Java虚拟机内联代码内联了返回常数或仅访问内部域(internal fields)的方法。为了利用方法内联,你可以从以下两件事中选做其一;即:你可以使一个方法对虚拟机所要执行的内联看上去是有吸引力的,或者你可以手工内联一个方法,只要它不破坏你的对象模型。在这一语境中的手工内联意味着可以直接从一个方法中将代码移动到正在调用该方法的方法中。
下面的小例子演示了虚拟机的自动方法内联:
在当前状态,addCount方法对虚拟机中的内联探测器显得是没有吸引力的,因为addCount方法返回一个值。要发现该方法是否被内联:
如果你将addCount方法改变为不再返回一个值,则虚拟机可在运行时将其内联。为使内联代码更友好,应用下面的程序替换addCount方法:
在Java 2发布之前,同步的方法和对象总是引发一些额外的性能干扰,这是因为用来实现这种代码锁定的机制采用了一种全局监控器注册,它在某些区域仅仅是单线程的(如搜索现存监控器)。在新发布的Java
2中,每个线程都有一个监控器注册,从而消除了许多现存的性能瓶颈。
如果你曾经使用过其它锁定机制来避免同步方法的性能干扰,现在则有必要重访这些代码并考虑新的Java 2新型锁定技术。
在下面的为同步块创建监控器的例子中,你可以将速度提高40%。所用时间在采用JDK1.1.7和采用Sun Ultra 1上的Java 2时分别为14ms和10ms。
Java HotSpotTM虚拟机是Sun Microsystem公司的下一代虚拟机。虽然Java HotSpot 虚拟机所采用的规范与Java
2虚拟机所采用的规范相同,但它已被重新设计,并使用了最先进的技术,从而可在未来许多年内,能够为Java平台提供一个强大而可靠的性能引擎。Java
HotSpot虚拟机可提供:
用来提高应用程序性能的最简单的工具是Just-In-Time(JIT)实时编译器。JIT是一个可将Java字节码转换为本地机器码的代码生成器。由JIT调用的Java程序,其运行速度通常要比由解释程序执行字节码时的速度高得多。
JIT编译器首先是在Java开发工具包(JDKTM)1.1.6中作为一种性能更新出现的,而现在它是你在Java 2平台上使用Java解释程序命令时调用的标准工具。你可以使用Java虚拟机的-Djava.compiler=NONE
选项来使JIT编译器失效,这在JIT的末尾部分有更详细的阐述。
JIT编译器是如何工作的?
JIT编译器是作为一种依赖于平台的本地库提供的。如果JIT编译器库存在,则Java虚拟机将初始化Java本地接口(JNI)的本地代码分支以调用在该库中可获得的JIT函数,而不是调用在解释程序中的相应函数。
java.lang.Compiler 类被用来加载本地库并启动JIT编译器内的初始化。当Java虚拟机调用一个Java方法时,它使用在加载的类对象的方法块中所指定的调用(invoker)方法。Java虚拟机具有若干个调用者方法,例如,如果方法是同步的,或者它是一个本地方法,则将使用不同的调用者。JIT编译器使用它自己的调用者。Sun的产品可以为值ACC_MACHINE_COMPILED检查方法存取位以告知解释程序该方法的代码已被编译并存储在加载类中。
代码何时成为JIT编译的代码?
当一个方法被首次调用时,JIT编译器为该方法将方法块编译为本地代码,并将其存储在该方法的代码块中。
一旦代码被编译完成,在Sun平台上所使用的ACC_MACHINE_COMPILED的位则被设定。
我如何知道JIT编译器在做什么?
环境变量JIT_ARGS允许对Sun Solaris JIT编译器进行简单控制。trace 和 exclude(list)是两个有用的值。要从示例InlineMe中排除(exclude)方法并显示跟踪记录(trace),应将JIT_ARGS
做如下设定:
请注意内联方法(如String.length)是免除的。String.length 也是一个特殊的方法,它一般由Java解释程序编译为一个内部快捷字节码。当使用JIT编译器时,由Java解释程序提供的这些优化失效,从而可以使JIT编译器能够理解哪个方法正在被调用。
首先要记住的一点是,JIT编译器在第二次调用一个方法时,会获得大部分速度上的改善。JIT编译器的确是编译了整个方法,而不是对其进行逐行解释,逐行解释也是一种在运行一个可执行JIT的应用程序时用以改善性能的途径。这意味着如果代码仅被调用一次,你将不会看到太大的性能改善。JIT编译器也将忽略构造函数(class
constructor),所以,如果可能的话,应最少量地保留构造函数代码。
如果不能预先检查某些Java边界条件,JIT编译器也不能获得最大的性能改善,这些边界条件包括零指针(Null pointer)或边界外数组等异常。JIT编译器能够知道出现零指针异常的唯一途径是通过由操作系统所提供的信号。由于该信号来自操作系统,而不是来自Java虚拟机,因而你的程序会出现性能上的干扰。为了保证在用JIT运行一个应用程序时,能够获取最好的性能,应确保你的代码完全没有象零指针或边界外数组那样的异常。
如果你要以远程调试状态运行Java虚拟机,或者你要看到源代码行数而不是看到在你的Java栈跟踪中的标签(Compiled Code)的话,你可能需要使JIT编译器失效。要使JIT编译器失效,可在你调用解释器命令时为JIT编译器提供一个空白或无效名称。下面的例子演示了用javac命令将源代码编译为字节码、以及用两种形式的java命令在没有JIT编译器的情况下调用解释程序的过程。
其它一些可用的工具包括可用来减小一般Java类文件尺寸的工具。Java类文件包括一个被称作常数池(constant pool)的区域。常数池在某一个地方为类文件保持有一个字符串和其它信息的列表,以备引用。在常数池中可以获取的诸多信息之一是方法和字段的名称。
类文件引用在类中的一个字段作为对常数池中的一个条目的引用。这意味着只要引用保持相同,则无所谓在常数池中存储什么样的值。 一些工具利用这点将常数池中的字段名和方法名重写为缩短的名称。利用这一技术可以大大减小类文件的尺寸,从而使在网上下载类文件变得简单。
<
Java虚拟机的特性
方法内联
public class InlineMe {
int counter=0;
public void method1() {
for(int i=0;i<1000;i++)
addCount();
System.out.println("counter="+counter);
}
public int addCount() {
counter=counter+1;
return counter;
}
public static void main(String args[]) {
InlineMe im=new InlineMe();
im.method1();
}
}
java -Xrunhprof:cpu=times InlineMe 它生成一个java.hprof.txt输出文件。前十个方法类似下面的结果: CPU TIME (ms) BEGIN (total = 510) Thu Jan 28 16:56:15 1999 rank self accum count trace method 1 5.88% 5.88% 1 25 java/lang/Character. 2 3.92% 9.80% 5808 13 java/lang/String.charAt 3 3.92% 13.73% 1 33 sun/misc/Launcher$AppClassLoader.getPermissions 4 3.92% 17.65% 3 31 sun/misc/URLClassPath.getLoader 5 1.96% 19.61% 1 39 java/net/URLClassLoader.access$1 6 1.96% 21.57% 1000 46 InlineMe.addCount 7 1.96% 23.53% 1 21 sun/io/Converters.newConverter 8 1.96% 25.49% 1 17 sun/misc/Launcher$ExtClassLoader.getExtDirs 9 1.96% 27.45% 1 49 java/util/Stack.peek 10 1.96% 29.41% 1 24 sun/misc/Launcher.
public void addCount() {
counter=counter+1;
}
再次运行profiler:
java -Xrunhprof:cpu=times InlineMe
这次,java.hprof.txt的输出应该显得是不同的。
AddCount方法已经消失。它已被内联了!
CPU TIME (ms) BEGIN (total = 560) Thu Jan 28 16:57:02 1999
rank self accum count trace method
1 5.36% 5.36% 1 27 java/lang/Character.
2 3.57% 8.93% 1 23 java/lang/System.initializeSystemClass
3 3.57% 12.50% 2 47 java/io/PrintStream.
4 3.57% 16.07% 5808 15 java/lang/String.charAt
5 3.57% 19.64% 1 42 sun/net/www/protocol/file/Handler.openConnection
6 1.79% 21.43% 2 21 java/io/InputStreamReader.fill
7 1.79% 23.21% 1 54 java/lang/Thread.
8 1.79% 25.00% 1 39 java/io/PrintStream.write
9 1.79% 26.79% 1 40 java/util/jar/JarFile.getJarEntry
10 1.79% 28.57% 1 38 java/lang/Class.forName0
新型同步
class MyLock {
static Integer count=new Integer(5);
int test=0;
public void letslock() {
synchronized(count) {
test++;
}
}
}
public class LockTest {
public static void main(String args[]) {
MyLock ml=new MyLock();
long time = System.currentTimeMillis();
for(int i=0;i<5000;i++ ) {
ml.letslock();
}
System.out.println("Time taken="+
(System.currentTimeMillis()-time));
}
}
Java Hotspot
- 可以探测并加速性能关键性代码的实时动态优化技术。
- 为发挥线程的最大性能而设计的超快速线程同步。
- 可最快速获取的精确而可靠的垃圾收集器。
- 由于其简洁、高层次以及面向对象的设计,因而在可维护性和可扩展性方面的重要改进。
JIT(Just-In-Time)编译器
JIT编译器是如何工作的?
代码何时成为JIT编译的代码?
我如何知道JIT编译器在做什么?
Unix: export JIT_ARGS="trace exclude(InlineMe.addCount InlineMe.method1)" $ java InlineMe Initializing the JIT library ... DYNAMICALLY COMPILING java/lang/System.getProperty mb=0x63e74 DYNAMICALLY COMPILING java/util/Properties.getProperty mb=0x6de74 DYNAMICALLY COMPILING java/util/Hashtable.get mb=0x714ec DYNAMICALLY COMPILING java/lang/String.hashCode mb=0x44aec DYNAMICALLY COMPILING java/lang/String.equals mb=0x447f8 DYNAMICALLY COMPILING java/lang/String.valueOf mb=0x454c4 DYNAMICALLY COMPILING java/lang/String.toString mb=0x451d0 DYNAMICALLY COMPILING java/lang/StringBuffer. mb=0x7d690 >>>> Inlined java/lang/String.length (4)
如何使用JIT来发挥你的优势
javac MyClass.java
java -Djava.compiler=NONE MyClass
or
javac MyClass.java
java -Djava.compiler="" MyClass
第三方工具
小小豆叮
JavaPCTM Software 之四
02:06
- 将瘦客户计算和JavaTM 技术的益处引至基于PC的设备
- JavaPC软件的主要特性
- 关于JavaPCTM 软件的常见问题解答
- 有关JavaPC软件的新闻与问题讨论
- 尝试一下JavaPC软件是容易的
有关JavaPC软件的新闻与问题讨论
小小豆叮
安全的代价是什么?
02:06
Java的力量是不容置疑的,但安全问题也是开发者的关注焦点. 本文将权衡其益处与随之而来的危险.
如今,Internet正在显著的改变着我们的生活与工作方式.
即使那些最狂热的愤世嫉俗者也会承认,在不远的将来,我们的指尖将控制这个世界。然而,对于生活的各个方面,新的自由并不会毫无代价的就可获得。所以,随之而来的问题会让人相当头痛的。对于那些在网上冲浪的人和不断扩大网上业务的公司来说,安全已经成为他们的一个按语。在Internet上,只要有一扇开着的门,我们便要找到一把防止各种恶意攻击的钥匙。下面是一些很普遍的例子:
一种毁坏硬盘数据的病毒。.
一个落入恶人之手的信用卡号码.
被窃取的客户清单.
一些新的Internet执行模式正逐渐为人所关注:下载程序的时代已经到来。是的,从表面上看,理智的个人从未知的站点下载他们未见过的程序,使他们的系统有勿庸置疑的访问权限。
Java作为一种为Web而产生的语言,正冲在计算浪潮的最前沿。applet已被添加到计算机科学词汇中,这充分说明了Java的巨大成功。一个Java applet正如它听上去那样:一个从远程主机处下载,并在本地机器上执行的小程序。 这种新结构无疑会有很多功能,但安全问题也随之而来。
任何一个人如果在使用浏览器,并在浏览一个包含applet的网页,他便在使用Java。而且,applet会将你暴露在更加复杂的攻击之下,如:
黑客编写一个似乎是本地的、可信赖的applet。这种漏洞需要两个技巧:黑客要设法将程序放到用户的本地硬盘上(利用一般浏览器的特点:文件缓冲) ,并骗过浏览器使之以一个applet调入。
黑客利用Java DNS翻译处理的弱点:黑客设法骗过 runtime环境,使之认为他的系统是一个可信赖的主机。然后初始化一个突破牺牲品的防火墙网路连接。
若基于applet的计算已成为Internet生活的一个实事,那么重要的问题应该为:
好的安全保障由什么构成
对执行环境的依赖程度到底应多大?
充分的安全保障的成本有多大?
让我们从显而易见的问题开始:真正安全的系统是没有任何外部访问能力 (如电话线、 LAN连接等) 并在一封闭环境中保持锁定和警戒。显然,这样的系统对用户的价值相当有限。所以,安全问题归根到底是对一种危险的分析,是Internet访问带来的益处和疏漏导致的后果的权衡。但是,权衡危险必须建立在理解安全模型的基础之上。这对下载环境尤为重要:因为你不知道一个applet会干些什么,你毫无办法,只有依赖Java内置的安全特性。但你能这么放心吗?
安全模型
Java的安全模型被分为三个主要的防范区域: Byte-code验证器、 类装人器 和安全管理器。虽然我们将逐个的讨论,但注意到这三个部分是Java运行环境执行模式的一个整体是非常重要的。也就是说,它们在Java设计时是相当重要的,而不是一种追思。
编译后,Java原代码被转换成为独立与平台的字节代码。applet安全便开始与Byte-code验证器,它将:
保证编译后的代码格式正确。
对每个成员函数进行检查以确保其字节代码不会违犯访问类型限制和指针寻址规则(见下文)。
通过验证后,以下几点将得到保证:
Internet堆栈不会发生溢出或下溢。 (若两种情况中的一种发生,系统的稳定程度下降,黑客也将得到“混水摸鱼”的机会。) 这种类型的攻击都是针对一些很著名的安全疏漏的。
不会发生非法数据转换 (验证器不允许整数作为指针来处理)。这点保证了变量不会访问限制访问的内存区域。
字节代码指令将有正确类型的参数 (与上面的原因相同)。
在一个对象的内部,只有公共数据和函数才可被访问。限制对所有部件的访问,所以属性数据保持为私有的。
另一个防范领域, Java类装入器,决定了类(代码)将如何(和何时)被调入。它许多重要功能中的一项就是要确保在运行环境中正在执行的applet不会取代系统级的部件。这个部分是由浏览器生产商提供---通常它是基于由Sun Microsystems公司提供的模板。
Java安全模型的第三个,同时也是最重要的部分便是安全管理器。它的任务对所有所谓“危险方法”(要求文件输入/输出、网络访问或想要定义一个心得类装入器)进行 runtime验证。其中包括:
管理所有的socket操作。
控制对操作系统线程的创建和访问。
阻止新的类装入器的安装。
管理对文件的访问 (显然,我们不想让applet随便阅读文件l)。
为保证安全,基本Java类库(由Sun提供的)中的所有的方法必须在执行任何危险操作之前得到安全管理器的许可。安全管理器对任何请求都有否决权。
在未来的日子里,Java的安全模型还会不断的发展---堵住已被发现的缺口同时不断支持新的语言特点。这些特点包括:
数字签名,这样你便可以放心执行的applet确实是你想要执行的。
允许站点互相识别的有验证能力的证书。
安全 Sockets, 通过加密来确保相互协作的构件间的私有和互相信任的通讯。
安全准则
Java用户目前所经历的问题中,绝大多数都应被划分到实现方面的欠缺,而不是安全模型本身的错误。 (例如,上文所提到的两个漏洞已经被改正)。但是,为了增加Internet上的经验,用户和系统管理圆应遵循以下的准则:
只使用网络软件的最新版本,特别是浏览器。较早版本将存在更多的错误。
能被你的系统所识别。例如,你是通过你的个人笔记本电脑或企业网络系统访问Net吗?
警惕并调查最小的可疑之处。一次“文件丢失”很可能由一次攻击所导致。
本文只是对Java安全模型的简略描述。还有很多的问题和计策需要管理员(和用户)去了解与探究(如防火墙、企业内部网等等)。但到此为止,你应该思考一下这两个问题:
Java applets确实安全吗?
你应该使用 Java吗?当然 --如果你能持之以恒,益处会远远多于危险。关键就在于要先于黑客一步。
关于作者
Charles F. Bowman 是一位精通关系型数据库、GUI和面向对象技术的独立的顾问、作家和演说家。可以通过cfb@panix.com和71700. 3570@compuserve.com与他联系
<
小小豆叮
在Applet中读取文件内容
02:06
---- 我们知道,在Java Applet中出于安全性考虑,Applet是不允许对文件进行操作的,不仅不允许写文件,而且不允许读文件。尽管我们在编制Applet时即使使用了文件操作的语句Java不会报错,在开发工具(如Cafe)中调试时也能够正常运行,但当我们在浏览器中运行这个Applet时浏览器就会报错。但有时我们的确要读取文件中的内容,比如要将服务器中的.txt文件内容在Applet中显示出来,是不是就没有办法了呢?
---- 不!有办法。决窍就是我们不要将这些服务器上的文件作为普通文件来处理,而是将它们作为网络资源来获取它们的内容。在Java中可用于获取网络资源的类主要有两种,一是URL类,另一个是URLConnection类。两个类都提供了以字节流的方式读取资源信息的方法,而且可以对资源信息的类型作出判断,以便作相应的处理。不同之处是URLConnection类可提供的信息比URL类要多得多,它除了可以获取资源数据外,还可以提供资源长度、资源发送时间、资源最新更新时间、资源编码、资源的标题等许多信息。
---- 以下是两个类的常用方法。
URL类:
· URL(String, String, int, String)
构造方法,创建一个包含协议类型、主机名、
端口号和路径的URL对象
· URL(String, String, String)
构造方法,创建一个包含协议类型、主机名和路径
的URL对象,其中端口号为缺省值
· URL(String)
构造方法,创建一个URL对象,参数将协议
、主机名、端口号和路径组合起来
· URL(URL,String)
构造方法,根据给定URL对象与相对路径创建一个新的URL对象
· Object getContent( )
检索URL内容信息,并返回给对象
· InputStream openStream( )
从资源处返回一个输入流
· URLConnection openConnection( )
生成一个URLConnection对象
URLConnection类:
· protected URLConnection(URL)
构造方法,创建一个针对指定URL对象的URLConnection类
· Object getContent( )
返回URL对象所对应的内容
· InputStream getInputStream( )
获取从对象中读取的字节流
· Protected static String guessContentTypeFromStream(InputStream is)
根据输入流猜测内容的类型
---- 下面以读取服务器上的.txt文件内容为例说明如何在Applet中读取文件。设服务器的IP地址为202.114.1.16,.txt文件的路径为/file/sample.txt。以下是读取sample.txt内容的Applet的源代码。
//getfile.html
< HTML >
< HEAD >
< TITLE >读取文件的Applet< /TITLE >
< /HEAD >
< BODY >
这是服务器上TXT文件的内容< BR >
< Applet code="getFile.class" width=200 height=100 >
< /Applet >
< /BODY >
< /HTML >
//getFile.java
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
public class getFile extends Applet
{
String info;
public void init()
{
URL url;
URLConnection urlc;
resize(200,100);
setBackground(Color.white);
try{
url = new URL("http://202.114.1.16/file/sample.txt");
urlc = url.openConnection();
urlc.connect();
info = getInfo(urlc);
}catch(MalformedURLException mfe){
System.out.println("URL form error!");
}catch(IOException ioe){
System.out.println("IO Exception!");
}
}
public void paint(Graphics g)
{
g.setColor(Color.red);
g.drawString(info,50,50);
}
public String getInfo(URLConnection urlc)
{
String txt = new String();
InputStream is;
int i;
try{
is = urlc.getInputStream();
i = is.read();
while(i != -1){
txt = txt + (char)i;
i = is.read();
}
is.close();
}catch(IOException ioe){
System.out.println("IO Exception!");
txt = new String("File read failed!");
}
return txt;
}
}
以上JAVA程序在两种系统中调试均通过,两种系统的配置分别为:
(1) 服务器:Digital Unix + Oracle Webserver3.0
浏览器:Netscape4.0.5或IE4.0
(2) 服务器:Windows98 + Pws
浏览器:Netscape4.0.5或IE4.0
<
小小豆叮
JAVA 语言中链表和双向链表的实现
02:05
链表是一种重要的数据结构,在程序设计中占有很重要的地位。C语言和C++语言中是用指针来实现链表结构的,由于JAVA语言不提供指针,所以有人认为在JAVA语言中不能实现链表,其实不然,JAVA语言比C和C++更容易实现链表结构。JAVA语言中的对象引用实际上是一个指针(本文中的指针均为概念上的意义,而非语言提供的数据类型),所以我们可以编写这样的类来实现链表中的结点。
class Node
{
Object data;
Node next; // 指向下一个结点
}
将数据域定义成Object类是因为Object类是广义超类(所有类的祖先),任何类对象都可以给其赋值,增加了代码的通用性。为了使链表可以被访问还需要定义一个表头,表头必须包含指向第一个结点的指针和指向当前结点的指针。为了便于在链表尾部增加结点,还可以增加一指向链表尾部的指针,另外还可以用一个域来表示链表的大小,当调用者想得到链表的大小时,不必遍历整个链表,下图是这种链表的示意图。
图一 链表的数据结构
我们可以用类List来实现链表结构,用变量Head、Tail、Length、Pointer来实现表头。存储当前结点的指针时有一定的技巧,Pointer并非存储指向当前结点的指针,而是存储指向它的前趋结点的指针,当其值为null时表示当前结点是第一个结点。那么我们为什么要这样做呢?这是因为当我们删除当前结点后仍需保证剩下的结点构成链表,如果Pointer指向当前结点,则会给操作带来很大困难。那么如何得到当前结点呢,我们定义了一个方法cursor(),返回值是指向当前结点的指针。类List还定义了一些方法来实现对链表的基本操作,通过运用这些基本操作我们可以对链表进行各种操作。例如reset()方法使第一个结点成为当前结点。insert( Object d )方法在当前结点前插入一个结点,并使其成为当前结点。remove()方法删除当前结点同时返回其内容,并使其后继结点成为当前结点,如果删除的是最后一个结点,则第一个结点变为当前结点。
链表类List的源代码如下:
import java.io.*;
public class List
{
/* 用变量来实现表头 */
private Node Head=null;
private Node Tail=null;
private Node Pointer=null;
private int Length = 0;
public void deleteAll()
/* 清空整个链表 */
{
Head = null;
Tail = null;
Pointer = null;
Length = 0;
}
public void reset()
/* 链表复位,使第一个结点成为当前结点 */
{
Pointer = null;
}
public boolean isEmpty( )
/* 判断链表是否为空 */
{
return( Length == 0 );
}
public boolean isEnd()
/* 判断当前结点是否为最后一个结点 */
{
if ( Length == 0 )
throw new java.lang.NullPointerException();
else if ( Length == 1 )
return true;
else
return( cursor() == Tail );
}
public Object nextNode()
/* 返回当前结点的下一个结点的值,并使其成为当前结点 */
{
if ( Length == 1 )
throw new java.util.NoSuchElementException();
else if ( Length == 0 )
throw new java.lang.NullPointerException();
else
{
Node temp = cursor();
Pointer = temp;
if ( temp != Tail )
return( temp.next.data );
else
throw new java.util.NoSuchElementException();
}
}
public Object currentNode()
/* 返回当前结点的值 */
{
Node temp = cursor();
return temp.data;
}
public void insert( Object d )
/* 在当前结点前插入一个结点,并使其成为当前结点 */
{
Node e = new Node( d );
if ( Length == 0 )
{
Tail = e;
Head = e;
}
else
{
Node temp = cursor();
e.next = temp;
if ( Pointer == null )
Head = e;
else
Pointer.next = e;
}
Length++;
}
public int size()
/* 返回链表的大小 */
{
return ( Length );
}
public Object remove()
/* 将当前结点移出链表,下一个结点成为当前结点, 如果移出
的结点是最后一个结点,则第一个结点成为当前结点 */
{
Object temp ;
if ( Length == 0 )
throw new java.util.NoSuchElementException();
else if ( Length == 1 )
{
temp = Head.data;
deleteAll();
}
else
{
Node cur = cursor();
temp = cur.data;
if ( cur == Head )
Head = cur.next;
else if ( cur == Tail )
{
Pointer.next = null;
Tail = Pointer;
reset();
}
else
Pointer.next = cur.next;
Length--;
}
return temp;
}
private Node cursor()
/* 返回当前结点的指针 */
{
if ( Head == null )
throw new java.lang.NullPointerException();
else if ( Pointer == null )
return Head;
else
return Pointer.next;
}
public static void main( String[] args )
/* 链表的简单应用举例 */
{
List a = new List();
for ( int i = 1; i <= 10; i++ )
a.insert( new Integer( i ) );
System.out.println( a.currentNode() );
while ( !a.isEnd() )
System.out.println( a.nextNode() );
a.reset();
while ( !a.isEnd() )
{
a.remove();
}
a.remove();
a.reset();
if ( a.isEmpty() )
System.out.println("There is no Node in List \n");
System.in.println( " You can press return to quit\n" );
try
{
System.in.read(); // 确保用户看清程序运行结果
}
catch( IOException e )
{}
}
}
class Node
/* 构成链表的结点定义 */
{
Object data;
Node next;
Node( Object d )
{
data = d;
next = null;
}
}
读者还可以根据实际需要定义新的方法来对链表进行操作。双向链表可以用类似的方法实现只是结点的类增加了一个指向前趋结点的指针。
我们可以用这样的代码来实现:
class Node
{
Object data;
Node next;
Node previous;
Node ( Object d )
{
data = d;
next = null;
previous = null;
}
}
当然双向链表基本操作的实现略有不同,这里就不再详述了。链表和双向链表的实现方法,也可以用在堆栈和队列的实现中,这里就不再多写了,有兴趣的读者可以将List类的代码稍加改动即可。
参考文献:《网络编程语言JAVA》 孙淑玲、王太权、陈意云
中国科技大学出版社
<
小小豆叮
保护Applet免遭"窃取"的小技巧
02:05
---- 我 们 知 道, 网 页 中 的Java Applet 虽 不 象 图 像 文 件 可 以 直 接 从 游 览 器 中 以Save as 命 令 获 取, 但 仍 可 能 被 浏 览 者 从Cache 中 找 出, 放 入 自 己 的 网 页。 因 此, 如 果 要 保 护 自 己 的applet 免 遭" 窃 取", 方 法 之 一 是 让Applet 只 运 行 于 指 定URL 的 网 页 中。 在Java 语 言 中,Applet 所 在 网 页 的URL 可 以 由java.applet 程 序 包 中 提 供 了 方 法getDocumenBase() 来 取 得。
---- 例 如, 在 下 面 一 段 程 序 中,Applet 在 初 始 化 阶 段 先 检 测 当 前URL, 一 旦 与 设 定 不 符, 就 在Java 控 制 台(Java Console) 中 输 出 警 告 信 息, 然 后 自 动 中 止 运 行, 从 而 实 现 了Applet 的" 版 权 保 护"。
import java.net.*;
import java.applet.*;
public class myonly extends Applet{
public void init(){
String ower=getDocumentBase().getHost();
if(!ower.equals("www.mydomain.com")){
System.out.println("Warning ! A stolen Applet.");
System.exit(1);
}
else{
… …
}
}
<
小小豆叮
用JAVA程序取得IP地址
02:04
在TCP/IP 互联网时,经常会需要查询自己主机的IP地址和www服务器的IP地址。虽然,我们可以使用IPCONFIG 和PING 进行IP地址查询,但是如果在应用程序或APPLET中使用此命令回破坏我们应用程序界面。
---- 为此本人使用JAVA 做了一个简单的程序可以直接查询自己主机的IP地址和www服务器的IP地址。
// 文件名为 NetTool.java (注意:在JAVA 语言中大小写敏感)
import java.net.*;
public class NetTool{
InetAddress myIPaddress=null;
InetAddress myServer=null;
public static void main( String args[]){
NetTool mytool;
mytool=new NetTool();
System.out.println("Your host IP is: "
+ mytool.getMyIP());
System.out.println("The Server IP is :"
+mytool.getServerIP());
}
//取得LOCALHOST的IP地址
public InetAddress getMyIP() {
try { myIPaddress=InetAddress.getLocalHost();}
catch (UnknownHostException e) {}
return (myIPaddress);
}
//取得 www.abc.com 的IP地址
public InetAddress getServerIP(){
try {myServer=InetAddress.getByName(
"www.abc.com");}
catch (UnknownHostException e) {}
return (myServer);
}
}
---- 由于JAVA语言的跨平台特性,以上程序编译后可直接在任何装有JVM系统的机器上运行。以上程序旨在抛砖引玉,读者可将上述代码稍加变换转化成APPLET加到你的homepage中,或将地址查询结果写到一个文件中去,建立自己本地的hosts文件。
<
小小豆叮
利用Java完成域名和IP地址的转换
02:04
对我们普通用户而言,形象化的符号要比一捆枯燥的数字编码要好记得多。比如,我们会很容易地记住字符串“www.oscar.com”,却很难记住206.17.191.12。本文简要地介绍了Internet命名规则和地址的划分约定,然后介绍了如何利用Java语言完成符号名(上面的字符串)和IP地址(上面的数字串)之间的转换工作,并给出一个实例,检查可访问的远程主机。
<
小小豆叮
用JDBC开发基于客户/服务器模式的Internet/Intranet数
02:04
1、概述
随着Internet/Intranet的普及以及WEB技术的发展,人们对信息的需求越来越强烈,数据库与Internet/Intranet应用软件的集成已经成为了非常迫切的问题。互联网上数据库产品的复杂,有限的带宽,以及互联网上软件产品的跨平台性,将是我们遇到的最大困难。本文通过作者的工作实践,介绍了JDBC规范及应用JAVA的SOCKET机制,以及JAVA的客户/服务器计算模式,最后根据以上的原理,实现了一个简单的基于客户/服务器的JAVA查询远端服务器上的数据库的小例子。
2、问题提出
当我从事Internet/Intranet软件开发时,为了达到很好的兼容性,安全性以及跨平台性,不得不采用JAVA编程,但是JAVA数据库方面的不足,确实众所周知。虽然JDBC技术在一段时间以前就已经发布了,但是对于大多数数据库产品而言,却要为其编制特殊的驱动。虽然有一些数据库厂商用JAVA编写了自己产品的驱动(如IBM的DB2数据库软件),但是这类产品的价格和复杂的使用方法却是使一些中小企业和Internet爱好者们望而却步,另外,在有限的带宽下如何减少数据的流量等一系列问题,将是解决这类问题的最大的绊脚石。因此,我们必须解决这类问题,并且产生一个跨平台性的,能挂接多种数据库的,基于客户/服务器的软件解决方案。
3、分析问题
以上的问题我们可以用JAVA的Socket机制实现客户/服务器,然后在服务器端用JDBC来完成客户端所提交的查询要求,因此,将问题分为如下两个方面。
(1)JAVA中的Socket机制
在各种网络的客户/服务器应用中,客户与服务器之间的通讯机制是多种多样的,但大多数都采用的是基于TCP/IP的Socket机制来完成的,Socket是两个程序间用来进行双向传输的网络通讯端点,在服务器程序方面通过IP在网络中标识自己,然后,通过一个客户端程序知道的端口号来提供服务,而客户端在网络中通过服务器的IP来找到服务器,通过连接他的端口号来获得服务器的某项服务。当然,计算机也拥有一些内置的用来提供其他服务的端口和空闲的端口,这样这些空闲端口就可供程序员来使用。由于Socket通讯机制是一种较为底层的通讯机制,所以通过Socket的数据表示是一种原始的字节流信息。那么客户端 服务器的程序就应该按照程序员制定的一种约定来进行数据的格式化处理后才能进行具体的应用,这种约定实际上就是一种协议。
Socket通讯机制提供了两种通讯方式,一种是有连接的,另一种是无连接的。有连接的方式是指程序在开始时,双方就建立了连接,形成了一条通讯链路,这条通讯链路一直存在,直到任意一方终止连接为止。在连接的同时,双方就通过这个通讯链路进行I/O操作,这种方式是可靠的而且是全双工的。无连接的方式是指提供不可靠的连接,数据传送时是以数据报的方式发送,类似与我们的寄信。本文采用的为有连接方式。
JAVA在现实中有两个类用于Socket连接,一个是ServerSocket类,应用于服务器方;一个是Socket,应用于客户方。在服务器端用如下语句对端口进行初始化及监听:
try{
ServerSocket server=new ServerSocket(9001);//监听9001端口,此端口不能与系统的
//端口号冲突
Socket socket=server.accept();//阻塞进程,一旦有客户连接初始化socket类
DataInputStream in=new DataInputStream(socket.getInputStream()); //建立输入流
PrintStream out=new PrintStream(socket.getOutputStream()); //建立输出流
}catch(IOException e){}
在客户端用如下语句即可与服务器连接:
try{
socket=new Socket(hostname,9001);//连接以hostname的服务器,端口号为9001
//这里的hostname为服务器的IP地址
in=new DataInputStream(socket.getInputStream());//初始化输入流
out=new PrintStream(socket.getOutputStream());//定义输出流
}catch(IOException e){}
通过以上语句后,在客户与服务器之间就建立了in和out这两个输入和输出流,那么就可以进行通信了。最后,为了在服务器端能响应多个客户端的请求,在服务器的输入和输出流的处理中应用了多线程,是每一个线程对应一个客户端的服务,由于JAVA的内存管理是一种摾
<
小小豆叮
Java Card应用程式开发工具
02:03
继各家厂商推出的J2ME IDE开发工具之後,接下来工具厂商的焦点放在更
小型的Java版本--Java Card的应用程式开发上面。Sun为了提供一个可以
用来开发、侦错并测试的完整工具,并能够和市面上的IDE工具作整合,已
经推出这样一个Java Card开发环境。现在Metrowerks已经将这个工具整合
到自己的Code Warrior IDE之中。
这个“Code Warrior for Java Card”提供了以下功能:
--Java Card的byte code和原始程式码的完整侦错功能。
--完整的模拟执行环境,以供程式开发者作实际执行状况的测试。
--从最新版Java Card Development Kit之中取得的工具元件和更新程式,
以获得Java Card平台中所有的工具支援。
这个产品正式版将在今年年底上市。目前的beta版本只要加入Metrowerks
的CodeWarrior Beta Program即可取得。
<
小小豆叮
JAVA实现服务器和多用户跨平台的通讯
02:03
---- 随着网络技术的发展,我们的局域网越做越大,里面的服务器客户机数量也很多。在为我们提供了诸多便利的同时,我们发现,由于服务器和客户机的操作平台不同,它们之间的通信是一个麻烦的问题,因为很多现成的通信软件或者源程序都是针对同一平台的。为了解决这个问题,我们采用JAVA编程,成功的实现了LINUX,WINDOWS NT,WIN98跨平台的通讯。
---- 服务器程序源代码如下:
//server.java import java.io.*; import sun.net.*; class
server extends NetworkServer //定义服务器类 {DataInputStream net_input; //定义数据输出
PrintStream net_output; //定义数据输入 public static void main(String args[]) { new
server();} public server() //运行服务器功能,并把端口设为1111 { try
{startServer(1111);} catch (Exception e) { System.out.println( "Unable to start
server."); return; } System.out.println("Waiting for clients..."); } public
void serviceRequest() //定义服务应答功能 { net_input = new
DataInputStream(clientInput); net_output = System.out; String user = read_net_input();
System.out.println(user+" connected!"); while(true) { String string;
if((string=read_net_input( ))==null) break; //如果客户机输入NULL,中断服务
write_net_output(user+":"+string); } System.out.println(user+" has
disconnected!"); } String read_net_input() { try {return net_input.readLine();}
catch(IOException e) {return null;} } void write_net_output(String string) {
net_output.println(string); net_output.flush(); } } 客户机程序源代码:
//client.java import java.io.*; import sun.net.*; class client extends NetworkClient //定义客户机类
{ DataInputStream net_input; PrintStream net_output; public static void main(String
args[])//获得服务器IP地址和客户机名 { if(args.length<2) { System.out.println( "To run,type:\n"); System.out.println( "java client "); }
System.out.println( "Connecting..."); try {new client(args[0],args[1]);} catch
(Exception e) { System.out.println( "Unable to create NetworkClient."); return;
} } public client (String host,String username) throws IOException //与服务器链接功能
{ super(host,1111); if(serverIsOpen()) { System.out.println( "Connected to
server."); net_input = new DataInputStream(System.in); net_output = serverOutput;
net_output.println(username); chat(); } else System.out.println("Error:Could not
connect to server."); } void chat() //定义信息传递函数,当输入EXIT时,中断链接
{ String string; System.out.println( "Type EXIT to exit"); while(true) {
string=read_net_input(); if(string.equalsIgnoreCase("EXIT")) break;
write_net_output(string); } System.out.println("Disconnecting...");
close_server(); System.out.println("Done!"); } String read_net_input() { try
{return net_input.readLine();} catch(IOException e) {return null;} } void
write_net_output(String string) { net_output.println(string); net_output.flush(); } void
close_server() { try {closeServer();} catch(Exception e) {System.out.println("Unable
to close server.");} } }
----
把两个源程序输入后,在任一操作平台上运行javac server.java和javac client.java,分别把它们编译成class文件。由于java的class文件的跨平台性,只要在服务器上运行相应的java解析程序执行server,在客户机上运行相应的java解析程序执行client ,就能实现客户机和服务器之间的通讯了,而且服务器允许多用户接入。以笔者学校的局域网为例,源程序在WIN98平台上用JDK 1.1.5编译成功,把server.class拷到一台LINUX服务器上,执行java server(该服务器已经安装了JAVA的RPM包),在其他WINNT平台上拷入client.class,运行jview client 192.168.100.1 NT(192.168.100.1是LINUX服务器的IP地址),就能实现跨平台通讯了。
<
小小豆叮
JSP的环境配置
02:03
我们先要确认你已经安装好orion和ant,对于orion和ant的安装可以参见它们的安装文档,这里我们不再细述。我已经假定你安装好了orion和ant同时系统的PATH中已经加入了ant的bin目录的路径,以及在环境变量中正确的设置好了ANT_HOME指向你的ant安装路径和JAVA_HOME指向到你的JDK安装路径。还有一点要注意的是需要你在系统的CLASSPATH环境变量中加入orion\jndi.jar、orion\orion.jar、orion\ejb.jar、orion\xalan.jar、orion\xerces.jar。具体的验证方法是在命令行下输入java -version命令时会得到如下这样的提示:
java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)
而输入ant -version命令时会得到如下这样的提示:
Ant version 1.2 compiled on October 24 2000
在orion目录中我们输入java -jar orion.jar后会得到以下这样的提示来说明orion也运行正常:
Orion/1.3.8 initialized
确定这所有的都正常后我们开始我们的开发工作了。
<
小小豆叮
如何进行Apache虚拟主机配置
02:02
假设你的域名为 www.abc.cxm. 你想在主机hjc.abc.cxm配置一个如下
URl jdeveloper.abc.cxm.的虚拟主机.
1. 在主机hjc.abc.cxm 安装 Apache
从 http://httpd.apache.org/ 获取最新的Apache Server (当前最新版为1.3.14)
# ./configure --prefix=/usr/local/apache --with-layout=GNU
--enable-shared=max --enable-module=most
# make
# make install
2. 配置DNS
参见 配置DNS
3. 配置 Apache 虚拟主机(基于名字)
步骤
首先 你的DNS应正确配置,能够解析hjc.abc.cxm
a.
在你的DNS配置中加入一行
jdeveloper IN CNAME hjc
b.修改apache httpd.conf
NameVirtualHost 111.222.1.88
< VirtualHost 111.222.1.88>
ServerAdmin jdeveloper@21cn.com
DocumentRoot "C:/Program Files/Apache Group/Apache/htdocs/demo"
ServerName jdeveloper.abc.cxm
ErrorLog logs/jdeveloper.log
< /VirtualHost>
注:111.222.1.88 为主机 hjc.abc.cxm的IP.
4. 测试
把你的HTML文件放到
C:/Program Files/Apache Group/Apache/htdocs/demo/
在设置好DNS的Client,通过IE访问 http://jdeveloper.abc.cxm 即可。
<
小小豆叮
用Socket类实现HTTP协议客户端应用
02:02
Http客户端程序已集成在Java语言中,可以通过URLConnection类调用。遗憾的
是,由于SUN没有公布Http客户程序的源码,它实现的细节仍是一个谜。本文根据HTTP
协议规范,用Java.net.Socket类实现一个HTTP协议客户端程序。
1.Socket类:
了解TCP/IP协议集通信的读者知道,协议间的通信是通过Socket完成的。在
Java.net包中,Socket类就是对Socket的具体实现。它通过连接到主机后,返回一个
I/O流,实现协议间的信息交换。
2 . HTTP协议
HTTP协议同其它TCP/IP协议集中的协议一样,是遵循客户/服务器模型工作的。客
户端发往服务端的信息格式如下:
------------------------------
请求方法 URL HTTP协议的版本号
提交的元信息
**空行**
实体
------------------------------
请求方法是对这次连接工作的说明,目前HTTP协议已经发展到1.1版,它包括GET、
HEAD、POST、DELETE、OPTIONS、TRACE、PUT七种。元信息是关于当前请求的信息。通
过分析元信息,可以检查实体数据是否完整,接收过程是否出错,类型是否匹配等。元
信息的引入使HTTP协议通信更加稳妥可靠。实体是请求的具体内容。
将上述报文发往Web服务器,如果成功,应答格式如下:
--------------------------------
HTTP协议的版本号 应答状态码 应答状态码说明
接收的元信息
**空行**
实体
--------------------------------
以上报文发向客户端,并且接收成功,彼此间关闭连接,完成一次握手。
下面用最常用的GET方法,来说明具体的报文应用
----------------------------------
GET http://www.youhost.com HTTP/1.0
accept: www/source; text/html; image/gif; image/jpeg; */*
User_Agent: myAgent
**空行**
-----------------------------------
这个报文是向www.youhost.com主机请求一个缺省HTML文档。客户端HTTP协议版本
号是1.0版,元信息包括可接收的文件格式,用户代理,每一段之间用回车换行符分
隔,最后以一个空行结束。发向服务器后,如果执行过程正常,服务器返回以下代码:
------------------------------------
HTTP/1.1 200 OK
Date: Tue, 14 Sep 1999 02:19:57 GMT
Server: Apache/1.2.6
Connection: close
Content-Type: text/html
**空行**
......
------------------------------------
HTTP/1.1表示这个HTTP服务器是1.1版,200是服务器对客户请求的应答状态码,OK
是对应答状态码的解释,之后是这个文档的元信息和文档正文。(相关应答状态码和元
信息的解释请参阅Inetrnet标准草案:RFC2616)。
3. HTTP客户端程序:
import java.net.*;
import java.io.*;
import java.util.Properties;
import java.util.Enumeration;
public class Http {
protected Socket client;
protected BufferedOutputStream sender;
protected BufferedInputStream receiver;
protected ByteArrayInputStream byteStream;
protected URL target;
private int responseCode=-1;
private String responseMessage="";
private String serverVersion="";
private Properties header = new Properties();
public Http() { }
public Http(String url) {
GET(url) ;
}
/* GET方法根据URL,会请求文件、数据库查询结果、程序运行结果等多种内容 */
public void GET(String url) {
try {
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "GET "+ getURLFormat(target) +" HTTP/1.0\r\n"
+ getBaseHeads()+"\r\n";
sendMessage(cmd);
receiveMessage();
}catch(ProtocolException p) {
p.printStackTrace();
return;
}catch(UnknownHostException e) {
e.printStackTrace();
return;
}catch(IOException i)
i.printStackTrace();
return;
}
}
/*
* HEAD方法只请求URL的元信息,不包括URL本身。若怀疑本机和服务器上的
* 文件相同,用这个方法检查最快捷有效。
*/
public void HEAD(String url) {
try {
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "HEAD "+getURLFormat(target)+" HTTP/1.0\r\n"
+getBaseHeads()+"\r\n";
sendMessage(cmd);
receiveMessage();
}catch(ProtocolException p) {
p.printStackTrace();
return;
}catch(UnknownHostException e) {
e.printStackTrace();
return;
}catch(IOException i)
i.printStackTrace();
return;
}
}
/*
* POST方法是向服务器传送数据,以便服务器做出相应的处理。例如网页上常用的
* 提交表格。
*/
public void POST(String url,String content) {
try {
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "POST "+ getURLFormat(target) +"
HTTP/1.0\r\n"+getBaseHeads();
cmd += "Content-type: application/x-www-form-urlencoded\r\n";
cmd += "Content-length: " + content.length() + "\r\n\r\n";
cmd += content+"\r\n";
sendMessage(cmd);
receiveMessage();
}catch(ProtocolException p) {
p.printStackTrace();
return;
}catch(UnknownHostException e) {
e.printStackTrace();
return;
}catch(IOException i)
i.printStackTrace();
return;
}
}
protected void checkHTTP(String url) throws ProtocolException {
try {
URL target = new URL(url);
if(target==null || !target.getProtocol().toUpperCase().equals("HTTP") )
throw new ProtocolException("这不是HTTP协议");
this.target = target;
}catch(MalformedURLException m) {
throw new ProtocolException("协议格式错误");
}
}
/*
* 与Web服务器连接。若找不到Web服务器,InetAddress会引发UnknownHostException
* 异常。若Socket连接失败,会引发IOException异常。
*/
protected void openServer(String host,int port) throws
UnknownHostException,IOException {
header.clear();
responseMessage=""; responseCode=-1;
try {
if(client!=null) closeServer();
if(byteStream != null) {
byteStream.close(); byteStream=null;
}
InetAddress address = InetAddress.getByName(host);
client = new Socket(address,port==-1?80:port);
sender = new BufferedOutputStream(client.getOutputStream());
receiver = new BufferedInputStream(client.getInputStream());
}catch(UnknownHostException u) {
throw u;
}catch(IOException i) {
throw i;
}
}
/* 关闭与Web服务器的连接 */
protected void closeServer() throws IOException {
if(client==null) return;
try {
client.close(); sender.close(); receiver.close();
}catch(IOException i) {
throw i;
}
client=null; sender=null; receiver=null;
}
protected String getURLFormat(URL target) {
String spec = "http://"+target.getHost();
if(target.getPort()!=-1)
spec+=":"+target.getPort();
return spec+=target.getFile();
}
/* 向Web服务器传送数据 */
protected void sendMessage(String data) throws IOException{
sender.write(data.getBytes(),0,data.length());
sender.flush();
}
/* 接收来自Web服务器的数据 */
protected void receiveMessage() throws IOException{
byte data[] = new byte[1024];
int count=0;
int word=-1;
// 解析第一行
while( (word=receiver.read())!=-1 ) {
if(word=='\r'||word=='\n') {
word=receiver.read();
if(word=='\n') word=receiver.read();
break;
}
if(count == data.length) data = addCapacity(data);
data[count++]=(byte)word;
}
String message = new String(data,0,count);
int mark = message.indexOf(32);
serverVersion = message.substring(0,mark);
while( mark-1) {
if(word=='\t') word=32;
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)word;
parseLine: {
while( (symbol=receiver.read()) >-1 ) {
switch(symbol) {
case '\t':
symbol=32; break;
case '\r':
case '\n':
word = receiver.read();
if( symbol=='\r' && word=='\n') {
word=receiver.read();
if(word=='\r') word=receiver.read();
}
if( word=='\r' || word=='\n' || word>32) break parseLine;
symbol=32; break;
}
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)symbol;
}
word=-1;
}
message = new String(data,0,count);
mark = message.indexOf(':');
String key = null;
if(mark>0) key = message.substring(0,mark);
mark++;
while( mark0) byteStream = new ByteArrayInputStream(data,0,count);
data=null;
closeServer();
}
public String getResponseMessage() {
return responseMessage;
}
public int getResponseCode() {
return responseCode;
}
public String getServerVersion() {
return serverVersion;
}
public InputStream getInputStream() {
return byteStream;
}
public synchronized String getHeaderKey(int i) {
if(i>=header.size()) return null;
Enumeration enum = header.propertyNames();
String key = null;
for(int j=0; j<=i; j++)
key = (String)enum.nextElement();
return key;
}
public synchronized String getHeaderValue(int i) {
if(i>=header.size()) return null;
return header.getProperty(getHeaderKey(i));
}
public synchronized String getHeaderValue(String key) {
return header.getProperty(key);
}
protected String getBaseHeads() {
String inf = "User-Agent: myselfHttp/1.0\r\n"+
"Accept: www/source; text/html; image/gif; */*\r\n";
return inf;
}
private byte[] addCapacity(byte rece[]){
byte temp[] = new byte[rece.length+1024];
System.arraycopy(rece,0,temp,0,rece.length);
return temp;
}
}
注: 程序中只实现GET、HEAD、POST三种方法。其他几种因不常使用,暂且忽略。
<
小小豆叮