飞艳小屋

程序--人生--哲学___________________欢迎艳儿的加入

BlogJava 首页 新随笔 联系 聚合 管理
  52 Posts :: 175 Stories :: 107 Comments :: 0 Trackbacks

2006 年 03 月 05 日

关于blob在ibatis中的处理

在 iBATIS 2.0.9 以前,处理 Oracle BLOB 类型相当麻烦,要自己实现TypeHandlerCallback 接口。iBATIS 2.0.9 提供了 BlobTypeHandlerCallback 实现类,写入、取出程序和上面的 SQL Server 一样。只是映射文件 resultMap 元素需要修改。



typeHandler="com.ibatis.sqlmap.engine.type.BlobTypeHandlerCallback"/>

因为ibatis2.0.9提供oracle的blob字段处理器。

Posted by@rimen at 2006 年 03 月 05 日 | Comments (0) | TrackBack

2006 年 02 月 26 日

关于ThreadLocal如何保证数据线程安全

在多线程的java环境中,我们很多使用需要一个共享内存来保证线程之间的通讯,这个在java里

面很简单,实例变量和静态变量。这也大家是在不经意间的使用方法,想都不用想都可以实现这些方法来

达到多线程共享数据的目的,这些共享数据存在一个线程安全的问题。特别是对于具有串行意义的数据,

流入请求号、事务号等。
这里有两个概念:
1.保证在一个线程中使用完这个数据之前不能让别人去修改这个字段的意义.
2.在整个线程生命周期内,保证这个数据一致性,而且不影响其他线程读取这个数据.
我们在考虑现存安全的时候,对第一种情况比较关注.例如大家都知道在对一个具有线程安全数据

访问的块加入一个synchronized.这里有些误导,大家会考虑在对大部分的线程安全操作考虑加入这个关键

字来达到不出错的目的,但是这个是一种很不明智的做法,我们很多时候在控制同步粒度的时候考虑不周,

导致大量的线程在同步阻塞,使得系统的性能大幅下降.所以我建议不到不得已少用慎用这个关键字.
对于第二种:大家或许或考虑用本地变量来保证这个特性.确实在一个方法中,我们的变量保证第

二种情况描述的特性,但是在我们复杂的环境中,往往有很多个线程在完成系统的任务,这样我们的时候不

可能用一个方法来描述一个线程的任务.例如我们在j2ee环境中,客户的请求号,我们在应用中需要用这个

号码来跟踪客户的信息.还有我们的事务,我们很多时候在系统中需要获得一个TXNID来贯穿事务.这样我们

能做到的就是使用ThreadLocal变量.
大家先看一下jdk中描述的,ThreadLocal提供了现成本地变量副本,为每个存放在这个实例中的变

量创建一个本地副本,在线程第一次方法这个变量的时候创建这个副本.而且表示,一般这个实例定义成一

个私有静态变量,供所有的线程来保存他们自己的全局资源副本.这样我们很容易想到使用它来做一个序号

发生器,当一个客户线程进来的时候给他分配一个序号,而在序号可以保存到他的请求结束:
public class SerialNum {
// The next serial number to be assigned
private static int nextSerialNum = 0;

private static ThreadLocal serialNum = new ThreadLocal() {
protected synchronized Object initialValue() {
return new Integer(nextSerialNum++);
}
};

public static int get() {
serialNum.set("")
return ((Integer) (serialNum.get())).intValue();
}
}

Posted by@rimen at 2006 年 02 月 26 日 | Comments (0) | TrackBack

2006 年 01 月 19 日

关于资源下载

很多朋友说这里下载太慢了。由于现在没有自己的服务器资源。
有一个朋友提供了一个公网的CVS:
CVS用户配置
认证方式: pserver
路径: /home/cvsroot
主机地址: 218.16.119.21
用户名: soosooone
密码: soosooone

项目名:soosoo

Posted by@rimen at 2006 年 01 月 19 日 | Comments (0) | TrackBack

2005 年 12 月 11 日

关于企业系统之间交互是app server的角色

在整个企业应用程序架构中,应用系统充当业务引擎实现,网络提供通讯宿主,而整正能给整个企业架构附注生命的还是app server。
想象一下在20年期,计算机技术还处于开始发展的时代,面向过程的计算机软件技术开始盛行。有很多大牛可以在c上做出高效庞大的应用系统。
但是就没有什么大牛能做出一个企业应用体系。因为那个时候没有app server这个东西。
app server也许最早出现的是操作系统的概念,它是软件运行的环境,我想很多人要问,为什么有了操作系统这个巨无霸的软件,为什么还需要
app server呢。我想从下面几点陈述:
1.面向的服务对象不应用
大家很多时候会在app server 上开发各种各样的系统,也有人在操作系统上开发各种各样的系统。但是只要你注意一下就知道,他们面向服务的对象
是不一样的。在面向对象软件技术出现之前,也有很多数据库应用系统,但是他们在规模上和服务模式上多时一样的。目前大家听得最多的B/S模式和C/S 模式,
也这是这两种模式可以来界定他们自己的差别.基于操作系统的应用程序,由于操作系统的差异性,大部分程序是基于C/S模式,提供各种操作系统的客户断安装版本.有的甚至还需要和硬件关联在一起,基于这种技术的程序,在客户端的代码异常庞大,维护困难.而在app server 上的程序,由于app server可以提供面向不同操作系统的统一服务,因此对应用程序的版本控制非常简单.客户也可以通过统一的方式获得上面的应用服务.综合来说,基于操作系统的程序适合比较底层,时实性要求比较高的应用.而基于app server 上的开发比较适合业务性很强的应用.

2.管理的对象不一样
由于操作系统主要实现的是对硬件物理设备的管理,给予上面的开放比较容易或者这些资源的访问控制方法.就目前软件技术的水平上开,很少会直接在操作系统上做业务系统的开发,因为他们需要管理的资源主要是业务逻辑和数据模型.而对物理设备的管理比较少.在行业分布上开, app server 比较容易在金融和物流等领域中应用,因为他们主要是业务逻辑,而在计算机网络通讯业,例如电信,移动等领域这种技术就没有那么适合.
3.内核技术
操作系统的内核技术是对物理资源的管理,给应用开发者提供一套api,不同的操作系统差异性比较大,也就是说你在 windows上开发的应用系统基本上不能在linux上运行,而基于app server技术的应用,一般实现的是一个企业级的规范,内核主要实现对不同操作系统的封装.从环境的分布上开,操作系统是app server的运行环境.因此很多app server提供的对操作系统功能的模拟,提供一个软计算机/虚拟计算机的概念.而对应用的接口一般不是app server定的,而是app server服务的规范体系定的.

Posted by@rimen at 2005 年 12 月 11 日 | Comments (0) | TrackBack

2005 年 12 月 01 日

j2ee中web层性能优化

你的J2EE应用是不是运行的很慢?它们能不能承受住不断上升的访问量?本文讲述了开发高性能、高弹性的JSP页面和Servlet的性能优化技术。其意思是建立尽可能快的并能适应数量增长的用户及其请求。在本文中,我将带领你学习已经实践和得到证实的性能调整技术,它将大大地提高你的servlet和jsp页面的性能,进而提升J2EE的性能。这些技术的部分用于开发阶段,例如,设计和编码阶段。另一部分技术则与配置相关。

技术1:在HttpServlet init()方法中缓存数据
服务器会在创建servlet实例之后和servlet处理任何请求之前调用servlet的init()方法。该方法在servlet的生命周期中仅调用一次。为了提高性能,在init()中缓存静态数据或完成要在初始化期间完成的代价昂贵的操作。例如,一个最佳实践是使用实现了javax.sql.DataSource接口的JDBC连接池。DataSource从JNDI树中获得。每调用一次SQL就要使用JNDI查找DataSource是非常昂贵的工作,而且严重影响了应用的性能。Servlet的init()方法可以用于获取DataSource并缓存它以便之后的重用:
public class ControllerServlet extends HttpServlet
{
private javax.sql.DataSource testDS = null;

public void init(ServletConfig config) throws ServletException
{
super.init(config);
Context ctx = null;
try
{
ctx = new InitialContext();
testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS");
}
catch(NamingException ne)
{
ne.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}

public javax.sql.DataSource getTestDS()
{
return testDS;
}
...
...
}

技术2:禁用servlet和Jsp的自动装载功能
当每次修改了Servlet/JSP之后,你将不得不重新启动服务器。由于自动装载功能减少开发时间,该功能被认为在开发阶段是非常有用的。但是,它在运行阶段是非常昂贵的;servlet/JSP由于不必要的装载,增加类装载器的负担而造成很差的性能。同样,这会使你的应用由于已被某种类装载器装载的类不能和当前类装载器装载的类不能相互协作而出现奇怪的冲突现象。因此,在运行环境中为了得到更好的性能,关闭servlet/JSP的自动装载功能。

技术3:控制HttpSession
许多应用需要一系列客户端的请求,因此他们能互相相关联。由于HTTP协议是无状态的,所以基于Web的应用需要负责维护这样一个叫做session的状态。为了支持必须维护状态的应用,Java servlet技术提供了管理session和允许多种机制实现session的API。HttpSession对象扮演了session,但是使用它需要成本。无论何时HttpSession被使用和重写,它都由servlet读取。你可以通过使用下面的技术来提高性能:
l 在JSP页面中不要创建默认的HttpSession:默认情况下,JSP页面创建HttpSession。如果你在JSP页面中不用HttpSession,为了节省性能开销,使用下边的页面指令可以避免自动创建HttpSession对象:
]]>
l 不要将大的对象图存储在HttpSession中:如果你将数据当作一个大的对象图存储在HttpSession中,应用服务器每次将不得不处理整个HttpSession对象。这将迫使Java序列化和增加计算开销。由于序列化的开销,随着存储在HttpSession对象中数据对象的增大,系统的吞吐量将会下降。
l 用完后释放HttpSession:当不在使用HttpSession时,使用HttpSession.invalidate()方法使sesion失效。
l 设置超时值:一个servlet引擎有一个默认的超时值。如果你不删除session或者一直把session用到它超时的时候,servlet引擎将把session从内存中删除。由于在内存和垃圾收集上的开销,session的超时值越大,它对系统弹性和性能的影响也越大。试着将session的超时值设置的尽可能低。
技术4:使用gzip压缩
压缩是删除冗余信息的作法,用尽可能小的空间描述你的信息。使用gzip(GNU zip)压缩文档能有效地减少下载HTML文件的时间。你的信息量越小,它们被送出的速度越快。因此,如果你压缩了由你web应用产生的内容,它到达用户并显示在用户屏幕上的速度就越快。不是任何浏览器都支持gzip压缩的,但检查一个浏览器是否支持它并发送gzip压缩内容到浏览器是很容易的事情。下边的代码段说明了如何发送压缩的内容。

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{

OutputStream out = null

// Check the Accepting-Encoding header from the HTTP request.
// If the header includes gzip, choose GZIP.
// If the header includes compress, choose ZIP.
// Otherwise choose no compression.

String encoding = request.getHeader("Accept-Encoding");

if (encoding != null && encoding.indexOf("gzip") != -1)
{
response.setHeader("Content-Encoding" , "gzip");
out = new GZIPOutputStream(response.getOutputStream());
}
else if (encoding != null && encoding.indexOf("compress") != -1)
{
response.setHeader("Content-Encoding" , "compress");
out = new ZIPOutputStream(response.getOutputStream());
}
else
{
out = response.getOutputStream();

}
...
...
}

技术5:不要使用SingleThreadModel
SingleThreadModel保证servlet一次仅处理一个请求。如果一个servlet实现了这个接口,servlet引擎将为每个新的请求创建一个单独的servlet实例,这将引起大量的系统开销。如果你需要解决线程安全问题,请使用其他的办法替代这个接口。SingleThreadModel在Servlet 2.4中是不再提倡使用。
技术6:使用线程池
servlet引擎为每个请求创建一个单独的线程,将该线程指派给service()方法,然后在service()方法执行完后删除该线程。默认情况下,servlet引擎可能为每个请求创建一个新的线程。由于创建和删除线程的开销是很昂贵的,于是这种默认行为降低了系统的性能。我们可以使用线程池来提高性能。根据预期的并发用户数量,配置一个线程池,设置好线程池里的线程数量的最小和最大值以及增长的最小和最大值。起初,servlet引擎创建一个线程数与配置中的最小线程数量相等的线程池。然后servlet引擎把池中的一个线程指派给一个请求而不是每次都创建新的线程,完成操作之后,servlet引擎把线程放回到线程池中。使用线程池,性能可以显著地提高。如果需要,根据线程的最大数和增长数,可以创建更多的线程。
技术7:选择正确的包括机制
在JSP页面中,有两中方式可以包括文件:包括指令(<%@ include file="test.jsp" %>)和包括动作()。包括指令在编译阶段包括一个指定文件的内容;例如,当一个页面编译成一个servlet时。包括动作是指在请求阶段包括文件内容;例如,当一个用户请求一个页面时。包括指令要比包括动作快些。因此除非被包括的文件经常变动,否则使用包括指令将会获得更好的性能。
技术8:在useBean动作中使用合适的范围
使用JSP页面最强大方式之一是和JavaBean组件协同工作。JavaBean使用标签可以嵌入到JSP页面中。语法如下:
"package.className" type="typeName">

scope属性说明了bean的可见范围。scope属性的默认值是page。你应该根据你应用的需求选择正确的范围,否则它将影响应用的性能。
例如,如果你需要一个专用于某些请求的对象,但是你把范围设置成了session,那么那个对象将在请求结束之后还保留在内存中。它将一直保留在内存中除非你明确地把它从内存中删除、使session无效或session超时。如果你没有选择正确的范围属性,由于内存和垃圾收集的开销将会影响性能。因此为对象设置合适的范围并在用完它们之后立即删除。
杂项技术
l 避免字符串连接:由于String对象是不可变对象,使用“+”操作符将会导致创建大量的零时对象。你使用的“+”越多,产出的零时对象就越多,这将影响性能。当你需要连接字符串时,使用StringBuffer替代“+”操作。
l 避免使用System.out.println:System.out.println同步处理磁盘输入/输出,这大大地降低了系统吞吐量。尽可能地避免使用System.out.println。尽管有很多成熟的调试工具可以用,但有时System.out.println为了跟踪、或调试的情况下依然很有用。你应该配置System.out.println仅在错误和调试阶段打开它。使用final Boolean型的变量,当配置成false时,在编译阶段完成优化检查和执行跟踪输出。
l ServletOutputStream 与 PrintWriter比较:由于字符输出流和把数据编码成字节,使用PrintWriter引入了小的性能开销。因此,PrintWriter应该用在所有的字符集都正确地转换做完之后。另一方面,当你知道你的servlet仅返回二进制数据,使用ServletOutputStream,因为servlet容器不编码二进制数据,这样你就能消除字符集转换开销。
总结
本文的目的是展示给你一些实践的和已经证实的用于提高servlet和JSP性能的性能优化技术,这些将提高你的J2EE应用的整体性能。下一步应该观察其他相关技术的性能调整,如EJB、JMS和JDBC等。

Posted by@rimen at 2005 年 12 月 01 日 | Comments (1) | TrackBack

2005 年 11 月 28 日

最近工作有点忙

经过网友的提醒,今天猛然发现自己的blog有10天没有更新了.看了最近的工作确实有点忙.由于短信项目在整个的系统词典里面还是一个新的东西,所有很多东西需要去学习,看了程序的悲哀就在这里,永远被学不玩的东西困扰着,慢慢的变得没有了自己的想法,没有了自己的方向.
不管这么样,还是很谢谢大家多我工作的支持,很多人在关注着soosoo的进展,说实话,现在个人的精力实在有限,加上在这个现实社会中,有的时候光靠自己的兴趣还不一定行的通,很多现实的问题在困扰着想我一样的每一个程序员,一直发至内心不甘,想通过自己的双手和智慧来改变自己的命运.着也算是对自己年轻生命的责任吧.
也许工作忙只不过是一个冠冕堂皇的理由来给自己的懒惰做辩解.可是在我的生活中,确实有了很多因素给自己不同的压力,才有了心态上的一种波动,我会经自己的努力去改变自己,让自己适应这个环境,给自己的人生打好每一个逗号和句号.

Posted by@rimen at 2005 年 11 月 28 日 | Comments (0) | TrackBack

2005 年 11 月 17 日

关于weblogic对jndi的spi接口实现问题

sun提供jndi规范,通过这个接口规范把名称和对象联系起来。但是怎么实现在于中间厂商对这个spi接口的实现。
先看下面的代码:
public static void main(String args[]){
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
properties.put(Context.PROVIDER_URL,"t3://server1:7001");
properties.put(Context.SECURITY_PRINCIPAL,"username");
properties.put(Context.SECURITY_CREDENTIALS,"password");
Properties properties1 = new Properties();
properties1.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
properties1.put(Context.PROVIDER_URL,"t3://server2:7001");
// properties1.put(Context.SECURITY_PRINCIPAL,null);
// properties1.put(Context.SECURITY_CREDENTIALS,null);
try {
System.out.println("新建context="+properties);
Context context2= new InitialContext(properties1);
Context context=new InitialContext(properties);
context.close();
System.out.println("新建context="+properties1);
context2.lookup("cn/com/soosoo/test");
context2.close();

} catch (NamingException e) {
e.printStackTrace();
}
}

大家可能目前还不知道这段代码有什么问题,运行应该也没有什么问题,很不幸的告诉你,运行的结果会出现一个security异常。大概描述是说在context2.lookup 的时候,weblogic的spi实现会拿context中设置的用户去连接context2中的服务器。很多人会想不通这一点,说实话我自己开始都不是很相信这事实。因为在context2之前把context还关了。为什么会出现这中情况。
目前weblogic对jndi接口实现的时候,把上下文环境的用户存入一个堆栈。当利用上下文环境进行查找对象的时候,如果发现其没有用户名和密码,weblogic会在当前内存栈里面pop出来进行连接。这样当如果没有conetxt这个上下文环境的初始化设置的时候,就不会出现任何问题。解决这情况的有两个途径。1,所有的context都设置用户和密码,2,在进行context操作之前完成所有context2的获取工作。也就是
System.out.println("新建context="+properties);
Context context2= new InitialContext(properties1);
System.out.println("新建context="+properties1);
context2.lookup("cn/com/soosoo/test");
context2.close();
Context context=new InitialContext(properties);
context.close();

特别是在大家做j2ee应用集成时候需要注意这个问题,因为你可能会出现多个业务系统服务的调用。

Posted by@rimen at 2005 年 11 月 17 日 | Comments (0) | TrackBack

2005 年 11 月 13 日

jvm装入原理

操作系统装入jvm是通过jdk中java.exe来完成,通过下面4步来完成jvm环境.
1.创建jvm装载环境和配置
2.装载jvm.dll
3.初始化jvm.dll并挂界到JNIENV(JNI调用接口)实例
4.调用JNIEnv实例装载并处理class类。

在我们运行和调试java程序的时候,经常会提到一个jvm的概念.jvm是java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间.
首先来说一下jdk这个东西,不管你是初学者还是高手,是j2ee程序员还是j2se程序员,jdk总是在帮我们做一些事情.我们在了解java之前首先大师们会给我们提供说jdk这个东西.它在java整个体系中充当着什么角色呢?我很惊叹sun大师们设计天才,能把一个如此完整的体系结构化的如此完美.jdk在这个体系中充当一个生产加工中心,产生所有的数据输出,是所有指令和战略的执行中心.本身它提供了java的完整方案,可以开发目前java能支持的所有应用和系统程序.这里说一个问题,大家会问,那为什么还有j2me,j2ee这些东西,这两个东西目的很简单,分别用来简化各自领域内的开发和构建过程.jdk除了jvm之外,还有一些核心的API,集成API,用户工具,开发技术,开发工具和API等组成
好了,废话说了那么多,来点于主题相关的东西吧.jvm在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的java运行环境,因此也就虚拟计算机. 操作系统装入jvm是通过jdk中java.exe来完成,通过下面4步来完成jvm环境.
1.创建jvm装载环境和配置
2.装载jvm.dll
3.初始化jvm.dll并挂界到JNIENV(JNI调用接口)实例
4.调用JNIEnv实例装载并处理class类。
一.jvm装入环境,jvm提供的方式是操作系统的动态连接文件.既然是文件那就一个装入路径的问题,java是怎么找这个路径的呢?当你在调用java test的时候,操作系统会在path下在你的java.exe程序,java.exe就通过下面一个过程来确定jvm的路径和相关的参数配置了.下面基于windows的实现的分析.
  首先查找jre路径,java是通过GetApplicationHome api来获得当前的java.exe绝对路径,c:\j2sdk1.4.2_09\bin\java.exe,那么它会截取到绝对路径c:\j2sdk1.4.2_09\,判断c:\j2sdk1.4.2_09\bin\java.dll文件是否存在,如果存在就把c:\j2sdk1.4.2_09\作为jre路径,如果不存在则判断c:\j2sdk1.4.2_09\jre\bin\java.dll是否存在,如果存在这c:\j2sdk1.4.2_09\jre作为jre路径.如果不存在调用GetPublicJREHome查HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”\JavaHome的路径为jre路径。
  然后装载jvm.cfg文件JRE路径+\lib+\ARCH(CPU构架)+\jvm.cfgARCH(CPU构架)的判断是通过java_md.c中GetArch函数判断的,该函数中windows平台只有两种情况:WIN64的‘ia64’,其他情况都为‘i386’。以我的为例:C:\j2sdk1.4.2_09\jre\lib\i386\jvm.cfg.主要的内容如下:
-client KNOWN
-server KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR
在我们的jdk目录中jre\bin\server和jre\bin\client都有jvm.dll文件存在,而java正是通过jvm.cfg配置文件来管理这些不同版本的jvm.dll的.通过文件我们可以定义目前jdk中支持那些jvm,前面部分(client)是jvm名称,后面是参数,KNOWN表示jvm存在,ALIASED_TO表示给别的jvm取一个别名,WARN表示不存在时找一个jvm替代,ERROR表示不存在抛出异常.在运行java XXX是,java.exe会通过CheckJvmType来检查当前的jvm类型,java可以通过两种参数的方式来指定具体的jvm类型,一种按照jvm.cfg文件中的jvm名称指定,第二种方法是直接指定,它们执行的方法分别是“java -J”、“java -XXaltjvm=”或“java -J-XXaltjvm=”。如果是第一种参数传递方式,CheckJvmType函数会取参数‘-J’后面的jvm名称,然后从已知的jvm配置参数中查找如果找到同名的则去掉该jvm名称前的‘-’直接返回该值;而第二种方法,会直接返回“-XXaltjvm=”或“-J-XXaltjvm=”后面的jvm类型名称;如果在运行java时未指定上面两种方法中的任一一种参数,CheckJvmType会取配置文件中第一个配置中的jvm名称,去掉名称前面的‘-’返回该值。CheckJvmType函数的这个返回值会在下面的函数中汇同jre路径组合成jvm.dll的绝对路径。如果没有指定这会使用jvm.cfg中第一个定义的jvm.可以通过set _JAVA_LAUNCHER_DEBUG=1在控制台上测试.
最后获得jvm.dll的路径,JRE路径+\bin+\jvm类型字符串+\jvm.dll就是jvm的文件路径了,但是如果在调用java程序时用-XXaltjvm=参数指定的路径path,就直接用path+\jvm.dll文件做为jvm.dll的文件路径.
  二:装载jvm.dll
通过第一步已经找到了jvm的路径,java通过LoadJavaVM来装入jvm.dll文件.装入工作很简单就是调用windows API函数:
LoadLibrary装载jvm.dll动态连接库.然后把jvm.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。jvm.dll的装载工作宣告完成。
  三:初始化jvm,获得本地调用接口,这样就可以在java中调用jvm的函数了.调用InvocationFunctions->CreateJavaVM也就是jvm中JNI_CreateJavaVM方法获得JNIEnv结构的实例.
  四:运行java程序.
java程序有两种方式一种是jar包,一种是class. 运行jar,java -jar XXX.jar运行的时候,java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例然后调用java类java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes("Main-Class")的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。main函数直接调用java.c中LoadClass方法装载该类。如果是执行class方法。main函数直接调用java.c中LoadClass方法装载该类。

然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中
“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的
CallStaticVoidMethod方法调用该java类的main方法。

Posted by@rimen at 2005 年 11 月 13 日 | Comments (1) | TrackBack

2005 年 11 月 03 日

环境命名上下文environment naming context (ENC)来定位j2ee资源

java:comp/env 是环境命名上下文environment naming context (ENC)
是在EJB规范1.1以后引入的,引入这个是为了解决原来JNDI查找所引起的冲突问题,也是为了提高EJB或者J2EE应用的移植性。ENC是一个引用,引用是用于定位企业应用程序的外部资源的逻辑名。引用是在应用程序部署描述符文件中定义的。在部署时,引用被绑定到目标可操作环境中资源的物理位置(JNDI名)。使用ENC是把对其它资源的JNDI查找的硬编码解脱出来,通过配置这个引用可以在不修改代码的情况下,将引用指向不同的EJB(JNDI)。 在J2EE中的引用常用的有:
---------JDBC 数据源引用在java:comp/env/jdbc 子上下文中声明
---------JMS 连接工厂在java:comp/env/jms 子上下文中声明
---------JavaMail 连接工厂在java:comp/env/mail 子上下文中声明
---------URL 连接工厂在 java:comp/env/url子上下文中声明

假如你写了一个EJB,获取datasource如:dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/DBPool");
那么在配置文件中进行资源映射时,在ejb-jar.xml中,

jdbc/DBPool
javax.sql.DataSource
Container

在weblogic-ejb-jar.xml中,


jdbc/DBPool
OraDataSource


实际服务器中的JNDI名字是OraDataSource,逻辑名jdbc/DBPool只是用来和它作映射的,这样做的好处是为了提高可移植性,移植的时候只需要把配置文件改一下就可以,而应用程序可不用改动。


Posted by@rimen at 2005 年 11 月 03 日 | Comments (1) | TrackBack

2005 年 11 月 01 日

gc和finalize关系

gc只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(当使用JNI技术时,可能会在栈上分配内存,例如java调用c程序,而该c程序使用malloc分配内存时).因此,如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行内存回收就要靠finalize().
举个例子来说,当java 调用非java方法时(这种方法可能是c或是c++的),在非java代码内部也许调用了c的malloc()函数来分配内存,而且除非调用那个了free() 否则不会释放内存(因为free()是c的函数),这个时候要进行释放内存的工作,gc是不起作用的,因而需要在finalize()内部的一个固有方法调用它(free()).
finalize的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.

Posted by@rimen at 2005 年 11 月 01 日 | Comments (0) | TrackBack

2005 年 10 月 25 日

java中的字符编码处理

做java已经两年,一直在规范和框架中挣扎,往往很少去考虑一些细节上的原理.以为很多在规范中屏蔽了.今天由于一个用户的需求引发了一个中文处理的问题.问题是这样的:用户要在一个发布一篇即有简体也有繁体的文章.由于当前系统采用的GBK的编码,发现有些问题,开始马上想到换成unicode编码,还是乱码.晕......,下定决心把这个东东搞清楚...........

上google,查出一大堆这类问题,有点描述太复杂,有的描述太过于个例化.最后在csdn上找到了AbnerChai 深入Java中文问题及最优解决方法的文章,非常详细,但是语言上有点罗唆.
其实说白了就是 显示--os--java处理(java程序,web容器,java组件)--jvm(jdk)--os--现实设备.对于核心的jvm来说,把载入的文件class一定是unicode的(当然我说的jvm应该是国际版).剩下的问题就是一个输入输出的问题了.还有就是在jvm前后进行编码转换时的编码源和编码目标怎么确定问题.
首先了解一下目前国际化编码:
1.ISO-8859 对于ISO-8859系列的字符集都想象成一个:2^8 = 16 * 16 = 256个格子的棋盘,这样所有的西文字符(英文)用这样一个16×16的坐标系就基本可以覆盖全了。而英文实际上只用其中小于128(\x80)的部分就够了。利用大于128部分的空间的不同定义规则形成了真对其他欧洲语言的扩展字符集:ISO-8859-2 ISO-8859-4等……

ISO-8859-1
ISO-8859-7
其他语言

英文 其他西欧字符
ōē
2.GB2312 BIG5 SJIS等多字节字符集(MultiByte Charsets):
对于亚洲语言来说:汉字这么多,用这么一个256格的小棋盘肯定放不下,所以要区别成千上万的汉字解决办法就是用2个字节(坐标)来定位一个“字”在棋盘上的位置,将以上规则做一个扩展:

如果第1个字符是小于128(\x80)的仍和英文字符集编码方式保持兼容;
如果第1个字符是大于128(\x80)的,就当成是汉字的第1个字节,这个自己和后面紧跟的1个字节组成一个汉字;
其结果相当于在位于128以上的小棋格里每个小棋格又划分出了一个16×16的小棋盘。这样一个棋盘中的格子数(可能容纳的字符数)就变成了128 + 128 * 256。按照类似的方式有了简体中文的GB2312标准,繁体中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大约有六仟多个常用简体汉字。
………………………………

由此可以看出,所有这些从ASCII扩展式的编码方式中:英文部分都是兼容的,但扩展部分的编码方式是不兼容的,虽然很多字在3种体系中写法一致(比如“中文”这2个字)但在相应字符集中的坐标不一致,所以GB2312编写的页面用BIG5看就变得面目全非了。而且有时候经常在浏览其他非英语国家的页面时(比如包含有德语的人名时)经常出现奇怪的汉字,其实就是扩展位的编码冲突造成的。

3.GBK和GB18030理解成一个小UNICODE:GBK字符集是GB2312的扩展(K),GBK里大约有贰万玖仟多个字符,除了保持和GB2312兼容外,繁体中文字,甚至连日文的假名字符也能显示。而GB18030-2000则是一个更复杂的字符集,采用变长字节的编码方式,能够支持更多的字符。关于汉字的编码方式比较

详细的定义规范可以参考:

http://www.unihan.com.cn/cjk/ana17.htm


ASCII(英文) ==> 西欧文字 ==> 东欧字符集(俄文,希腊语等) ==> 东亚字符集(GB2312 BIG5 SJIS等)==> 扩展字符集GBK GB18030这个发展过程基本上也反映了字符集标准的发展过程,但这么随着时间的推移,尤其是互联网让跨语言的信息的交互变得越来越多的时候,太多多针对本地语言的编码标准的出现导致一个应用程序的国际化变得成本非常高。尤其是你要编写一个同时包含法文和简体中文的文档,这时候一般都会想到要是用一个通用的字符集能够显示所有语言的所有文字就好了,而且这样做应用也能够比较方便的国际化,为了达到这个目标,即使应用牺牲一些空间和程序效率也是非常值得的。UNICODE就是这样一个通用的解决方案。

4.UNICODE双字节字符集:
所以你可以把UNICODE想象成这样:让所有的字符(包括英文)都用2个字节(2个8位)表示,这样就有了一个2^(8*2) = 256 * 256 = 65536个格子的大棋盘。在这个棋盘中,这样中(简繁)日韩(还包括越南)文字作为CJK字符集都放在一定的区位内,为了减少重复,各种语言中写法一样的字共享一个“棋格”。

接下来就是怎么看java处理编码问题了.
我们知道jvm处理的字节码一定是unicode,对于没有产生jvm之外的输入和输出的话,任何处理都不会带来乱码问题.例如javaBean ,ejb等不会产生编码问题.
常用的输入:
用户和java程序交互(java组件解析或转换器编码)
jsp/servlet 等客户端的输入(通过web服务器获得传输的信息默认编码)
数据库 通过jdbc获得默认的信息编码
jsp/java等源文件到字节码(unicode)的输入(有jsp编译器和javac来转换源文件的编码)
第一种情况:就是java程序运行在console上等待用的信息输入,这是java类会默认用file.encoding编码格式对用户输入的串进行编码并转化为unicode保存入内存(用户可以设置输入流的编码格式,这可以理解java组件做转换器)。
编码来源:默认os编码,或者用户在类中自己设定.编码目标:jvm处理编码unicode.这里编码源必须要能包含输入的字符集.
第二种情况:就是jvm装入servlet进行接受表单输入的值和URL中传入的值,此时如果程序中没有设定接受参数时采用的编码格式,则WEB容器会默认采用ISO-8859-1编码格式来接受传入的值并在JVM中转化为UNICODE格式的保存在WEB容器的内存中。
编码来源:web容器,或者用filter 进行request.setCharacterEncoding(),编码目标:jvm处理编码unicode.这里的编码源必须能包含输入的字符集.
第三种情况:数据库的JDBC驱动程序,默认的在Java程序和数据库之间传递数据都是以ISO-8859-1为默认编码格式的,所以,我们的程序在向数据库内读取数据时会把数据转化成unicode.
编码来源:默认jdbc编码 iso,jdbc设定的编码格式.编码目标:jvm处理的unicode.这里的源必须能包含数据存储的字符集.
第四种情况:java通过平台编写,获得记事本编写,有个文件编码,一般默认系统编码,有的工具可以改.通过javac进行编译生成class文件unicode编码.jvm可以处理unicode的文件.jsp编译器编译jsp时也是一样,可以用jsp的pageEncoding 设置jsp文件的源编码.
编码来源:有javac获得系统默认或者-encoding参数来确定编码源.编码目标:jvm处理的class文件.jsp只是多了一个编译成java的过程.原理一样.这里的编码源设置必须保证能覆盖文件种的字符集.

综合上面的输入情况,实际细心的读者会发现,这里有两种情况,一种是文件,一种是字节流,文件的话编码可以设置在文件头,例如jsp,xml等,字节流必须在java处理之前指定能覆盖其字符集的编码.(注意一定是字节流,字符流是已经进行了编码的).只要保证java处理的编码源正确一定可以能让java正确处理.

输出的情况相对简单的多.jvm出来的信息是unicode,你要怎么处理只要能让os的编码集认识,现实一定不会有问题.
例如.console上java会以file.encoding格式传会给操作系统
jsp客户设置charSet:默认按ISO-8859-1编码
jdbc可以设置其输入编码:默认按ISO-8859-1编码

Posted by@rimen at 2005 年 10 月 25 日 | Comments (4) | TrackBack

2005 年 10 月 23 日

关于Lucence中的索引优化

  车东在他的文章中提到,通过提供IndexWriter的merge因子属性可以提供索引的速度.从原理入手,进行lucence代码的分析,然后通过测试数据进行实际应用中的优化.

先看下面的代码:
private final void maybeMergeSegments() throws IOException {
long targetMergeDocs = minMergeDocs;
while (targetMergeDocs <= maxMergeDocs) {
// find segments smaller than current target size
int minSegment = segmentInfos.size();
int mergeDocs = 0;
while (--minSegment >= 0) {
SegmentInfo si = segmentInfos.info(minSegment);
if (si.docCount >= targetMergeDocs)
break;
mergeDocs += si.docCount;
}

if (mergeDocs >= targetMergeDocs) // found a merge to do
mergeSegments(minSegment + 1);
else
break;

targetMergeDocs *= mergeFactor; // increase target size
}
}
一看就知道,当segmentInfos中的merge的文档数小于targetMergeDocs是,系统不会进行merge,而targetMergeDocs由minMergeDocs和mergeFactor决定.SegmentInfo 如果是单个索引的话,si.docCount为1,如果此时minMergeDocs为1,也就是targetMergeDocs为1,系统会进行merge,这样在效率上回大大降低.minMergeDocs>1的话,决定merge的大小还是minMergeDocs,targetMergeDocs *= mergeFactor无效,可以说mergeFactor只对批量索引有效,si.docCount>1的情况.
如果是批量索引,当si.docCount>minMergeDocs时,minMergeDocs和targetMergeDocs 决定了merge的数量.
  在我的优化过程中,由于采用的是单一索引,所以我调整mergeFactor带来的性能上的差异不大,可以看成是系统带来的误差.而我改变minMergeDocs获得的性能可以从下面的数据看到:
以10000条记录为测试案例
minMergeDocs   对于运行的时间ms
default (10)    times=32707
20 times=21191
30 times=18476
40 times=16334
50 times=16744
100 times=13138
200 times=10125
400 times=10234
800 times=10015
1600 times=11256
3200 out of memory
从上面的数据来看,在100~200之间性能和存储比较优.当minMergeDocs 大到一定的程度,由于在内存调度上的问题,系统可能有更大量的访问IO.建议在定制lucence时,对与单一的索引可以把minMergeDocs 设成100左右以提供性能.而车东给出的说法并不完全.

Posted by@rimen at 2005 年 10 月 23 日 | Comments (0) | TrackBack

2005 年 10 月 13 日

三种ejb的生命周期

在做企业业务继承的时候,通常利用session进行提供同步的远程过程服务.通过EAI等Q组件,实现企业级的数据总线,把应用之间的网状结构关系进行星型化..利用mdb从jms(Q)服务器上获取相应的消息服务.总之在企业信息化的过程中,基于j2ee的应用整合,ejb提供了一种良好的方法,当然你也可以选择websevice,但是在网络结构和实时处理效率上还是有很大的差别.下面详细描述以下三种ejb的生命周期,在开发中和利用他的容器回调功能,来注入和回收外部的各种资源。

1 session Bean (有状态)
有状态会话Bean实例有三种状态,不存在,就绪,和钝化。
lifetime
客户端调用home接口的create 方法,ejb容器实例化一个Bean并调用setSesssionContext

ejbCreate方法,使得Bean处于就绪状态。然后客户就可以使用其商业方法了。ejb容器对Bean的生命周期

进行管理,一般会对最少使用的EJB进行钝化,当客户在使用这个EJB时,容器会进行激活,这个过程对客

户来说是透明的。当用户调用remove方法,容器调用ejbRemove方法,ejb生命周期结束.
无状态会话Bean实例就两种状态,不存在和就绪.
lifetime

客户端调用home接口的create方法,如果不存在可用的实例.jb容器实例化一个Bean并调用

setSesssionContext ejbCreate方法.当客户调用remove方法之后,ejb容器则调用ejbRemove的方法, 
ejb生命周期结束.

2 实体bean
实体bean有三种状态,不存在,在pool中,就绪
lifetime

ejb容器创建实例时调用setEntityContext,把容器的上下文传到bean组件中.实例化之后bean

会移到池中,此时ejb没有和任何的实体对象进行关联,所有的bean实例是一样,容器会指派它和具体的

实体标示关联,进入就绪状态。有两种方法使得一个实体bean从池化进入到就绪状态,一是客户端使用

create方法,使得ejb容器调用ejbCreate and ejbPostCreate 方法,二是容器调用ejbActivate 方法,

这对客户来说是透明的,只有当实体bean处于就绪状态时,才能调用其商业方法。同样如果实体bean要从

就绪进行池化也有两种方法,一是客户端调用remove方法,容器调用ejbRemove;二是容器ejbPassivate方法。
bmp和cmp,在bean实例从池化到就绪时,对于bmp的实体bean,容器不会自动设置primary key.因此ejbCreate and ejbActivate 需要获得这个primary key ,如果这个key非法,ejbLoad and ejbStore methods 不能同步实体变量到数据库。ejbCreate 通过参数传入,ejbActivate 通过id = (String)context.getPrimaryKey();在pool状态,这些需要持久化的实体变量则不需要,在ejbPasssivate 中把它赋值null。unsetEntityContext,
bean生命周期结束的时候,调用
3 mdb 消息bean
    消息bean就两种状态:不存在和就绪
lifetime

就像sessionless session bean,容器在实例化bean的时候,调用setMessageDrivenContext,ebjCreate. 调用ejbRemove方法结束生命周期。当消息到达的时候Onmessage方法。因此可以mdb是一种jms客户端企业级组件。

Posted by@rimen at 2005 年 10 月 13 日 | Comments (1) | TrackBack

2005 年 10 月 05 日

深入理解abstract class和interface

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

理解抽象类

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。


从语法定义层面看abstract class和interface

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

使用abstract class的方式定义Demo抽象类的方式如下:

abstract class Demo {
abstract void method1();
abstract void method2();

使用interface的方式定义Demo抽象类的方式如下:

interface Demo {
void method1();
void method2();

}

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。


从设计理念层面看abstract class和interface

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用abstract class方式定义Door:

abstract class Door {
abstract void open();
abstract void close();
}


使用interface方式定义Door:


interface Door {
void open();
void close();
}


其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。

解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}


或者

interface Door {
void open();
void close();
void alarm();
}


那么具有报警功能的AlarmDoor的定义方式如下:

class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}


或者

class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:

abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}


这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

结论

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。

Posted by@rimen at 2005 年 10 月 05 日 | Comments (0) | TrackBack

2005 年 10 月 04 日

avalon ioc容器的一个实现

大家只要用过spring 的人或多或少对对ioc这个概念有的理解.他是一个设计模式,有始终不同的实现方式.spring同时javaBean set方法注入Bean组件.
Apache的Avalon是一个包括核心框架、工具、组件和容器的面向组件编程(COP)的完整开发平台。通过使用关键设计模式,如反向控制模式(IoC)和分离考虑模(SoC),Avalon实现了传统OOP框架的一些优点: 1.没有执行锁 2.组件之间低耦合 3.管理组件生命周期 4.配置管理和易用的API 5.组件元数据框架和工具 6.服务相关的管理独立的、J2EE或Web环境的嵌入式容器 在COP方面,可重用的组件能够被组合到容器中,以提供应用程序模块。模块可以依次使用来创建你所需要的,从客户桌面应用程序,到FTP服务器,到Web服务,等等。Avalon提供各种基本组件和缺省的应用程序模块,帮助你快速的建立你自己的应用程序解决方案。

Posted by@rimen at 2005 年 10 月 04 日 | Comments (0) | TrackBack

2005 年 09 月 29 日

Java XML API 漫谈

转载自forum.hibernate.org.cn, author:robbin


在IBM的developerWorks上有几篇非常优秀的关于Java XML API的评测文章,它们是:

http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml

http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml

http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part2/index.shtml

http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part1/index.shtml

对这几篇文章我想说的就是“吐血推荐”

Java的XML API这几篇文章该讲的都讲到了,我只想补充几点:

一、Crimson和Xerces恩仇录
Crimson来自于Sun捐赠给Apache的ProjectX项目,Xerces来自IBM捐赠给Apache的XML4J项目,结果Xerces胜出,成了Apache XML小组全力开发的XML API,而Crimon已经早就不做了,如今Xerces名满天下,到处都是在用Xerces DOM和SAX解析器,只有Sun不服气,非要在JDK1.4里面使用过时的Crimson,让人感觉像是在赌气一样,真是让人可怜又可气!不过IBM发行JDK用的XML 解析器自然是Xerces。

由于JDK的Class Loader的优先级关系,当你采用JAXP编写XML程序的时候,即使把Xerces包引入CLASSPATH,JDK还是会顽固的使用Crimson,这一点通过打开JVM的verbose参数可以观察到。不过JDK也允许你采用其它的解析器,因此我们可以通过在JRE\lib\目录下建一个jaxp.properties的文件,来替换解析器,jaxp.properties内容如下:

引用:
javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
javax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl

这样就可以使用Xerces,当然你必须还是要把Xerces包放到CLASSPATH下。

二、JAXP的姗姗来迟
Sun在XML领域总是后知后觉,等到Sun重视XML的时候,XML的API早就满天 飞了,尤其是IBM具有非常大的领先优势。不过Sun是规范的制订者,于是参考W3C的标准制订了JAXP规范。JAXP不像Xerces和Crimon那样,它只是一个spec,本身是不做任何事情的,它的作用就是提出一个统一的接口,让其它的XML API都来遵循JAXP编程,那么用JAXP写出来的程序,底层的API可以任意切换。

具体来说JAXP包括了几个工厂类,这就是JDK1.4里面的javax.xml.parsers 包,用来寻找符合DOM标准的XML API实现类的位置;此外JAXP还包括一整套interface,这就是JDK1.4里面的org.w3c.dom那几个包。工厂类负责加载DOM的实现类。那么加载的规则是什么呢?

我是通过阅读JAXP的源代码知道的,工厂类首先会根据java命令行传入的参数进行寻找,然后在根据JRE\lib\jaxp.properties中定义的实现类寻找,最后什么都找不到的话,就用Crimson。注意Crimons是由Bootstrap Class Loader来load的,如果你不通过上面两个方法来改变工厂的寻找顺序,那么铁定用Crimson了

三、 DOM解析器和DOM API
当你严格采用JAXP编程的时候,是遵循W3C的DOm标准的,那么在JAXP底层你实际上可以任意切换不同的DOM实现,例如Xerces,或者Crimon,再或者其它,切换方法就是配置jaxp.properties。因此JAXP就是一些标准接口而已。

而Xerces和Crimon也不单单是一个DOM实现那么简单,他们本身实际上也包含SAX解析器和DOM解析器。所以一个JAXP程序下面有如下层次:

引用:

JAXP应用程序 -> JAXP接口 -> Xerces DOM实现 -> Xerces DOM/SAX 解析器

只要你用JAXP编程,那么你就可以切换到Crimson上来

引用:

JAXP应用程序 -> JAXP接口 -> Crimson DOM实现 -> Crimson DOM/SAX 解析器

另外你也可以这样来做:

引用:

JAXP应用程序 -> JAXP接口 -> Crimson DOM实现 -> Xerces DOM/SAX 解析器

不过如果你的程序不安装JAXP来写,那么就没有办法切换不同的DOM实现了。

四、不是标准的dom4j和jdom
W3C的DOM标准API难用的让人想撞墙,于是有一帮人开发Java专用的XML API目的是为了便于使用,这就是jdom的由来,开发到一半的时候,另一部分人又分了出来,他们有自己的想法,于是他们就去开发dom4j,形成了今天这样两个API,至于他们之间的性能,功能之比较看看上面我推荐的文章就知道了,jdom全面惨败。

jdom 相当于上面的 JAXP接口 + Xerces DOM实现部分,它本身没有解析器,它可以使用Xerces或者Crimson的解析器,就是这样:

引用:

jdom应用程序 -> jdom API -> Xerces/Crimson解析器

dom4j 和jdom类似,不过他自己绑定了一个叫做Alfred2的解析器,功能不是很全,但是速度很快,当没有其它的解析器的时候,dom4j将使用Alfred2解析器,如下:

引用:

dom4j应用程序 -> dom4j API -> Xerces/Crimson解析器

或者

引用:

dom4j应用程序 -> dom4j API -> Alfred2解析器

你在SF上下载的dom4j.jar是不含 Alfred2解析器的,而dom4j-full.jar包含了 Alfred2解析器,在这种情况下,实际上你什么也不需要,光是一个dom4j-full.jar就全部都包括了。

因此可以看出采用dom4j/jdom编写的应用程序,已经不具备可移植性了。

五、小插曲
Sun是JAXP标准的制订者,甚至很执著的在JDK1.4里面绑定Crimson DOM实现和解析器,然后可笑的是,Sun自己的JAXM RI竟然不是用JAXP写出来的,而是dom4j,制订标准让大家遵守,自己却监守自盗,这未免太说不过去了吧!

BTW: Hibernate也用的是dom4j来读取XML配置文件,如今已经越来越多的程序纷纷采用dom4j,如果你不是那么在乎可移植性,我强烈建议你采用dom4j。


--------------------------------------------------------------------------------
Reference Java XML API 漫谈
http://www.dannyzhu.com:8000/Java/JavaXMLAPIAI

Posted by@rimen at 2005 年 09 月 29 日 | Comments (0) | TrackBack

2005 年 09 月 27 日

通过私有构造函数强化不可实例化的能力

Item 3: Enforce noninstantiability with a private constructor


如果一个类缺少显式的构造函数,编译器会自动提供一个公有的、无参数的默认构造函数(default constructor)。

我们只要让这个类包含单个显式的私有构造函数,则它就不可被实例化了,而企图通过将一个类做成抽象类来强制该类不可被实例化是行不通的。
A class can be made noninstantiable by including a single explicit private constructor:Attempting to enforce noninstantiability by making a class abstract does not work.

例如://noninstantiable utility class
public class UtilityClass{
//suppress default constructor for noninstantiability
private UtilityClass(){
//this constructor will never be invoked
}
...
}

说明:工具类(UtilityCLass)指只包含静态方法和静态域的类,它不希望被实例化因为对它进行实例化没有任何意义。


这种做法的一个副作用,是它使得这个类不能被子类化,因为子类将找不到一个可访问的超类的构造函数。

As a side effect, this idiom also prevents the class from being subclassed. All constructors must invoke an accessible superclass constructor, explicitly or implicitly, and a subclass would have no accessible constructor to invoke.

Posted by@rimen at 2005 年 09 月 27 日 | Comments (0) | TrackBack

2005 年 09 月 11 日

maven初验

一直以来,沉浸的ant的成就中,我也没有对项目资源的管理提出更高的要求,也许本身也是项目接触的原因。罱捎诠驹谧蚴褂胮ortal技术,而且是用开源的jetspeed.我当时听了一大跳,说那么大的企业级应用怎么能这么草率的选择开源的东西(虽然我对开源这东西很喜欢)。没有办法了,领导命令下来,做兵的只能埋头苦干了。一开始碰到的是项目的编译问题。说jetspeed是基于maven描述的


   晕~!maven是什么东西,看来真的是孤陋寡闻了。以前一直是eclipse的平台下开发,加上ant工具,对项目的创建,开发,发布,文档生成都很方便。第一次听到这东西很纳闷,心里骂到,这群人是不是吃了没事干啊。可是骂归骂,事情还得做。老方法,第一步google,第二步找到官方站点。我才奇怪,apache每天都上怎么就没有注意的这东东呢。咳,看来rimen我真的很懒了,懒得连举手之劳的事情都不做了~。言归正传,从实际的项目出发。我的目的很简单,就是能够建立jetspeed项目,能够让他在eclipse进行开发。在我心里就有了如下的问题:
 1.maven是什么东西
 2.maven和ant由什么区别,maven和现有的IDE工具有什么关系
 3.maven的主要过程和原理
 4.怎么利用maven进行项目的开发和管理
 经过一天的努力,查询了一些相关的资料,发现有价值的中文资料不多,最全的还是apache上的doc文档。不过都是老外的文字,要知道我最痛恨的就是那些仅有线条没有寓意的文字了。不过为了工作也为了自己,咳,没有办法了,重洋眉外一下了,大家不要骂我^_^。但是这里还是推荐一篇对初学者比较有价值的文章,matrix上的:http://www.matrix.org.cn/resource/article/43/43661_Maven.html 也是从英文翻译过来的,咳,国人啊,加油啊~!
 开始解开那几个问题了.
 第一个问题很简单,它就是一个工具,仅仅是一个工具,至于什么工具下面问题就可以体现了。它的原始想法就是为了简化jakarta螺旋式项目的构建,解决项目之间jar依赖的共享。往往伟大的发明都是来自于解决某些人的懒惰,看当初java就是,服了那些老外了,什么都想的出,我也很懒,我也发现在eclipse开发多个项目的时候管理jar之间的版本很麻烦,可是我就想不出这个东西,任命了。
 我相信大家只要开发过java程序的人,或多或少都会接触到ant的东东,怎么说呢,ant其实只能说一个脚本解析器,它对任务的处理还得依赖于外部的工具。这就不得不想到makefile这东西了,在以前开发c程序的时候,为了管理c项目的各种资源包括原文件和类库,对编译工具提供了一个make工具。我个人觉得ant就是make的java版本,错了莫怪。至于maven,那可不是一言两语可以概括的。不过我们可以重程序和源码中抽身出来,想象自己是个pm或者一个architecture来观察一个项目或一个软件产品。它提供很多的基础构件,特别是对j2ee系统,每个组件都可以看成一个宏观上的资源。我们从软件工厂的角度出发,它里面保护了机器设备(external jars,tools,plugins ),生产原料(jar,src,config file,databse),产品(ear,war,jar,doc,config file 等)。下面我们要管理这些东西,叫一个人(老总)取管理,我看他在高的工资也不会干,因为他知道钱有命赚没命花,同时也为了考虑可发展道路,他不会这么干的。所以在maven中引入了plugin的概念。这个plugin管理的东西是上面的说的构件,这样我们必须就有一东西来记录这些东西,不然它怎么知道这是不是它的管辖范围,这就是通过project.xml文件进行定义,它是基于pom元数据结合xml技术进行描述。当然有了被管理的对象,工厂必须有其业务规则,不然它也运作不起来,这就是通过plugin.jelly进行定义,利用jelly脚步语言进行构建每一个管理的目标。这样老总一句话(maven命令),加上目标名,他就会找到相应的plugin进行根据它定义的规则进行处理。这仅仅是从宏观上的描述,真的的原理还必须参考相关的文档进行。这里面提出几个概念大家可以注意一下,maven plugin,jelly脚本,pom元数据,软件工厂等。还有一点就是和目前流行的IDE工具例如eclipse,instillj,jbuider,netbeans IDE 等工具的关系。以eclipse为例,严格来说maven和eclipse应该是风马牛不相及的东西,如果硬要把这个两个东西来过来说的话,那也只是人为强加的因素。但是可以肯定的作为常用的软件,如果说maven创建的项目,如果不能在eclipse中开发,这可能就失去了这些工具的原来意义,这也许就是他们之间的唯一联系。首先maven和eclipse对象项目资源描述元数据都是不同的,当然现在有mavenide(maven workshop)等工具来保证这些元数据之间的转换和同步。其次eclispe和maven之间的功能和职责不同,maven基本上不用来进行源码的开发,这样用eclipse和maven结合来开发确实可以减少很多工作量。
 3.maven的主要原理我想没有比它doc上将的清楚。我想说的就一点,一开始我总用ant的观点去理解它,总觉得少了很多东西。其实maven最大的成功在于简化了面向过程的任务管理。你知道定义资源,maven用基于插件的目标方式来完成构建。当然你也可以想ant一样定义自己特殊的目标,你也可以把自己项目的目标制作成通用的插件。
 4.maven的项目开发,通过默认的模板新建一个maven项目.它会生成项目的各种资源和目录,例如src,conf等.然后你可以通过maven的IDE插件生成相应的IDE项目,例如maven eclipse.
        其实我也只是用它来编译jetspeed.目前越来越多的项目基于maven的方式发布,我们可以通过maven的插件把它转换成自己适应的IDE项目进行开发.基于它对项目管理强大功能,对资源和项目版本管理的统一性,我相信会有越来越多人会选择它来做项目管理工具,特别是团队开发.

Posted by@rimen at 2005 年 09 月 11 日 | Comments (1) | TrackBack

posted on 2006-03-22 12:28 天外飞仙 阅读(1279) 评论(0)  编辑  收藏 所属分类: java

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


网站导航: