2006年3月2日

http://www.firstui.com/read.php/250.htm

posted @ 2006-11-23 23:47 killvin| 编辑 收藏

很久都已经下定了决心不再维护那里的空间了,即使是成为了特约的作者也难以挽回我对他们Blog的信心,虽然blogjava的系统与csdn的是同样出自一份代码,但显然无论从服务的内容还是代码的理解上,csdn与blogjava相差的太远,当然我并不是完全的赞同目前blogjava的风格,尤其是身为介绍java的博客系统,竟然采用asp的技术来实现,实在是太煞风景了,甚至有些让你匪夷所思,不过瑕不掩瑜,毕竟无论从速, 专业程度还是目前提供的服务上来看,blogjava确实比同类型的网站要优秀一些.

posted @ 2006-10-29 15:54 killvin| 编辑 收藏

Version 1.01 on Bindows enhanced its Mozilla support considerably. At this point, the level of support is such that many complicated applications are available to Mozilla users. For example: now these can use our forum and the registration application.
We are dedicated to make Bindows work at least as well with Mozilla 1.4+ as it does with Internet Explorer 6.0 SP1. The Mozilla support is now integral part of Bindows and we will continue to develop, maintain and support it.
However, some issues are not solved yet and this document details them.


Known Issues
Web Services

We do not yet have a working SOAP web service client working with Mozilla. Mozilla has a built in web service client but it does not yet work with all the major web services platforms. We anticipate this to be resolve in a near future version of Bindows.


Graphs, Charts and Gauges
Graphs, charts and gauges are not supported. For the Bindows charts and gauges we rely on VML to do the drawing. Mozilla does not yet have a way to draw vector graphics at the client side without using a plugin. We consider several alternatives here but there is no time frame for a solution yet.


Miscellaneous
The caret is sometimes not shown in a focused textfield. This is a Mozilla bug and unless this is fixed in Mozilla this issue will remain.
Preferred size is not working consistently. Please report the cases where you are experiencing issues with the preferred size and we will see what can be done in those specific cases.
Icons in menu items works inconsistently between Mozilla versions. This is due to differences in the Mozilla toolkits used by different browsers.
Menu labels do not accept HTML. If you use HTML, the markup will be stripped.
The rendering of the group box is incorrect.
Labels with text align center and icon position set to top or bottom are a bit jumpy, ie. the icon move around depending on the state.
Dragging elements sometimes inconsistent. If an image is dragged an OS level drag and drop is initiated. Also, for some components a dragging will not work when the mouse leaves the browser window or enters a frame.
The upper border for the tab pages are not correctly drawn in Mozilla Firefox using Windows Classic. This is a Mozilla bug.
BiKeyboardEvent keyCode is sometimes incorrect in a keypress event (for special keys). The reason for this bug is that Internet Explorer and Mozilla are handling keyboard events differently.
Resize events are not fired on all BiComponents.
Focus and blur events are inconsistent when using BiRichEdit.

posted @ 2006-10-14 16:21 killvin| 编辑 收藏

Jsparse is a parse to parse the schema file with javascript.
If you are interested in it, you can vist the url
http://code.google.com/p/jsparse/  

get source with svn
svn checkout http://jsparse.googlecode.com/svn/trunk/ jsparse

posted @ 2006-09-13 23:23 killvin| 编辑 收藏

ACE中的Double Checked Locking 模式


(作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作)

意图

无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,可以用Double Checked Locking 模式来减少竞争和加锁载荷。

动机

1、标准的单例。开发正确的有效的并发应用是困难的。程序员必须学习新的技术(并发控制和防止死锁的算法)和机制(如多线程和同步API)。此外,许多熟悉的设计模式(如单例和迭代子)在包含不使用任何并发上下文假设的顺序程序中可以工作的很好。为了说明这点,考虑一个标准的单例模式在多线程环境下的实现。单例模式保证一个类仅有一个实例同时提供了全局唯一的访问这个实例的入口点。在c++程序中动态分配单例对象是通用的方式,这是因为c++程序没有很好的定义静态全局对象的初始化次序,因此是不可移植的。而且,动态分配避免了单例对象在永远没有被使用情况下的初始化开销。


class Singleton
{
public:
static Singleton *instance (void)
{
if (instance_ == 0)
// Critical section.
instance_ = new Singleton;
return instance_;
}
void method (void);
// Other methods and members omitted.
private:
static Singleton *instance_;
};



应用代码在使用单例对象提供的操作前,通过调用静态的instance方法来获取单例对象的引用,如下所示:
Singleton::instance ()->method ();
2、问题:竞争条件。不幸的是,上面展示的标准单例模式的实现在抢先多任务和真正并行环境下无法正常工作。例如,如果在并行主机上运行的多个线程在单例对象初始化之前同时调用Singleton::instance方法,Singleton的构造函数将被调用多次,这是因为多个线程将在上面展示的临界区中执行new singleton操作。临界区是一个必须遵守下列定式的指令序列:当一个线程/进程在临界区中运行时,没有其他任何线程/进程会同时在临界区中运行。在这个例子中,单例的初始化过程是一个临界区,违反临界区的原则,在最好的情况下将导致内存泄漏,最坏的情况下,如果初始化过程不是幂等的(idempotent.),将导致严重的后果。

3、 通常的陷阱和弊端。实现临界区的通常方法是在类中增加一个静态的Mutex对象。这个Mutex保证单例的分配和初始化是原子操作,如下:


class Singleton
{
public:
static Singleton *instance (void)
{
// Constructor of guard acquires lock_ automatically.
Guard guard (lock_);
// Only one thread in the critical section at a time.
if (instance_ == 0)
instance_ = new Singleton;
return instance_;
// Destructor of guard releases lock_ automatically.
}
private:
static Mutex lock_;
static Singleton *instance_;
};


guard类使用了一个c++的习惯用法,当这个类的对象实例被创建时,它使用构造函数来自动获取一个资源,当类对象离开一个区域时,使用析构器来自动释放这个资源。通过使用guard,每一个对Singleton::instance方法的访问将自动的获取和释放lock_。
即使这个临界区只是被使用了一次,但是每个对instance方法的调用都必须获取和释放lock_。虽然现在这个实现是线程安全的,但过多的加锁负载是不能被接受的。一个明显(虽然不正确)的优化方法是将guard放在针对instance进行条件检测的内部:


static Singleton *instance (void)
{
if (instance_ == 0) {
Guard guard (lock_);
// Only come here if instance_ hasn't been initialized yet.
instance_ = new Singleton;
}
return instance_;
}

这将减少加锁负载,但是不能提供线程安全的初始化。在多线程的应用中,仍然存在竞争条件,将导致多次初始化instance_。例如,考虑两个线程同时检测 instance_ == 0,都将会成功,一个将通过guard获取lock_另一个将被阻塞。当第一线程初始化Singleton后释放lock_,被阻塞的线程将获取lock_,错误的再次初始化Singleton。
4、解决之道,Double Checked Locking优化。解决这个问题更好的方法是使用Double Checked Locking。它是一种用于清除不必要加锁过程的优化模式。具有讽刺意味的是,它的实现几乎和前面的方法一样。通过在另一个条件检测中包装对new的调用来避免不必要的加锁:


class Singleton
{
public:
static Singleton *instance (void)
{
// First check
if (instance_ == 0)
{
// Ensure serialization (guard constructor acquires lock_).
Guard guard (lock_);
// Double check.
if (instance_ == 0)
instance_ = new Singleton;
}
return instance_;
// guard destructor releases lock_.
}
private:
static Mutex lock_;
static Singleton *instance_;
};


第一个获取lock_的线程将构建Singleton,并将指针分配给instance_,后续调用instance方法的线程将发现instance_ != 0,于是将跳过初始化过程。如果多个线程试图并发初始化Singleton,第二个检测件阻止竞争条件的发生。在上面的代码中,这些线程将在lock_上排队,当排队的线程最终获取lock_时,他们将发现instance_ != 0于是将跳过初始化过程。

上面Singleton::instance的实现仅仅在Singleton首次被初始化时,如果有多个线程同时进入instance方法将导致加锁负载。在后续对Singleton::instance的调用因为instance_ != 0而不会有加锁和解锁的负载。 通过增加一个mutex和一个二次条件检测,标准的单例实现可以是线程安全的,同时不会产生过多的初始化加锁负载。

适应性

> 当一个应用具有下列特征时,可以使用Double Checked Locking优化模式:
1、应用包含一个或多个需要顺序执行的临界区代码。
2、多个线程可能潜在的试图并发执行临界区。
3、临界区仅仅需要被执行一次。
4、在每一个对临界区的访问进行加锁操作将导致过多加锁负载。
5、在一个锁的范围内增加一个轻量的,可靠的条件检测是可行的。

结构和参与者

通过使用伪代码能够最好地展示Double Checked Locking模式的结构和参与者,图1展示了在Double Checked Locking模式有下列参与者:



1、仅有一次临界区(Just Once Critical Section,)。临界区所包含的代码仅仅被执行一次。例如,单例对象仅仅被初始化一次。这样,执行对new Singleton的调用(只有一次)相对于Singleton::instance方法的访问将非常稀少。
2、mutex。锁被用来序列化对临界区中代码的访问。
3、标记。标记被用来指示临界区的代码是否已经被执行过。在上面的例子中单例指针instance_被用来作为标记。
4、 应用线程。试图执行临界区代码的线程。

协作

图2展示了Double Checked Locking模式的参与者之间的互动。作为一种普通的优化用例,应用线程首先检测flag是否已经被设置。如果没有被设置,mutex将被获取。在持有这个锁之后,应用线程将再次检测flag是否被设置,实现Just Once Critical Section,设定flag为真。最后应用线程释放锁。



结论

使用Double Checked Locking模式带来的几点好处:
1、最小化加锁。通过实现两个flag检测,Double Checked Locking模式实现通常用例的优化。一旦flag被设置,第一个检测将保证后续的访问不要加锁操作。
2、防止竞争条件。对flag的第二个检测将保证临界区中的事件仅实现一次。
使用Double Checked Locking模式也将带来一个缺点:产生微妙的移植bug的潜能。这个微妙的移植问题能够导致致命的bug,如果使用Double Checked Locking模式的软件被移植到没有原子性的指针和正数赋值语义的硬件平台上。例如,如果一个instance_指针被用来作为Singleton实现的flag,instance_指针中的所有位(bit)必须在一次操作中完成读和写。如果将new的结果写入内存不是一个原子操作,其他的线程可能会试图读取一个不健全的指针,这将导致非法的内存访问。
在一些允许内存地址跨越对齐边界的系统上这种现象是可能的,因此每次访问需要从内存中取两次。在这种情况下,系统可能使用分离的字对齐合成flag,来表示instance_指针。
如果一个过于激进(aggressive)编译器通过某种缓冲手段来优化flag,或是移除了第二个flag==0检测,将带来另外的相关问题。后面会介绍如何使用volatile关键字来解决这个问题。

实现和例子代码

ACE在多个库组件中使用Double Checked Locking模式。例如,为了减少代码的重复,ACE使用了一个可重用的适配器ACE Singleton来将普通的类转换成具有单例行为的类。下面的代码展示了如何用Double Checked Locking模式来实现ACE Singleton。


// A Singleton Adapter: uses the Adapter
// pattern to turn ordinary classes into
// Singletons optimized with the
// Double-Checked Locking pattern.
template 
class ACE_Singleton
{
public:
static TYPE *instance (void);
protected:
static TYPE *instance_;
static LOCK lock_;
};
template  TYPE *
ACE_Singleton::instance ()
{
// Perform the Double-Checked Locking to
// ensure proper initialization.
if (instance_ == 0) {
ACE_Guard lock (lock_);
if (instance_ == 0)
instance_ = new TYPE;
}
return instance_;
}


ACE Singleton类被TYPE和LOCK来参数化。因此一个给定TYEP的类将被转换成使用LOCK类型的互斥量的具有单例行为的类。
ACE中的Token Manager.是使用ACE Singleton的一个例子。Token Manager实现在多线程应用中对本地和远端的token(一种递归锁)死锁检测。为了减少资源的使用,Token Manager被按需创建。为了创建一个单例的Token Manager对象,只是需要实现下面的typedef:

typedef ACE_Singleton Token_Mgr;
Token Manager单例将被用于本地和远端的token死锁检测。在一个线程阻塞等待互斥量之前,它首先查询Token Manager单例,来测试阻塞是否会导致死锁状态。对于系统中的每一个token,Token Manager单例维护一个持有token线程和所有阻塞等待在该token的线程记录链表。这些数据将提供充足的检测死锁状态的依据。使用Token Manager单例的过程如下:


// Acquire the mutex.
int Mutex_Token::acquire (void)
{
// If the token is already held, we must block.
if (mutex_in_use ()) {
// Use the Token_Mgr Singleton to check
// for a deadlock situation *before* blocking.
if (Token_Mgr::instance ()->testdeadlock ()) {
errno = EDEADLK;
return -1;
				}
else
// Sleep waiting for the lock...
		// Acquire lock...
		}

变化

一种变化的Double Checked Locking模式实现可能是需要的,如果一个编译器通过某种缓冲方式优化了flag。在这种情况下,缓冲的粘着性(coherency)将变成问题,如果拷贝flag到多个线程的寄存器中,会产生不一致现象。如果一个线程更改flag的值将不能反映在其他线程的对应拷贝中。

另一个相关的问题是编译器移除了第二个flag==0检测,因为它对于持有高度优化特性的编译器来说是多余的。例如,下面的代码在激进的编译器下将被跳过对flag的读取,而是假定instance_还是为0,因为它没有被声明为volatile的。

Singleton *Singleton::instance (void)
{
if (Singleton::instance_ == 0)
{
// Only lock if instance_ isn't 0.
Guard guard (lock_);
// Dead code elimination may remove the next line.
// Perform the Double-Check.
if (Singleton::instance_ == 0)
// ...



解决这两个问题的一个方法是生命flag为Singleton的volatile成员变量,如下:
private:
static volatile long Flag_; // Flag is volatile.
使用volatile将保证编译器不会将flag缓冲到编译器,同时也不会优化掉第二次读操作。使用volatile关键字的言下之意是所有对flag的访问是通过内存,而不是通过寄存器。

相关模式

Double Checked Locking模式是First-Time-In习惯用法的一个变化。First-Time-In习惯用法经常使用在类似c这种缺少构造器的程序语言中,下面的代码展示了这个模式:


	static const int STACK_SIZE = 1000;
static T *stack_;
static int top_;
void push (T *item)
{
// First-time-in flag
if (stack_ == 0) {
stack_ = malloc (STACK_SIZE * sizeof *stack);
assert (stack_ != 0);
top_ = 0;
}
stack_[top_++] = item;
// ...
}



第一次push被调用时,stack_是0,这将导致触发malloc来初始化它自己。

posted @ 2006-09-13 23:16 killvin| 编辑 收藏

在上一篇的文章中,比较仔细的分析了IE对于Cookie处理过程中出现的问题,既然存在问题就要想办法解决,而问题的关键在于IE没有能够"老实"的报告用户对于Cookie的设置问题

IE
GET /mail HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Maxthon)
Host: mail.google.com
Connection: Keep-Alive
Cookie:
gmailchat=killvin.liu@gmail.com/519974 ; PREF=ID=d68c481e542af276:NW=1:TM=1151742225:LM=1151742225:S=2qbdhg0_z3i-OAbW; SID=DQAAAG8AAACEdcjD2IZMNqZVatDbD62X8_U18oJuTVQc9XZUJi7MgCkM8sggJ8M5npZ35GXjdalT2o8QWPUve04tepy61MPv4v_EpILafg3JdIf8AFjD91aMT0tI5gb763FouV3e_2-C364HDO5Qzb4P4gjjgpHC

Firefox
GET /mail HTTP/1.1
Host: mail.google.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

我们可以注意到:如果用户关闭了浏览器的Cookie选项,在Firefox中对于新的请求是包含Cookie的信息的,然而IE却恰恰相反!这也就直接用IE浏览一些网站所导致的Cookie校验失败问题(比如即使用户在使用过程中突然关闭Cookie,依然可以自由的访问网站的各项服务),进一步,如果某个网站是读取依靠Cookie中的信息来验证用户(尤其是那种保存客户信息的提示),即使这个用户离开或者关闭窗口,任何一个后来的人都可以轻松的浏览这个用户的私有信息!这就是Cookie的最大的问题所在,当然这是另外的话题,不属于本次讨论的范畴,这一次要说的是
如何避免IE浏览器下,用户关闭Cookie的问题。

有一个思路,经过测试我们发现IE浏览器只是在首次页面请求的时候才会去读取Cookie的值,所以,为了避免IE浏览器关闭Cookie的问题,可以在用户请求某个链接的时候,先将用户的Cookie存放在某处,并清空此时所有的Cookie值,再次的请求页面,而这样做的目的就是强迫IE读取硬盘上的Cookie,如果此时用户关闭了Cookie,就不会在请求的头信息中看到Cookie信息;如果没有关闭Cookie,就可以放心的将原先的Cookie写回Reponse对象。


只是不知道Google的网站是否也是按照这个思路去解决此问题的?

posted @ 2006-07-02 21:53 killvin| 编辑 收藏

IE在处理COOKIE方面的问题

1。即使你提高了浏览器的隐私登记,在第一次打开窗口的时候,你获取不到任何的Cookies对象(很显然的结果),然而当你再次刷新本页面的,Cookie此时会奇迹般的出现!而在Firefox浏览器中按照以上的步骤,是不会出现这样的情况的。

2。不仅如此,你还可以透过Action的处理,多次的往Cookie中增加Cookie的数量(当然在Action中你依然可以自由的获取到Cookie这个数组,并且不为空),然而让人匪夷所思的是在Cookie的存放目录下你是找寻不到任何的蛛丝马迹的。而在Firefox没有出现以上的情况。

-解决
1。在首次进入页面时查询客户端的Cookie,如果不存在则警告用户,并要求再次的登陆。
2。在用户登陆后,如果更改了浏览器的隐私级别,对于Firefox标准的浏览器,此时肯定不会再找到Cookie数组对象了,你需要做的仅仅只是将页面调转到登陆窗口;而在IE下就非常的麻烦了甚至无法解决,因为你依然可以访问到原来的Cookie数组值,(比如,用IE在CSDN登陆后提高隐私级别,你依然可以登陆到其他的服务区域)此时没有什么好的办法,不过Google解决了这样的问题,只是不知道如何解决的。



IE在处理Frame标记的问题
1。如果你在某个页面中嵌入了Frame标签,并且希望与这个嵌入的页面共享某些存放在Session中的数据,此时你需要将外部的sessionId传入到frame标记的页面中。然而在IE中你可能并不能完全的享受这样的逻辑,原因在于IE对于嵌入的页面不会自动的传递sessionId,也许你碰巧可以,也许不行,也就是说完全是在IE的"掌控"之下。而在Firefox没有出现这样的情况。

-解决
为嵌入的页面中所有的链接增加sessionId参数。


最好的办法就是:说服客户使用标准的浏览器Firefox!

posted @ 2006-07-01 15:53 killvin| 编辑 收藏

在Struts中我们经常这样循环的打印Message
<logic:messagesPresent message="true">
  <html:messages id="msg" message="true">
    <div class="success">
      <bean:write name="msg"/>
    </div><br/>
  </html:messages>
</logic:messagesPresent>

查阅struts tag的文档我们看到了关于messagesPresent的message属性注释如下

message

By default the tag will retrieve the request scope bean it will iterate over from the Globals.ERROR_KEY constant string, but if this attribute is set to 'true' the request scope bean will be retrieved from the Globals.MESSAGE_KEY constant string. Also if this is set to 'true', any value assigned to the name attribute will be ignored


也就是说在将message设置为true时,会去request中寻找Globals.MESSAGE_KEY所代表的bean,然而我们在具体的Action使用ActionMessages类的时候往往是这样的
ActionMessages messages = getErrors(request);
messages.add(ActionMessages.GLOBAL_MESSAGE , new ActionMessage(key , value0));
saveMessages(request,messages);

而往往困扰人的就在于为什么要给messages中放入名称为"ActionMessages.GLOBAL_MESSAGE"的ActionMessage对象,而且还需要再次的调用saveErrors(request,messages)方法?

首先要说明的是你为ActionMessage起任何的名称都没有关系,因为ActionMessages本身维持着一个HashMap,而参数property就是这个HashMap中的key值,如果不存在则会建立相应的key,并将需要保存的ActionMessage对象存入到这个key所对应的List中。
    public void add(String property, ActionMessage message) {

        ActionMessageItem item = (ActionMessageItem) messages.get(property);
        List list = null;

        if (item == null) {
            list = new ArrayList();
            item = new ActionMessageItem(list, iCount++, property);

            messages.put(property, item);
        } else {
            list = item.getList();
        }

        list.add(message);

    }

至于为什么一定要调用saveMessages(request,messages)?看看它具体的实现逻辑就清楚了
    protected void saveMessages(
        HttpServletRequest request,
        ActionMessages messages) {

        // Remove any messages attribute if none are required
        if ((messages == null) || messages.isEmpty()) {
            request.removeAttribute(Globals.MESSAGE_KEY);
            return;
        }

        // Save the messages we need
        request.setAttribute(Globals.MESSAGE_KEY, messages);
    }

再对比前面介绍的messagesPresent标签的使用,是不是就清楚了呢?原来它是将ActionMessages对象保存在request中,并且名称是Globals.ERROR_KEY!从而为tag的顺利解析铺平了道路。当然按理你可以选择将这样的对象放置在任何的scope中,但Action只是提供了request , session两种Scope(不过page , application不经常使用,可以理解,但不提供相应的结构就不太好了)

至于messagesPresent标签是如何在scope中寻找ActionMessages对象

org.apache.struts.taglib.logic.MessagesPresentTag
    protected boolean condition(boolean desired) throws JspException {
        ActionMessages am = null;

        String key = name;
        if (message != null && "true".equalsIgnoreCase(message)){
           key = Globals.MESSAGE_KEY;
        }

        try {
            am = TagUtils.getInstance().getActionMessages(pageContext, key);
           
        } catch (JspException e) {
            TagUtils.getInstance().saveException(pageContext, e);
            throw e;
        }

        Iterator iterator = (property == null) ? am.get() : am.get(property);

        return (iterator.hasNext() == desired);

    }

org.apache.struts.taglib.TagUtils
   public ActionErrors getActionErrors(PageContext pageContext, String paramName)
            throws JspException {

        ActionErrors errors = new ActionErrors();

        Object value = pageContext.findAttribute(paramName);
        if (value != null) {
            try {
                if (value instanceof String) {
                    errors.add(
                            ActionMessages.GLOBAL_MESSAGE,
                            new ActionMessage((String) value));

                } else if (value instanceof String[]) {
                    String keys[] = (String[]) value;
                    for (int i = 0; i < keys.length; i++) {
                        errors.add(
                                ActionMessages.GLOBAL_MESSAGE,
                                new ActionMessage(keys[i]));
                    }

                } else if (value instanceof ActionErrors) {
                    errors = (ActionErrors) value;

                } else {
                    throw new JspException(
                            messages.getMessage(
                                    "actionErrors.errors",
                                    value.getClass().getName()));
                }

            } catch (JspException e) {
                throw e;

            } catch (Exception e) {
                log.debug(e, e);
            }
        }
        return errors;
    }

PageContext中的findAttribute会帮你在scope中寻找名称为Globals.MESSAGE_KEY的ActionMessage对象。


注意
虽然Struts已经声明:不推荐使用ActionErrors & ActionError对象,但在一些遗留的系统中,依然还是可以看到其影子,所以如果你的系统不幸属于这样的两种混合系统,有以下的几种方法可以参考
1。两次调用messagesPresent,如下
<!-- Print ActionErrors Object -->
<logic:messagesPresent>
  <html:messages id="msg" message="true">
    <div class="success">
      <bean:write name="msg"/>
    </div><br/>
  </html:messages>
</logic:messagesPresent>

<!-- Print ActionMessages Object -->
<logic:messagesPresent message="true">
  <html:messages id="msg" message="true">
    <div class="success">
      <bean:write name="msg"/>
    </div><br/>
  </html:messages>
</logic:messagesPresent>

2.分别使用<html:messages> <html:errors>标签,当然在老系统中需要调用Action的saveErrors方法,而在新的应用中要调用saveMessages方法。

3.更换所有的ActionErrors为ActionMessages,并将所有调用saveErrors的地方更换成saveMessages,并将<html:errors>标签相应的更换成<html:messages message="true"> - 推荐!

 

posted @ 2006-06-27 23:02 killvin| 编辑 收藏

第2章 并发问题及控制手段

什么是并发问题?假设有这么一家书吧,顾客可以到那里喝茶读书。顾客拿着选好要读的图书到柜台登记,然后找个地方去阅读,临走时将图书归还店家。有一天,一个顾客相中了一本书后正要拿去登记,另一个顾客的手也抓住了这仅有的一本书,并发问题出现了。两个顾客要读同一本书,互不相让,这让店主伤透了脑筋。这个案例仅仅是众多并发问题中的一个微小部分,但从中我们可以看出并发问题主要出现在多个用户对有限资源进行访问的时候,如果解决不好会直接影响系统的有效、正常运行。数据库是一个共享的资源,并发问题的出现是必不可免的,如何识别并发类型并加以控制是这一章重点要讲述的内容。

本章将分成两大部分,一部分主要讲Visual FoxPro中并发控制机制。VFP中并发控制相对简单,数据加锁的形式比较单一,非常适合作为初步了解并发问题的切入点。第二部分以SQL Server 2000、ADO.NET以及C#为主要工具,深入了解并发一致性问题、封锁协议、事务隔离等内容,难度相对较深。象一些更为深入的并发控制手段,例如多粒度封锁和意象锁等内容在本章中将不做深入讨论,感兴趣可以参考相关书籍。

 

[实在不好意思COPY别人的成果,不过这篇文章出奇的精彩,将并发操作的来龙去脉说的清清楚楚,也是我正要找的,比JAVAEYE上面所谓的专家叫嚷着什么"悲观锁"、"乐观锁"而不解是原因要强的多!值得收藏]

posted @ 2006-04-06 10:57 killvin| 编辑 收藏

痛快!。。。人们总是认为骂人是没有素质的象征,然而我到觉得要看对谁而言,如果你的面前是个谦虚、严谨而又懂得尊重别人的人,你根本就不可能让自己愤怒,也不可能让自己表现的很没有素质;相反如果你的面前是个先入为主、自傲、不懂得尊重别人的人,你的愤怒就会在最短的时间内激发出来。-对于小人,就要骂!

而这有与北大青鸟有什么关系呢?

在我的职业生涯里,有一段让自己愤怒的培训经历,到今天我都为自己在这样的培训部的学习经历而感到耻辱!甚至不愿意在简历里提及ACCP的字样,而这样的培训经历就是在北大青鸟(西安兆隆)里度过的。

我参加的是北大青鸟(西安兆隆)的第二期培训,培训的课程涉及到数据库、编程语言、职业素质、项目实践等,看上去非常的正规,可是真正的学习效果却是让人非常的失望的!

首先,就是它的课程体系,完全的引入印度的教程,而翻译的质量非常的差,其实这到没有什么,只是教程的讲解范围和深度都非常的浅(这还是第二期的培训),如果你只是按照它的教程我可以说你根本学不到任何有价值的东西,只是皮毛而已。-拿着淡薄的教学资料的同时你也就上了贼船了。


再次,就是它的老师的水平实在不敢恭维,(当然并不是说根本就没有好的老师,曾经就遇到过一个讲解JAVA与UML的老师,水平非常的高深,可惜ACCP没有留住),比如编程语言中学习了VB,仅仅只是这一门课程就换了3个老师,最后是一位号称是西工大的博士上的课,然而这位博士显然是心思不再这里,除了讲解课本上已经有的代码,根本不敢深入的讲解原理,这门课程也就这样混过去了。-如果这样学习编程语言道不如自学来得轻松。


还有,就是所谓的项目实践根本就不是老师带的项目,而是一些书本上或者捏造出来的一些项目,比如图书管理系统等,如果学员无法真正的理解需求,怎么去理解设计,又如何来进行实际的编码呢?如果遇到图书管理的专业问题来问谁呢?-别指望在他们宣传的所谓的项目实践中得到锻炼。

在看看他们引以为傲的工作分配问题吧?就我个人的亲身感受来言,这里会提供一些工作的机会,但都是一些小公司,而且往往是一些前三个月不给工资的公司;当时我在我们班级里是比较认真的一个(我是班里唯一的一个交了对项目分析文档的学员,并且是项目实践中的小组组长),可是在一次很好的面试机会面前,她们竟然由于工作的失误忘记了给我打电话?!-如果这样的工作分配策略也算作是给予你的答案的话,你一定会变得和我一样的愤怒!


我没有在ACCP学到任何的有用的东西,然而在真实的世界里却学到了人们对于ACCP的看法,很多的技术经理 、 技术总监从骨子里非常的厌恶从北大青鸟出来的学生,对于他们开设的一些课程也是相当的反感,当时在西部世纪工作的时候,同组的就有和我一起上课的同班同学,在他交了8000多元的学习费用(总共的费用接近1万多元)后,得到的仅仅只是西部世纪的一张试用通知单,而条件是必须无条件的为公司工作三个月!


这就是我在北大青鸟的学习经历,然而愤怒的不是这个,却是今天参加的面试经历,对照着自己的个人的工作经历我希望自己能够成为一名培训部的老师,教给那些原来如我一样的学生一些在实践当中有用的东西,而不是空洞的理论!所以我给ACCP投了自己的简历,然而让我却又一次的遇到了那个欺骗过我的咨询师,不过她的记忆力也是非常的好,一眼就认出了我(当然了我是曾经给她带来奖金的人),在紧接着的面试过程中已经变了味道,谈论的话题也不是具体项目技术的问题,(不是我笑话她与她谈论技术问题根本就如同对牛弹琴)她显然是一个没有素质的WOMAN,也可以肯定的是她根本没有在外企工作过的经历,所以说话的词汇以及与其如同大街上的泼妇一般,可以想见她一定是一个被ACCP严重洗过脑的人,而且是属于那种技术失败的产品,她无法接受别的培训部的名字,一旦你提起别的培训部的某个老师的水平如何如何的好,她就如同一个快被点燃的鞭炮一般,两眼充满了愤怒!

然而让我最终和她一般的没有素质的大骂的是她对于我辞职的评点,我很纳闷的是她在不知道一件事情的原因的情况下,可以讲述自己的观点!并且是带着教育的口吻,实在是人才呀!这就是北大青鸟的"人才力量",对于这样的白痴级别的任务的观点,我只好打断,因为它甚至连我在的那个单位的名字都不知道!!

实在是一场闹剧,而她更像是个小丑,除了唧唧喳喳的重复一些"洗脑"的词汇已经不知道窗外的天气了,可以预料北大青鸟的牌子必将会毁在这些个白痴的手中,世界需要白痴,那如同鲜花需要牛粪一样,我们改变不了白痴的世界观的,你能够做的就是别被这臭烘烘的牛粪给熏臭了。

posted @ 2006-04-01 12:53 killvin| 编辑 收藏


[深入的思考] 为什么要采用java这个平台?


从开发项目的类别角度看java平台

基于B/S结构的系统,在这个方向上的竞争是激烈的,有专注于此的LAMP(Linux + Apache + Mysql + Php);也有刚刚兴起的Rails(Ruby Frameworks)甚至是号称快速开发的ASP.NET;当然了java在这个领域里的MVC框架数都数不完,比如Struts . Webwork等,然而即便是如此,选择java作为开发的理由也是不充分的,因为在这个梯队里java顶多排名最后。

基于C/S结构的系统,在这个方面java显然没有考虑周到,面对VB 、DELPHI、vc这些个如狼似虎的快速开发IDE,JAVA实在是显得异常的淡薄,即使你找到了一个可以匹敌这些个ide的工具,面对第三方的组件又会成为一大障碍,所以java在这个方面又一次的输了。


从java所强调的特性角度看java平台

java的重点是业务逻辑!(我以前也是如此坚信不移)可是谁有能够说别的语言不注重业务逻辑呢,业务逻辑只是一个抽象的概念,java只是依靠ejb提出了业务组件而已,其他的语言在实现业务逻辑的时候也可以包装成POJO的形式,看来这个观点也是失败的。

java强调的是跨平台的优势!这可以理解为初级的、商业的、忽悠人的词汇,面对众多动态语言如Python,在若干平台上的表现,java又如何来强调自己这方面的优势呢?失败

java支持分布式应用的项目!可笑的言论,分布式根本不是值得炫耀的资本,在java之前的c/s项目中何尝不是分布式的应用呢?失败


既然没有了这些个优势,我们看看java到底还剩下些什么?对了其实就是应用服务器!然而看过J2EE WITHOUT EJB的读者肯定知道Spring所希望达到的目的,也就是脱离应用服务器概念上的J2EE体系实现,既然在作者的眼里APPLICATION SERVER只不过是一个忽悠人的词汇,那么任何项目都选择java作为开发的依据显然就是自找苦吃,

那么什么情况下改选择java作为开发的平台呢?
<1> 如果你真的遇到了大型的系统开发任务,恭喜你,你终于可以看到分布式对象、集群的优势了。
<2> 客户是一个java的忠实fans或者是sun、ibm的金牌合作伙伴之类的,选择java是不得已的,但记住并不能证明java是最好的实现方式
<3> 如果你只想关心业务逻辑的实现,对于事务、缓存、查找等服务的实现没有兴趣的话,倒是不妨考虑采用ejb的形式,当然前提是你不愿意在寻找合适的替代品的情况下。
<4> 如果项目迫切的寻找某种框架的支持,选择java就是对的,你有众多优秀的、免费的、可扩展的、天才的框架可以选择,更多的时候你是出于尴尬的境地,因为任何一个都让你心动、而这样的选择往往是最痛苦、和快乐的。


正确的选择
<1>
条件: 如果项目仅仅只是一个小型的网站系统
选择: LAMP、Rails

<2>
条件: 项目规模中等
并且项目的时间比较紧,
项目可以架构在windows的系统之上,
选择: .Net  / Delphi

<3>
条件: 大型的系统,有支持分布式对象、集群的要求;或者SUN / IBM的金牌合作伙伴 ; 想要寻找某种优秀的框架来解决问题
选择: java是不二的选择,可是我想问一下,在现实中你能遇到这样的项目吗?

所以,从实际的角度出发,我们面对的99%可能都是一些符合条件1,2的系统,而选择java实在是得不偿失的。最后以一段Code Complete中的话来作为结束语

每个程序员都有很多的工具,但并不存在任何一个能够适用于所有工作的工具,因地制宜的选择正确工具是成为能有效编程的程序员的关键。

posted @ 2006-03-29 13:49 killvin| 编辑 收藏

 

作者简介
XincChen是Xtremework公司创始人.a自从.NET推出以来,1他已使用.NET帮助很多行业的用户开发了体现其商业理念的软件产品.aXincChen是.NET和EAI方面的专家,1他与Microsoft和Accenture等多家技术领先的公司合作,1为它们的客户提供了优秀的解决方案.a在工作之余,1他喜欢读书.c写书.c以及静静地休息.aApress出版社的另一本书——《BizTalkc2002cDesigncandcImplementation》——也是出自他的笔下.aXincChen拥有哥伦比亚大学的统计学文科硕士学位,1现居国内,1正在筹建公司在北京的研发中心.a可通过xchen@Xtremework.com和作者取得联系.

个人评论

这本书本来购买时的期望值非常的高,想必是一本细数.NET框架类的图书,结果却大失所望,而这本书也就因为其技术上的薄弱而消弱了其价值的表现力,不过作为一本.NET入门级的图书还是比较的合适,但在CSDN上竟然有四颗半的星的待遇实在不敢恭维。

而且作者的代码显然非常的粗造,竟然连起码的业务级数据校验都没有,在一些功能的分解上也是抹浆糊,可以想象作者并没有深入的思考而仅仅是去实现功能而已,猜想这样的框架很可能是作者对于以前工作的简单抽象而已。

整体上本书除了第一章应用框架介绍 、 第二章应用框架解析比较的出彩,尤其是创造性的提出了业务假设(Business assumption)的概念还比较的有深度,其他的章节只是对于其SAF框架的介绍,但在技术实现上只是对于.NET的若干相关服务的简单包装而已,对于已经在JAVA世界里的人来说,SAF框架其实就是Spring框架在.NET世界的简单实现版本,但相对于Spring的技术实力,作者还需要努力才行,不过作者显然是.NET方面的高手,对于.NET世界提供的杂乱无章的服务特性以及服务的原理非常的清楚,这也难怪在这样的平台上再去"多此一举"的开发另外的框架,无论从技术上还是推广上都非常的难!不过领略一下..NET的秀发也是多少有所收获的 :)

再次强调这本书仅仅只能够算作.NET方面的初级读物,个人评星3.5 ,请朋友们谨慎购买。

posted @ 2006-03-24 14:22 killvin| 编辑 收藏

wfc是构建在B/S结构上的流程定义工具

具备以下的功能
1> 实现了B/S结构上的工作流定义工具(没有看到同类型的产品)。
2> 流程定义格式与具体的工作流格式相分离,并可以在此基础上实现其他的流程定义工具产品。
3> 采用了Buffalo(XML-RPC的javascript实现)实现与后端Servlet的绑定。
4> 可以对具体的节点进行属性的配置(配置后的数据会被绑定为java的List自动传递到后端)。

目前还需要改进的
1> 修改目前工作流的图标(需要分支、状态、合并这样专业的图标)
2> 解决浏览器的刷新问题。
3> 增加线段的表现形式选项
4> 增加曲线的表现形式,尤其是在多线段的情况下要自动调整为曲线的形式。


目前的WFC已经被Michael Chen(buffalo的作者)收录在其网站上!地址如下
http://demo.amowa.net/wfc

对于buffalo的关注早在作者刚发布的时候就已经开始了,起初不太了解buffalo的作用,然而在具体的工作中寻找基于XML-RPC协议的产品时突然明白了它的价值,不过说简单一点buffalo是实现了基于javascript的对象的序列化以及反序列化工作。

posted @ 2006-03-21 17:58 killvin| 编辑 收藏

通用类图书

卓越奖: Prefactoring by Ken Pugh (O'Reilly)

生产力奖:
Innovation Happens Elsewhere: Open Source as Business Strategy by Ron Goldman, Richard P. Gabriel (Morgan Kaufmann)
Producing Open Source Software: How to Run a Successful Free Software Project by Karl Fogel (O'Reilly)
The Art of Project Management by Scott Berkun (O'Reilly)

技术类图书

卓越奖:Agile Web Development with Rails by Dave Thomas, David Hansson, Leon Breedt and Mike Clark (Pragmatic Bookshelf)

生产力奖:
Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries by Krzysztof Cwalina and Brad Abrams (Addison-Wesley)
Practical Common Lisp by Peter Seibel (Apress)
Why Programs Fail: A Guide to Systematic Debugging by Andreas Zeller (Morgan Kaufmann)

企业项目管理

卓越奖: WelcomRisk 2.6 (Welcom)

生产力奖:
Corticon Business Rules Management 4.0 (Corticon)
JBoss 2 Portal (JBoss)
Visual Studio Team System 2005 (Microsoft)


数据库引擎和数据工具

卓越奖: Microsoft SQL Server 2005 (Microsoft)

生产力奖:
Berkeley DB 4.4 (Sleepycat Software)
Google Maps API 2005 (Google)
MySQL 5.0 (MySQL)

缺陷跟踪,变更和配置管理

卓越奖: Perforce SCM 2005 (Perforce)

生产力奖:
FogBugz 4.0 (Fog Creek)
Guiffy SureMerge 7.0 (Guiffy Software)
JIRA 3.4 (Atlassian Software)

设计和建模工具

卓越奖:Lattix LDM 2.0 (Lattix)

生产力奖:
Borland Together 2006 for Eclipse (Borland)
Enterprise Architect 6.0 (Sparx Systems)
MindManager Pro 6.0 (Mindjet)

开发环境

卓越奖: Visual Studio Team System 2005 (Microsoft)

生产力奖:
Eclipse SDK 3.1 (Eclipse.org)
IntelliJ IDEA 5.0 (JetBrains)
Komodo 3.5 (ActiveState)

类库、框架和组建

卓越奖: .NET Framework 2.0 (Microsoft)

生产力奖:
Dundas Chart for .NET 5.0 (Dundas Software)
Qt 4.0 (Trolltech)
Spring Framework 1.2.6 (SpringFramework.org)

移动开发工具

卓越奖: Crossfire 5.6 (AppForge)

生产力奖:
Carbide.c++ Express (Nokia)
Flash Lite 2.0 (Adobe)
NetBeans IDE 4.1 (Sun Microsystems)

项目质量管理

卓越奖: Rally 5.6 (Rally Software Development)

生产力奖:
CollabNet Enterprise Edition with Project Dashboard/Task Management 2005 (CollabNet)
QACenter Enterprise Edition 5.1 (Compuware)
TargetProcess Suite 1.4 (TargetProcess)

安全工具

卓越奖: Elemental Compliance System 1.4 (Elemental)

生产力奖:
CodeAssure 2.0 (Secure Software)
DevPartner SecurityChecker 1.0 (Compuware)
Fortify Security Tester 1.0 (Fortify)

测试工具

卓越奖:VMTN Subscription 2005 (VMware)

生产力奖:
Agitator 3.0 (Agitar Software)
AQtime 4.7 (AutomatedQA)
Clover 1.3 (Cenqua)

实用程序

卓越奖:Camtasia Studio 3.0 (TechSmith)

生产力奖:
DevPartner Studio 8 (Compuware)
Fog Creek Copilot 1.2 (Fog Creek Software)
ReSharper 1.5 (JetBrains)

Web开发工具

卓越奖: Rails 1.0 (rubyonrails.org)

生产力奖:
JBoss Application Server 4x (JBoss)
Macromedia Studio 8 2005 (Adobe)
Zend Studio - Enterprise Edition 5.0 (Zend)

名人堂产品获奖者:

?Visual Studio Professional Edition (Microsoft)

感慨
    MS在这次的JOLT中屡获殊荣,深思。。。

posted @ 2006-03-19 09:32 killvin| 编辑 收藏

很久都不骂人了,可是一不留心又看到了他写了恶心文章,实在是让人愤慨!这位板桥里人的"大名"是从透明的blog上知道的,首先就是感觉这个人的脑子有点问题!不去评论他发布的那个所谓的框架,就看看他写的一些文章就足以知道这个人脑袋非常的混乱,在这样混乱的情况下写出来的文章也就可想而知了,最近这位所谓的"大侠"又开始害人了

你还在用if else吗?- http://www.jdon.com/artichect/ifelse.htm


面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断。如果你还在大量使用if else,当然,界面表现层除外,即使你使用Java/C#这样完全面向对象的语言,也只能说明你的思维停留在传统的面向过程语言上。

-我很纳闷了作者怎么可以从是否使用if else来判断一个设计是否符合OO特性呢?我们看到的if else属于语言这个层次的东西,而if else仅仅是完成逻辑判断的语句;如果说作者的这个观点成立的话,java、c#的语言发明者应该早就明白或者预测到if else 的"不OO"的特性,也会考虑到在语言层面删除这样的逻辑判断语句,但事实呢?发明者非但没有删除相反其他语言的发明者也一起发明了if else语句?!难道是大师们错了?

还是以大家熟悉的论坛帖子为例子,如ForumMessage是一个模型,但是实际中帖子分两种性质:主题贴(第一个根贴)和回帖(回以前帖子的帖子),这里有一个朴素的解决方案:
建立一个ForumMessage,然后在ForumMessage加入isTopic这样判断语句,注意,你这里一个简单属性的判断引入,可能导致你的程序其他地方到处存在if else 的判断。

  如果我们改用另外一种分析实现思路,以对象化概念看待,实际中有主题贴和回帖,就是两种对象,但是这两种对象大部分是一致的,因此,我将ForumMessage设为表达主题贴;然后创建一个继承ForumMessage的子类ForumMessageReply作为回帖,这样,我在程序地方,如Service中,我已经确定这个Model是回帖了,我就直接下溯为ForumMessageReply即可,这个有点类似向Collection放入对象和取出时的强制类型转换。通过这个手段我消灭了以后程序中if else的判断语句出现可能。

-作者在这里似乎列举了一个例子,可是对于帖子和回帖这样简单的问题,只要遵守OO的设计师都会抽象出一个帖子的父类,然而这又能说明什么呢?在具体的业务逻辑中难道你不判断传递过来的对象的类别?(现在主题贴与回帖的处理方法是不同的),同样你无法避免在具体的编码中使用if else的可能?!


最后总结:将if else用在小地方还可以,如简单的数值判断;但是如果按照你的传统习惯思维,在实现业务功能时也使用if else,那么说明你的思维可能需要重塑,你的编程经验越丰富,传统过程思维模式就容易根深蒂固,想靠自己改变很困难;建议接受专业头脑风暴培训。

 用一句话总结:如果你做了不少系统,很久没有使用if else了,那么说明你可能真正进入OO设计的境地了。(这是本人自己发明的实战性的衡量考核标准)。

-显然作者并不是去讨论if else的语言问题,而是为自己的"洗脑培训"打广告!并讲这样的问题上升到设计模式、禅的境界,可谓是煞费苦心呀,没有人说设计模式不好也没有人怀疑禅的境界的高深,但不是作者这样的人靠读一两篇文章或者发布个所谓的"毫无实际意义"的框架就可以领悟到的,还是那句话:长得丑不要紧,不要出来吓人!不过我还要补充一句就是,不懂不要紧,不要乱说免得害人(因为我们都知道泼妇骂街的道理,虽然没文化但确实能够带来眼球效应)。

posted @ 2006-03-10 11:49 killvin| 编辑 收藏

该死,鼠标瘫痪了!
本来以为是Maxthon的问题(在鼠标单击的时候会关闭一些窗口,并且是随机的?!)还就这样的问题给作者写了一封信?汗。。。经过仔细的寻找原因,原来是自己的鼠标的问题(这个鼠标跟了我快7年了,Logitech最好的滚轴鼠标,要300多块大洋)有点舍不得,又没有办法,该下岗了,一同下岗的也许还有我的键盘(TCL的手感级差!:( )

不过为了找原因我倒是废了一番周折,下载和试用了N多的浏览器,也看了很多网上的评论对比文章,现在主流的浏览器主要就是Opera , Firefox, Maxthon , GreenBrowser , MyIE , TheWorld 等,然而在资源的利用率上这些个产品都非常的让人失望,难道就没有一款适合于程序员的胃口的浏览器?!

可喜的是偶发现了GOSURF这款浏览器,在众多的浏览器中它的内存占用率是最低的,而且它的作者似乎在这方面也是煞费苦心,"一味"的强调速度,甚至对于鼠标手这个及其流行的功能的加入也是非常的谨慎,可以看出作者是个很有个性的程序员,值得期待!

如果你和我一样对资源的利用率近乎苛刻,可以考虑这样的一款产品 http://gosurfbrowser.com/?ln=cn

posted @ 2006-03-07 17:42 killvin| 编辑 收藏

不错的文章
http://www-128.ibm.com/developerworks/cn/java/j-junit4.html

JUnit 是 Java™ 语言事实上的 标准单元测试库。JUnit 4 是该库三年以来最具里程碑意义的一次发布。它的新特性主要是通过采用 Java 5 中的标记(annotation)而不是利用子类、反射或命名机制来识别测试,从而简化测试。在本文中,执着的代码测试人员 Elliotte Harold 以 JUnit 4 为例,详细介绍了如何在自己的工作中使用这个新框架。注意,本文假设读者具有 JUnit 的使用经验。

JUnit 由 Kent Beck 和 Erich Gamma 开发,几乎毫无疑问是迄今所开发的最重要的第三方 Java 库。正如 Martin Fowler 所说,“在软件开发领域,从来就没有如此少的代码起到了如此重要的作用”。JUnit 引导并促进了测试的盛行。由于 JUnit,Java 代码变得更健壮,更可靠,bug 也比以前更少。JUnit(它本身的灵感来自 Smalltalk 的 SUnit)衍生了许多 xUnit 工具,将单元测试的优势应用于各种语言。nUnit (.NET)、pyUnit (Python)、CppUnit (C++)、dUnit (Delphi) 以及其他工具,影响了各种平台和语言上的程序员的测试工作。

然而,JUnit 仅仅是一个工具而已。真正的优势来自于 JUnit 所采用的思想和技术,而不是框架本身。单元测试、测试先行的编程和测试驱动的开发并非都要在 JUnit 中实现,任何比较 GUI 的编程都必须用 Swing 来完成。JUnit 本身的最后一次更新差不多是三年以前了。尽管它被证明比大多数框架更健壮、更持久,但是也发现了 bug;而更重要的是,Java 不断在发展。Java 语言现在支持泛型、枚举、可变长度参数列表和注释,这些特性为可重用的框架设计带来了新的可能。

JUnit 的停滞不前并没有被那些想要废弃它的程序员所打败。挑战者包括 Bill Venners 的 Artima SuiteRunner 以及 Cedric Beust 的 TestNG 等。这些库有一些可圈可点的特性,但是都没有达到 JUnit 的知名度和市场占有份额。它们都没有在诸如 Ant、Maven 或 Eclipse 之类的产品中具有广泛的开箱即用支持。所以 Beck 和 Gamma 着手开发了一个新版本的 JUnit,它利用 Java 5 的新特性(尤其是注释)的优势,使得单元测试比起用最初的 JUnit 来说更加简单。用 Beck 的话来说,“JUnit 4 的主题是通过进一步简化 JUnit,鼓励更多的开发人员编写更多的测试。”JUnit 4 尽管保持了与现有 JUnit 3.8 测试套件的向后兼容,但是它仍然承诺是自 JUnit 1.0 以来 Java 单元测试方面最重大的改进。

注意:该框架的改进是相当前沿的。尽管 JUnit 4 的大轮廓很清晰,但是其细节仍然可以改变。这意味着本文是对 JUnit 4 抢先看,而不是它的最终效果。

posted @ 2006-03-05 13:10 killvin| 编辑 收藏

http://blog.raylife.com/?p=336

在这个暖暖的午后享受着阳光的温暖的同时,听着CUSCO 的专辑实在是一种享受,我喜欢那种宁静的音乐,因为它带给你的除了纯净还是纯净。。。。

不想被任何的人打扰,城市的喧闹让自己的思想变得复杂,而唯独音乐才能够真正的唤醒你心灵深处的东西,

posted @ 2006-03-04 13:23 killvin| 编辑 收藏

目前在找工作阶段,参加了一些公司的面试,对于目前的一些公司的面试官我真的要说上两句,比如我去一家"非常著名"(至少在程序界是比较有名气的)的公司去面试,在回答一些无关痛痒的自我介绍后,技术总监突然问我"Struts是如何解决多配置文件的问题的?",当时就把我给问住了,主要是自己已经很久都没有做项目了,对于Struts也仅仅停留在使用这样的基础上,况且当时接触到的Struts是不支持多配置文件的,所以我只好乱说“将配置文件放在classpath就行了”,显然这样的答案是技术总监不愿意听到的,(想必很多的程序员也会这样认为),可是事情真的就是这样简单吗?

标准答案是:在web.xml中配置ActionServlet的config参数,在经过大脑短暂的思考后我断定我的回答并没有错!

谁说一定要配置这样的参数?那只是ActionServlet的"一厢情愿",如果有一天Struts不高兴了,将寻找配置文件的策略更改成:从classpath寻找,你认为我说的答案还是错误的吗?

所以我想说这样的面试问题是弱智的,一个人不可能知道所有的一切,对于现在这个社会如此众多的框架产品你怎么可能光靠某个问题就断定一个人的能力呢?

其实如果他可以这样问:你认为配置文件的读取策略可以有多少种?或者如果是你设计Struts你会如何解决多配置文件的问题?这样启发式的询问更会激起面试者的兴趣,难道这些个所谓的技术总监不该反思一下?

当然,不是仅仅一个这样的事件引起了自己点愤怒,而是很多很多的所谓的技术专家的弱智问题,不知道磨灭了多少人的热情!如果你也是遇到了这样的尴尬,索性问他一个自己非常另类的问题-比如"内部类如何访问外部类的对象?"

该死,请不要再问我弱智的问题了

posted @ 2006-03-03 17:53 killvin| 编辑 收藏

在OSWorkflow中最让人恼火的就是它的接口定义!我会就这些接口的混乱展开一系列的分析,今天先说说Configuration接口

偶继承了它的Configuration接口

import com.company.engine.workflow.store.IWorkFlowStore;
import com.opensymphony.workflow.StoreException;
import com.opensymphony.workflow.config.Configuration;
import com.opensymphony.workflow.spi.WorkflowStore;

public interface IConfiguration extends Configuration
{
 /**
  * @deprecated getIWorkflowStore()
  */
    WorkflowStore getWorkflowStore() throws StoreException;
   
    /**
     * return WorkFlowStore which implements the interface of IWorkFlowStore
     * @return
     * @throws StoreException
     */
    IWorkFlowStore getIWorkflowStore() throws StoreException;
   
}

你可能奇怪我为何要继承它的接口(肯定是Bad smell),原因如下,

IWorkFlowStore 接口定义

import com.opensymphony.workflow.StoreException;
import com.opensymphony.workflow.spi.Step;
import com.opensymphony.workflow.spi.WorkflowEntry;
import com.opensymphony.workflow.spi.WorkflowStore;

public interface IWorkFlowStore extends WorkflowStore
{
 
 public Step createCurrentStep(WorkflowEntry _entry , Step _step) throws StoreException;

}

WorkflowStore接口定义

    /**
     * Persists a step with the given parameters.
     *
     * @param entryId The workflow instance id.
     * @param stepId the ID of the workflow step associated with this new
     *               Step (not to be confused with the step primary key)
     * @param owner the owner of the step
     * @param startDate the start date of the step
     * @param status the status of the step
     * @param previousIds the previous step IDs
     * @return a representation of the workflow step persisted
     */
    public Step createCurrentStep(long entryId, int stepId, String owner, Date startDate, Date dueDate, String status, long[] previousIds) throws StoreException;

看到了吧?

其实我只是希望在createCurrentStep时按照OO的方法执行,而不是传递那些"Bad Smell"的参数,而OSWorkflow中的WorkflowStore是需要Configuration来获取的,此时为了增加一个看似合理的方法,需要分别继承Configuration与WorkflowStore;这还没有完,你需要实现一个Configuration实现!!

import com.company.engine.workflow.store.IWorkFlowStore;
import com.opensymphony.workflow.StoreException;
import com.opensymphony.workflow.config.DefaultConfiguration;
import com.opensymphony.workflow.spi.WorkflowStore;

public class DefaultIConfiguration extends DefaultConfiguration implements IConfiguration
{
 
    public static DefaultIConfiguration INSTANCE = new DefaultIConfiguration();
    private transient IWorkFlowStore store = null;

 /**
  * @deprecated getIWorkflowStore()
  */
 public WorkflowStore getWorkflowStore() throws StoreException
 {
  return null;
 }

 public IWorkFlowStore getIWorkflowStore() throws StoreException
 {
  if (store == null)
  {
   String clazz = getPersistence();

   try
   {
    store = (IWorkFlowStore) Class.forName(clazz).newInstance();
   }
   catch (Exception ex)
   {
    throw new StoreException("Error creating store", ex);
   }

   store.init(getPersistenceArgs());
  }

  return store;
 }

}

总结

1。OSWorkflow与WorkflowStore接口的关系比较的微妙,它需要借助于Configuration接口的实现来获取到实际的WorkflowStore对象。

2。由于这样的一种微妙关系,对WorkflowStore接口的扩展必将连带着需要扩展Configuration接口,而产生这样的"果冻效应"的罪魁祸首就是由于WorkflowStore接口与Configuration接口耦合的太紧。

3。OSWorkflow并没有很好的遵守OO的设计规则,尤其在它的参数传递上,非常的差!

posted @ 2006-03-02 21:04 killvin| 编辑 收藏

Workflow Project 目前状态
版本 0.11

已经完成
1。完成了接口1 和接口2 的方法
2。完成接口3的默认实现
3。完成事务回滚的实现方法-等待测试

未完成
1。接口3的注册与实例化解决方案
2。应用的并发访问问题以及解决数据的脏读问题
3。与具体的某个应用挂接并测试


-事务的回滚
OSWorkFlow的事务回滚是依靠WorkflowContext这个接口来实现的,在New出某个WorkFlow的时候需要声明WorkflowContext的实现类,一般会采用uper.context = new GearWheelWorkFlowContext(_caller);方法
比如这样实现:

public GearWheelWorkFlow(String _caller)
{
super.context = new GearWheelWorkFlowContext(_caller);
}

但OSWorkFlow的WorkflowContext的默认实现BasicWorkFlowContext中根本没有实现setRollbackOnly方法,也就没有了参考的可能

再看看这个接口的其他实现类也都是建立在JTA这样的跨Session的事务服务上,比如它的EJB的实现也是要调用容器提供的JTA实现才行!而JTA的实现比如要JNDI到数据库池,此时的应用光JTA+JNDI就已经宣布 -这样的例子必须生存在应用服务器的环境下!!

可是,我不死心,我记得Hibernate可以实现本地事务,也就是依靠JDBC本身的事务处理能力,而要实现这样的功能就需要在数据库连接的获取上下一些功夫,也就是要保证回滚的数据库连接必须是获取时的那个连接,而存储连接就成了一个需要首先解决的问题。

解决数据库连接的存储问题
目前存储数据库连接除了依靠静态类外,还有一个通用的方法ThreadLocal类,这样获取数据库连接的方法写成了如下的形式:

package com.company.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
public class DB2ConnectFactory
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(DB2ConnectFactory.class);
private static ThreadLocal threadLocal = new ThreadLocal();
//~
private Connection connect = null;
private Statement state = null;
private ResultSet result = null;
private boolean closeConnWhenDone = false;
//~
private String url = "jdbc:db2:WORKFLOW";
private String user = "";
private String password = "";
private String driverClassName = "COM.ibm.db2.jdbc.app.DB2Driver";

public DB2ConnectFactory() throws SQLException
{
this.init();
}

/**
* 获取数据库连接
* @return
* @throws SQLException
*/
public Connection getConn() throws SQLException
{
return (Connection)threadLocal.get();
}

/**
* 初始化数据库,并在缓冲中注册数据库连接
* @throws SQLException
*/
private void init() throws SQLException
{
try
{
// Get connect object
Class.forName(driverClassName);
closeConnWhenDone = true;

connect = DriverManager.getConnection(url, user, password);
state = connect.createStatement();

//Register the connection object in the threadlocal
threadLocal.set(connect);
}
catch (Exception e)
{
e.printStackTrace();
throw new SQLException(e.getMessage());
}
}

}

解决事务回滚
刚才说了需要实现WorkflowContext接口
package com.company.engine.workflow;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.log4j.Logger;
import com.company.common.DB2ConnectFactory;
import com.opensymphony.workflow.WorkflowContext;
public class GearWheelWorkFlowContext implements WorkflowContext
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(GearWheelWorkFlowContext.class);

private static ThreadLocal threadLocal = new ThreadLocal();

// ~ Instance fields
// ////////////////////////////////////////////////////////
private String caller;
// ~ Constructors
// ///////////////////////////////////////////////////////////
public GearWheelWorkFlowContext(String caller)
{
this.caller = caller;
}
// ~ Methods
// ////////////////////////////////////////////////////////////////
public String getCaller()
{
return this.caller;
}
/**
* Tranaction : Set Roll back
* @throws SQLException
*/
public void setRollbackOnly()
{
Connection connect = null;
try
{
DB2ConnectFactory factory = new DB2ConnectFactory();
connect = factory.getConn();
if(connect != null) connect.rollback();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
this.clostConnection(connect);
}
}

private void clostConnection(Connection connect)
{
try
{
if(connect != null) connect.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

总结
1。我们可以看到由于接口中setRollbackOnly没有异常的声明,方法中即使抛出了异常也要自己"忍了"!看来良好的接口声明其实是非常重要的。

2。而且需要重载原来JDBCWorkflow 中的cleanup方法,将其中的代码屏蔽掉!数据库的关闭放在了setRollbackOnly访访的finally中,原因就是由于我们要统一的管理数据库连接所引发的,我们不能够在WorkFlowStore的每一个方法执行完毕后就关闭连接,因为这样的话你根本没有了事务回滚的可能,所以此时的连接需要在WorkflowContext中来处理。


感触
OSWorkFlow的实现方法并不是像网上所说的那样的优秀和文雅,更像是一个未完成任务的"半成品",Heni被网上鼓吹为大牛,但一个不写注释和文档的人,根本称不上什么大牛!
OSWorkFlow更多的是实现了一个微内核,而它的用户模式是与OSUser这样的框架耦合的(偶已经将这样的耦合打开了,也就是接口3的定义),它的相关数据是与PropertySet框架耦合的(也就是接口2的定义),而且采用OSWorkFlow要经过很原始的修改(比如我实现了DB2下的WorkFlowStore的实现)。

不过也好即使以后不采用OSWorkFlow,自己实现一个这样的引擎也应该没有什么问题的,有时间了我倒是很想看看别的工作流的产品。

posted @ 2006-03-02 21:03 killvin| 编辑 收藏

workflow接口划分

1。应用接口 Application Interface
--interface1 工作流自身提供的服务接口
--interface2 工作流与应用之间的接口(主要是提供相关数据的调用接口)

2。扩展接口 PlugIn Interface
--interface3 工作流与组织机构之间的接口
--interface4 工作流与其他工作流之间的接口

将接口划分成应用接口与扩展接口主要是依据工作流与相关应用的调用关系,比如工作流与组织机构之间,是工作流调用组织机构中的人员信息,所以主动者是WORKFLOW、被动方是组织机构,所以应该采用扩展接口来实现

在扩展接口上应该采用Adapter模式,从而使工作流不局限于某个特定的实现

目前的进展
0。Application Interface接口已经基本实现了
PlugIn Interface接口目前基本完工,但OSWorkflow的实现实在是非常的丑陋,需要更改的地方太多,而且对于Interface3不可以使用它采用的User / Group模型(而且它使用了OSUser这个框架,对于多数的应用程序基本可以说不适合,而且它的User类竟然是Final ?!而且我发现它的很多类的属性都是Protected!也就是说除了他们自己根本没有办法扩展,即使扩展也是很丑陋的方式)

1。现在最大的问题是它的WorkStore接口的扩展,我采用DB2的方式实现了它的接口,但这样的方式会与DB2绑定在一起,如果自己写实现就要根据不同的DB采用不同的SQL语言-也就是不同的方言策略?!而且考虑到性能估计不是什么好主意,看来明天需要更换成HibernateWorkStore的形式,这样工作流的持久层接口将工作在Hibernate之上,看来很完美的解决了这个问题。

2。而且我扩展了它的PropertySet,使其不再依靠JNDI寻找DataSource,而是通过嵌入在程序内部采用JDBC的形式寻找数据库连接,这样我就不必为了验证一个问题去建立那该死的数据库缓冲池了(而且JNDI的形式也就不可避免的要用到容器,太重了!)

3。我编写了UserGroupCondition的实现类,这个类的作用就是调用Interface3的方法,从而判断某个用户是否属于某个组(现在的做法是让WorkStore实现Interface3的偷懒办法,但很乱,看来还是要写一个Adapter去实现interface3才对!)

4。目前工作流引擎的工厂类已经实现完工并测试通过。


用了近一个月的时间完成了这些工作,看起来很少但是基本上大量的时间花费在熟悉工作流规范、WFMC标准、以及学习和扩展OSWorkflow接口上,不过对OSWorkflow的实现基本上掌握了,如果抛开OSWorkflow自己也可以采用自己的方式去实现,或者会考虑使用Spring的方式(Interface3的Adapter不行就采用Spring实现)。

BTW:
OSWorkflow的实现其实比较的丑陋!而且编码根本没有什么规范,接口的定义也是天马行空,看来Heni除了他的大嘴外应该好好的提高自己的技术修养。-实在不敢恭维这位"大师"的编码水平!

posted @ 2006-03-02 21:03 killvin| 编辑 收藏

在Workflow事务回滚中遇到了问题,是这样的

DB2ConnectFactory 中getConn方法
/**
* 获取数据库连接
* @return
* @throws SQLException
*/
public Connection getConn() throws SQLException
{
Object obj = threadLocal.get();
if(obj == null)
{
this.initFactoryStack();
}else
{
connect = (Connection)obj;
}
connect.setAutoCommit(false); //事务的回滚必须建立在将Commit状态为False下,默认是true
logger.debug("Get connect from factory - " + connect.hashCode());
return connect;
}

AbstractWorkflow 的doAction()方法


try {
//transition the workflow, if it wasn't explicitly finished, check for an implicit finish
if (!transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps)) {
checkImplicitFinish(id);
}
} catch (WorkflowException e) {
context.setRollbackOnly(); // 这里调用WorkContext对象的setRollbackOnly()方法,执行事务的回滚
throw e;
}

GearWheelWorkFlowContext 的setRollbackOnly方法
/**
* Tranaction : Set Roll back
* @throws SQLException
*/
public void setRollbackOnly()
{
logger.debug("Context execute setRollbackOnly() !!");
Connection connect = null;
try
{
DB2ConnectFactory factory = new DB2ConnectFactory();
connect = factory.getConn();
logger.debug("Context get connect " + connect.hashCode());

if(connect != null) connect.rollback();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
this.clostConnection(connect); //这里将关闭数据库连接
}
}


可是这是"异常"情况下的处理流程,如果正常执行呢?
刚开始我想写在CleanUp()方法里,但又一想不行,因为正常执行的流程需要做两个工作
1。将Commit状态更新为true,并提交连接
2。关闭数据库连接


关键就是关闭数据库的连接在哪里写?!现在写在CleanUp()不合适,因为每一个WorkStore的方法都要默认(程序已经写死了,我可不想重载它的所有的方法!!)的关闭数据库连接!
仔细的分析了一下,其实有两个方法可以做到
1。编写Proxy类
2。重载所有AbstractWorkflow中设计到事务的方法,(本来可以重载transitionWorkflow但是方法的类型却为private?!)在它的方法下增加一个"提交"的方法。比如这样

try {
//transition the workflow, if it wasn't explicitly finished, check for an implicit finish
if (!transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps))
{
checkImplicitFinish(id);
}

dosubmit();

} catch (WorkflowException e) {
context.setRollbackOnly(); // 这里调用WorkContext对象的setRollbackOnly()方法,执行事务的回滚
throw e;
}

可以看到方法2比较"烂",看来下一步即使编写方法1的实现


posted @ 2006-03-02 21:02 killvin| 编辑 收藏

早上的时间被该死的WorkflwoStore里的主键生成策略("主键生成策略"来源于Hibernate文档),该死的Sequence,从文档资料上看到DB2是支持Sequence的,按照db2的文档我执行了如下的语句:
create sequence seq_os_wfentry start with 10 increment by 10;
create sequence seq_os_currentsteps;
执行-ok

可是我以前不太了解Sequence的概念,这片资料倒是很有价值
http://www-128.ibm.com/developerworks/db2/library/techarticle/dm-0407zhang/
不过我以为查询Sequence就只需要执行SELECT NEXT VALUE FOR seq_os_wfentry 就ok了,可是谁知道总是报错?!在比较仔细的看了这片文章之后发现,其实根本就无法执行这条SQL!而需要这样
INSERT INTO EMPLOYEE ( SERIALNUMBER, FIRSTNAME, LASTNAME,
SALARY) VALUES(NEXTVAL FOR EMPSERIAL, 'Martin', 'Wong', 1000.00)

可是看看JDBCWorkflowStore的实现,这里是JDBCWorkflowStore的主键生成策略!
protected long getNextEntrySequence(Connection c) throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Executing SQL statement: " + entrySequence);
}

PreparedStatement stmt = null;
ResultSet rset = null;

try {
stmt = c.prepareStatement(entrySequence);
rset = stmt.executeQuery();
rset.next();

long id = rset.getLong(1);

return id;
} finally {
cleanup(null, stmt, rset);
}
}

c.prepareStatement(entrySequence) - 其实执行了一条SQL语句,所以看来JDBCWorkflow根本不支持Sequence生成策略!!

该死的实现方式,看来我要重载其实现方式,不过说真的JDBCWorkflow的编码人员其实水平不匝地!

posted @ 2006-03-02 21:01 killvin| 编辑 收藏

OSPropertySet的最新版本是1.3 Date: 9/22/2003 ,不要使用OSWorkflow中自带的OSPropertySet.jar ,主要是因为它的版本为propertyset-1.3-21Apr04,甚至里面的接口PropertySet竟然私自更换了(这个版本的remove()方法是抽象的,而新版本已经将这个方法命名为Public!!)

posted @ 2006-03-02 21:00 killvin| 编辑 收藏

J2EE(即Java 2 平台企业版)是由Sun公司主持推出的一项中间件技术。从CORBA、IDL到面向消息的系统,中间件技术已经走过了很长的一段路程,如今J2EE作为中间件技术史上的一块具有决定意义的里程碑,正受到业界越来越广泛的重视和采纳。

J2EE,一方面有着一套相当庞大的标准体系和数个不同版本,另一方面,由于市场上应用服务器品种多样,各家开发商使用的术语又不尽相同,因此,围绕着J2EE,常常有不少被人误解的地方。本文将深入探讨J2EE究竟是什么,它到底能做什么。
什么是J2EE?
在试图给J2EE 下一个明确的定义之前,我们首先要了解J2EE 并不简单地只是一门语言、一种工具或一套服务。

· J2EE——Java 2 平台企业版
简单地说,J2EE是一个标准中间件体系结构,旨在简化和规范多层分布式企业应用系统的开发和部署。J2EE方案的实施可显著地提高系统的可移植性、安全性、可伸缩性、负载平衡和可重用性。

J2EE技术出现之前,几家主要的中间件开发商的产品各自为阵,彼此之间缺乏兼容性,可移植性差,难以实现互操作,没有一个被普遍认可的行业标准。J2EE的出现标志着中间件技术在经历了多年的不断摸索和经验总结后,正逐步走向成熟。

J2EE的核心是一组规范和指南,定义了一个使用Java语言开发多层分布式企业应用系统的标准平台。开发人员在这些规范和指南的基础上开发企业级应用,同时由J2EE供应商确保不同的J2EE平台之间的兼容性。由于基于规范的各J2EE平台之间具有良好的兼容性, 因此J2EE应用系统可以部署在不同的应用服务器上,无需或只需进行少量的代码修改。


· J2EE视点
下面我们将从几个不同的侧面来考察J2EE,以期读者能对J2EE有个更全面清晰的印象。

(1)J2EE:多层、分布式中间件语法
采用多层分布式应用模型,J2EE将应用开发划分为多个不同的层,并在每一个层上定义组件。各个应用组件根据他们所在的层分布在同一个或不同的服务器上,共同组成基于组件的多层分布式系统。典型的J2EE四层结构包括客户层、表示逻辑层(Web层)、商业逻辑层和企业信息系统层。

有了J2EE,分布式系统的开发变得简单了,部署的速度也可以加快。J2EE组件的分布与服务器环境无关,所有的资源都可通过分布式目录进行访问。这意味着开发人员不再需要为组件和资源的分布问题耗费精力,从而可以有更多的时间专注于业务逻辑的实现,提高开发效率。

(2)J2EE:企业级应用系统开发平台
J2EE本身是一个标准,一个为企业分布式应用的开发提供的标准平台。而J2EE的实施,则具体表现为诸如BEA Web logic或IBM Web sphere之类的特定Web服务器产品。利用J2EE应用-编程模型开发的企业应用系统,可以部署在不同厂商生产的、但相互兼容的J2EE 应用服务器上。

目前,市场上基于J2EE的Web服务器品种繁多,性能特点各有千秋,每家厂商的产品都有精心设计的独到之处。但与产品个性无关的是,所有的J2EE应用服务器都为企业级应用系统的开发和部署提供了一个共同的基础。

(3)J2EE:电子化应用开发模型
J2EE应用很容易发布到Web、掌上电脑或移动电话等手持设备上。换言之,应用组件可以很轻松地实现电子化。J2EE的应用-编程模型保证组件在向不同类型的客户端移植过程中,商业逻辑和后端系统保持不变。

此外,J2EE平台的其他主要优点还有:自动负载平衡、可伸缩、容错和具有故障排除等功能。部署在J2EE环境中的组件将自动获得上述特性,而不必增加额外的代码开销。

J2EE所有这些特性对于需要构建全天候网络门户的企业来说显得尤为重要。

(4)J2EE:Web应用服务器上广泛采用的标准
可以说,J2EE是首个获得业界广泛认可和采纳的中间件标准。目前几乎所有的一流Web应用服务器,如BEA的Web logic、IBM的Web sphere、HP的应用服务器、Sun的iPlanet和Macromedia的Jrun等,都是基于J2EE的。迄今为止,还没有哪个其他标准能获得如此众多的中间件供应商的一致支持。

而且,有了J2EE,企业的应用开发对于某个特定的开发商或应用服务供应商的依赖性更小。应用组件只要符合J2EE规范,完全可以部署在不同的应用服务器上。为了确保不同厂商的J2EE应用服务器的兼容性和一致性,Sun公司发布了J2EE兼容性测试包。

· J2EE究竟是什么
至此,我们可以试着用一句话来概括J2EE,那就是:J2EE是一个中间件基础架构,有了它,开发者只需要集中精力编写代码来表达企业应用的商业逻辑和表示逻辑,至于其他系统问题,如内存管理,多线程,资源分布和垃圾收集等,都将由J2EE自动完成。

J2EE如何应对挑战?
在这一部分里,我们将探讨J2EE是如何应对企业开发过程中所面临的问题,以及如何为企业未来发展之需要提供空间。

· 独立于硬件配置和操作系统
J2EE运行在Java虚拟机(JVM)上,利用Java本身的跨平台特性,独立于硬件配置和操作系统。Java运行环境(JRE)——JVM的可安装版本加上其他一些重要组件——几乎可以运行于所有的硬件/OS组合。因此,通过采用Java,J2EE使企业免于高昂的硬件设备和操作系统的再投资,保护已有的IT资源。在很多情况下,J2EE还可以直接运行在EIS服务器环境中,从而节约网络带宽,提高性能。


· 坚持面向对象的设计原则
作为一门完全面向对象的语言,Java几乎支持所有的面向对象的程序设计特征。面向对象和基于组件的设计原则构成了J2EE应用编程模型的基础。

J2EE多层结构的每一层都有多种组件模型。因此,开发人员所要做的就是为应用项目选择适当的组件模型组合,灵活地开发和装配组件,这样不仅有助于提高应用系统的可扩展性,还能有效地提高开发速度,缩短开发周期。此外,基于J2EE的应用还具有结构良好,模块化,灵活和高度可重用性等优点。

· 灵活性、可移植性和互操作性
利用Java的跨平台特性,J2EE组件可以很方便地移植到不同的应用服务器环境中。这意味着企业不必再拘泥于单一的开发平台。

J2EE的应用系统可以部署在不同的应用服务器上,在全异构环境下,J2EE组件仍可彼此协同工作。这一特征使得装配应用组件首次获得空前的互操作性。例如,安装在IBM Websphere环境下的EJB,一方面可以直接与Websphere环境下的CICS直接交互,另一方面也可以通过安装在别处的BEA Weblogic 服务器上的EJB进行访问。

· 轻松的企业信息系统集成
J2EE技术出台后不久,很快就将JDBC、 JMS和 JCA等一批标准纳归自身体系之下,这大大简化了企业信息系统整合的工作量,方便企业将诸如legacy system(早期投资系统),ERP和数据库等多个不同的信息系统进行无缝集成。

由于几乎所有的关系型数据库系统都支持JDBC,因此只需借助必要的JDBC驱动程序,J2EE应用就可以和所有主流数据库系统进行通信。类似的,目前业界正冒出一批基于Java连接器体系标准的EI适配器,也用于提供各类legacy system和ERP/CRM的无缝集成。

· 引进面向服务的体系结构
随着Web服务以及SOAP等开放标准的出现,企业异构系统之间的互操作性成为可能。J2EE,作为一个可扩展平台,很自然需要加入Web服务特性。为此,Sun公司发布了一整套称为“JAX包”的API,支持从XML语法分析、XML绑定、SOAP消息发送、注册表查寻、XML RPC到XML消息传递等所有各种Web服务需求。

虽然J2EE平台的出现早于Web服务技术,但它的可扩展能力使它能很好地适应技术的最新发展。我们有理由相信,在未来,J2EE将引入更多的技术进步而不会动摇它的核心框架和应用-编程模型。

结束语
作为一个被业界广泛采用的中间件标准,J2EE是开发可伸缩的、具有负载平衡能力的多层分布式跨平台企业应用的理想平台。J2EE的首要任务在于提供一个标准中间件基础架构,由该基础架构负责处理企业开发中所涉及的所有系统级问题,从而使得开发人员可以集中精力重视商业逻辑的设计和应用的表示,提高开发工作的效率。

J2EE有效地满足了行业需求,提供独立于操作系统的开发环境。基于J2EE的应用系统灵活且易于移植和重用,可运行在不同厂家的Web服务器上。更为重要的是,J2EE是一个开放体系,完全有能力适应未来技术的进步和发展。

posted @ 2006-03-02 20:59 killvin| 编辑 收藏

该文转自guty

O-R Mapping

J2EE的标准是CMP Entity Bean,而实际应用中受到诟病最多的也是它。我们化了整整半年时间研究CMP2.0的开发方法,目前总算能够将代码量减少到70%,并且有希望减少到 90%。我曾经很满足现有的成绩,但是当我真正地阅读了hibernate后,对CMP2.0的信心彻底动摇了。

hibernate至少比CMP2.0有以下优点:
1. 兼容性。 规范一模一样,实现各有不同,这是CMP的现状。用第三方O-R Mapping工具可以解决这个问题。
2. 保护智力投资。在了解了Orion, Weblogic, JBoss的CMP实现后,我不愿意再去学习Websphere 或者Resin的实现了。
3. 性能。
a. local v.s. remote, hibernate、JDO、Castor都是本地调用,CMP2.0虽然也有Local接口,但是Web层还是需要通过Remote接口访问EJB层的数据,序列化、网络调用、创建大量的对象,都是性能降低的原因。
b. transaction,J2EE提出了一个全新的事务模型(method-based descriptor),对程序员的开发确实是个“简化”,记得一本教程建议所有的EJB方法都用Required。但这样的结果是什么?性能极度降低!互锁!没有办法,我们只有再去调节各个方法的Transaction属性,然后又出现 新的互锁...
新的事务模型是不成功的。它试图简化问题,却引入了更为严重的问题。各家厂商的Transaction实现也不尽相同,有的支持Optimistic Lock,有的在VM中同步Entity对象,又是兼容性的一大敌。
hibernate没有试图创造一个更新的模式,相反,它沿用了传统数据库的Transaction编程模式,在对J2EE的Transaction伤透脑筋后看到它,真是十分亲切,感觉自己确实在编程,而不是碰运气填代码了。
4. 动态Query。
Entity Bean很难实现动态Query,这是因为它基于代码自动生成技术,即最终的执行代码是在部署编译时生成的。hibernate则有根本的改变,它基于 reflection机制,运行时动态Query是很自然的事。另外,hibernate几乎支持所有的SQL语法,传统数据库可以做的它就可以做。
5. 发展速度。
I have a dream, 有一天Entity Bean会变得很好。但至少目前来看,Entity Bean是一个不完善的产品,它是大公司政治斗争和妥协的产品,而且习惯性将一些问题“无限期搁置”,典型的例子就是Query(之所以不提其他问题,是因为其他都是Entity Bean的致命伤:))
形成强烈反差的是,hibernate的核心程序员只有一人,但它改进的速度确是Entity Bean无法企及的。
6. 继承和多态。
OO语言的精华在Entity Bean这里是行不通的,我曾经自我安慰将Entity Bean看做一个“内存中的数据表”,才找到了一点平衡。
但当我看到hibernate时,又开始不平衡了。

另外,CMP2.0也有一些缺点是可以弥补的。
1. 代码维护。
大量的接口文件和配置文件,开发和维护的工作量很大。
解决途径:采用xdoclet,可以自动产生众多的接口和配置文件,甚至facade, delegate等高级模式。

至少目前来看,hibernate的缺点有:
1. 代码维护
hibernate提供了自动生成mapping文件“框架”的工具,但还需要手工调节。而这类开发,能想到的最佳模式就是xdoclet的(代码+注释)的模式了。幸好,hibernate的程序员已经向xdoclet项目增加了hibernate的模块。现在需要的是等待xdoclet的下一个 release。

结论:
hibernate至少从文档上超越了Entity Bean很多,我要学习hibernate。

以下是robbin的观点

如果说不使用Session Facade模式的话,我认为EB还是一个很有意义的的东西,因为EB是唯一直接支持跨RMI的持久化方案。但是由于EB的效率和减少跨RMI的网络调用的原因,EB已经完全被封装到SB的后面,EB的分布式调用的功能,EB的安全验证功能,EB的容器事务功能完全被前面的SB给做了,结果EB就只剩下了唯一的ORM功能了,单就ORM这一点来说EB实在是一个非常非常糟糕的东西。那么EB还有什么功能值得我非去用它不可呢?

用 Session Bean + DAO + Hibernate 来取代 Session Bean + Entity Bean,不但能够极大降低软件设计难度,软件开发难度,软件调试难度和软件部署难度,而且还可以提高允许效率,降低硬件要求。

不要把EB直接拿来和Hibernate做比较,两者不是一个范畴的东西,而应该整体比较两种方案:
Session Bean + DAO + Hibernate
Session Bean + Entity Bean
我找不出来第二方案有哪怕一点方面能够比第一方案好的。

CMP可以使用CMR来表示多表之间通过外键关联的关系。但是你仍然会遇到即使没有键关联的表仍然需要连接查询的情况,这是一个非常普遍的现象。

如果是Hibernate,可以在HSQL里面定义outer join,BMP也可以写JDBC,而CMP没有任何办法来解决该问题,除非你把需要的连接查询都定义为CMR,但那样的话,凡是有需要连接查询,或者有键关联的表都必须打在一个包里面。你如果不打在一个jar包里面,如果能够建立CMR?不是我想放在一个jar里面,而是不得不放在一个jar里面。基本上CMP还是非常笨拙的。

CMP的另一大缺点是不能动态SQL,guty已经提到了,一个SQL就要定义一个EJBFinder方法,在编译的时候就确定死了。在实际应用中,经常会遇到不确定查询条件的查询,比如说用户在页面上用下拉列表来选择查询的条件,用户既有可能什么限制条件都不选,也有可能选择某几个条件。这时候你怎么办?假设有n个查询条件,你要写 C1n + C2n + C3n +...+ Cnn(C是组合公式的符合,n是下标,1...n是上标)个EJBFinder方法才行,很恐怖吧。

其实JDBC的PrepareStatement也不能很好的解决这个问题,因为它是按照1,2这样的次序来set参数的。用Statement是肯定不行的,会严重影响数据库,甚至会导致数据库down掉(我的实际经验)。但是Hibernate就解决的不错,因为它可以按照 :name 这样的形式来设定SQL中的Placeholder,这样set参数就可以按照参数名称传递,因为次序不是死的,在程序里面就很容易根据用户选择的查询条件,动态的产生SQL,动态的set参数了。

CMP2.0还有一个大问题是不支持order by,当然你可以在Java里面对取出来的集合排序,但是速度和数据库里面就排好序速度不在一个数量级了。Hibernate不但可以order by,还可以group by,having,子查询,真是没有办法比下去了。

其实对于动态SQL和排序问题,特定的App Server也可以做,但那不是CMP2.0的规范罢了,所以为了可移植性,也不敢随便去用。

在项目开发时, 开发和运行效率以及灵活性是非常重要的指标。由于Entity Bean天生是一种粗粒度的使用方式,这就必定使它在装载的时候有较长的响应时间,也不能自如的支持懒装入的方式,使用成细粒度会使程序变得复杂,以及远程调用细粒度的entity bean是一种非常可怕的行为, 太慢了.

Hibernate正好满足开发和运行效率以及灵活性,说来说去,它可以称做一个OO化的JDBC, 这样大家就不会对Hibernate产生误解及恐惧心理。它支持粗细两种粒度方式,运用起来灵活自如,前提是你必知道如何使用,一个entity bean 实现要N种重复的方法, such as ejbRemove,ejbstore,ejb...., 光类也有一大堆,象Home Interface, Romote Interface..., Primary class if necessary. Hibernate只需要一个就行了。

CMP在进行O/R Mapping方面只是做了最基础的工作而已,完全用CMP做数据层,会发现你在把数据库应该做的工作全部都搬到App Server里面来重新实现一遍,有这必要吗?

CMP是把EJBQL写死在ejb-jar.xml里面的,所以n个条件就需要(c0n+c1n+...cnn )2的n次方个EJBFinder方法,简直没有办法说。

JDBC实现PrepareStatement的动态SQL构造不是不能够,而是非常麻烦,需要写一个非常非常大的if elseif else嵌套的判断。

Hibernate实现起来特别简单,(其实OJB也实现了PrepareStatement的动态SQL构造)这本身并不复杂,但是需要你多写些代码而已,由于CMP把EJBQL写死在配置文件里面了,你连选择的余地都没有。

posted @ 2006-03-02 20:58 killvin| 编辑 收藏

A reader asked a question via a comment a couple months ago that I didn't really have an answer for (and had always kind of wondered the same thing). In the original post (which showed how to use JDBC with ColdFusion), I used the following snippet of code:

Class.forName("jdbc.DriverXYZ");
Connection con = DriverManager.getConnection(url,
  "myLogin", "myPassword");

and the reader wanted to know what the Class.forName(..) method did. The most common answer you'll hear is that it loads the database driver, which, while technically true, is shallow. Where does it get loaded? How does it happen? And why?

To answer the question I started with the JavaDoc for the Class.forName() method. According to the documentation, the method:

... attempts to locate, load, and link the class or interface
I wasn't perfectly clear on what "locate, load, and link" meant, so I did a little digging through the Java Language Specification. According to chapter 12 of the JLS:
Loading refers to the process of finding the binary form of a class or interface type with a particular name, perhaps by computing it on the fly, but more typically by retrieving a binary representation previously computed from source code by a compiler, and constructing, from that binary form, a Class object to represent the class or interface.
Next, again according to the JLS, it must be transformed from it's binary representation to something the Java virtual machine can use, this process is called linking. Finally, the class is initialized, which is the process that executes the static initializer and the initializers for static fields declared in the class.

So then back to the original problem, when Class.forName() is called with an argument like this:

Class.forName("org.gjt.mm.mysql.Driver");

the classloader attempts to load and link the Driver class in the "org.gjt.mm.mysql" package and if successful, the static initializer is run. The MySQL Driver (download the source code) static initializer looks like this:

static {
  try {
    java.sql.DriverManager.registerDriver(new Driver());
  } catch (SQLException E) {
    throw new RuntimeException("Can't register driver!");
  }
}

So it calls a static method in the java.sql.DriverManager class which apparently registers a copy of itself when it loads.

So now I understand the where and the how, what about why? To understand the why you have to look at the next line in the initial code example:

Connection con = DriverManager.getConnection(url,
  "myLogin", "myPassword");

The DriverManager class (view DriverManager source here) returns a database connection given a JDBC URL string, a username and a password. In order to create that connection, the DriverManager class has to know which database driver you want to use. It does that by iterating over the array (internally a Vector) of drivers that have registered with it (ie: the registerDriver(Driver driver) method illustrated above) and calls the acceptsURL(url)) method on each driver in the array, effectively asking the driver to tell it whether or not it can handle the JDBC URL.

So there you have it. Class.forName explained.

posted @ 2006-03-02 20:57 killvin| 编辑 收藏

原文: http://forum.javaeye.com/viewtopic.php?t=17912


搂主的问题问的含含糊糊:flyjie给出了非常详细的解释,
不过就是没有解释String实例化的特殊方面以及Intern()方法的含义

-------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
java代码:
String str ;

这样声明str它只支是一个对象的reference,不会产生实际的对象。如果没有初始化str,编译时便会发生错误。
java代码:
String str1=new String("test");
String str2 = "test";

str1是一个新的对象。new关键字的意思就是创建某个新的对象。而str2是一个对象的引用。 它们的内容相同,但内存地址是不一样的。 java中对象的引用存在Stack(栈)中,而对象由Heap(堆)分配空间。

3、引用==变量?  不一定
java代码:

  public class TestString {
public static void main(String[] args) {
String s1 = "test";
String s2 = new String("test");
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))
System.out.println("s1 equals s2");
else System.out.println("s1 not equals s2");
}
}

我们将 s2 用 new 操作符创建程序输出:s1 != s2 s1 equals s2.
java代码:

s2 = s2.intern();

在你加上这句话后,上面的程序输入:s1 == s2 s1 equals s2

而String a = "test" ; String b = "test" ; a == b 会返回true; 这里a="test"时创建一个在栈中的reference, b=test时jvm发现栈中已存在名为"test"的字符串,直接引用。结论:String 是个对象,要对比两个不同的String对象的值是否相同明显的要用到 equals() 这个方法. 而== 比较的是内存地址的值。

4、private final String a = "test", 这个a属于常量,存放在常量存储空间(CS)中。

5、建议你看看<<深入浅出java虚拟机>>一书。

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



总结

1. 在学习JAVA的时候就知道==比较的是内存地址.而equals比较的是内存地址对应的值!(可是还是有很多的人问来问去的,真不知道他们JAVA的基础课程是怎么学的?!)

2. JAVA所有的对象都是存放在堆中的!你获取的"对象"仅仅只是对象的引用而已

3. String是比较特殊的对象,特殊在
3.1 > String a = new String("test") -此时你是在堆中实例化了一个字符串对象
3.2 > String b = "test"-此时JVM会先去堆中寻找这样的对象;如果有就返回此对象的引用;如果没有就重新实例化一个这样的对象!基于这样的一个过程所以JAVA要求String不可以更改值的。

3.3 >intern()方法就是试图完成这样的一个寻找过程
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

这里有一份详细的参考资料:

关于Java栈与堆的思考 http://www.javafan.net/article/20051123115654293.html


posted @ 2006-03-02 20:56 killvin| 编辑 收藏

URL : http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html

网上的一些关于内部类的概念是不完整的,还是看看SUN的文档上的标准答案。
...

Like other members, a nested class can be declared static (or not). A static nested class is called just that: a static nested class. A nonstatic nested class is called an inner class.

- Nested class分为静态Static nested class 的和非静态的 inner class, 在SUN的眼里只有Nested Class!!


As with static methods and variables, which we call class methods and variables, a static nested class is associated with its enclosing class. And like class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class — it can use them only through an object reference.

- 静态的Static nested class是不可以直接调用它的外部类enclosing class的,但是可以通过外部类的引用来调用,就像你在一个类中写了main方法一样。
...

As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's instance variables and methods. Also, because an inner class is associated with an instance, it cannot define any static members itself.

-非静态类inner class 可以自由的引用外部类的属性和方法,但是它与一个实例绑定在了以其,不可以定义静态的属性、方法(这点不是很理解,可能需要看JVM的类实现)

...
class EnclosingClass {
...
class InnerClass {
...
}
}


The interesting feature about the relationship between these two classes is not that InnerClass is syntactically defined within EnclosingClass. Rather, it's that an instance of InnerClass can exist only within an instance of EnclosingClass and that it has direct access to the instance variables and methods of its enclosing instance. The next figure illustrates this idea.


-图形化的嵌入类与外部类的关系

posted @ 2006-03-02 20:55 killvin| 编辑 收藏

初识Java内部类
    提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比。内部类从表面上看,就是在类中又定义了一个类(下文会看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,但是随着对它的深入了解,你会发现Java的设计者在内部类身上的确是用心良苦。学会使用内部类,是掌握Java高级编程的一部分,它可以让你更优雅地设计你的程序结构。下面从以下几个方面来介绍:
    ·第一次见面
public interface Contents {
    int value();
}
public interface Destination {
    String readLabel();
}
public class Goods {
    private class Content implements Contents {
        private int i = 11;
        public int value() {
            return i;
        }
    }
    protected class GDestination implements Destination {
        private String label;
        private GDestination(String whereTo) {
            label = whereTo;
        }
        public String readLabel() {
            return label;
        }
    }
    public Destination dest(String s) {
        return new GDestination(s);
    }
    public Contents cont() {
        return new Content();
    }
}
class TestGoods {
    public static void main(String[] args) {
        Goods p = new Goods();
        Contents c = p.cont();
        Destination d = p.dest("Beijing");
    }
}
    在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了——隐藏你不想让别人知道的操作,也即封装性。
    同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:
    outerObject=new outerClass(Constructor Parameters);
    outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);
    注意在创建非静态内部类对象时,一定要先创建起相应的外部类对象。至于原因,也就引出了我们下一个话题——
    ·非静态内部类对象有着指向其外部类对象的引用
    对刚才的例子稍作修改:
public class Goods {
    private valueRate=2;
    private class Content implements Contents {
        private int i = 11*valueRate;
        public int value() {
            return i;
        }
    }
    protected class GDestination implements Destination {
        private String label;
        private GDestination(String whereTo) {
            label = whereTo;
        }
        public String readLabel() {
            return label;
        }
    }
    public Destination dest(String s) {
        return new GDestination(s);
    }
    public Contents cont() {
        return new Content();
    }
}
    修改的部分用蓝色显示了。在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。我们发现,value()可以访问valueRate,这也是内部类的第二个好处——一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。
    有人会问,如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了,怎么办?没事,Java里用如下格式表达外部类的引用:
outerClass.this
    有了它,我们就不怕这种屏蔽的情况了。
    ·静态内部类
    和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。这实际上和C++中的嵌套类很相像了,Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用这一点上,当然从设计的角度以及以它一些细节来讲还有区别。
    除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。这也算是两者的第二个区别吧。
    ·局部内部类
    是的,Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。
public class Goods1 {
     public Destination dest(String s) {
          class GDestination implements Destination {
               private String label;
               private GDestination(String whereTo) {
                    label = whereTo;
               }
               public String readLabel() { return label; }
          }
          return new GDestination(s);
     }
     public static void main(String[] args) {
          Goods1 g= new Goods1();
          Destination d = g.dest("Beijing");
     }
}
    上面就是这样一个例子。在方法dest中我们定义了一个内部类,最后由这个方法返回这个内部类的对象。如果我们在用一个内部类的时候仅需要创建它的一个对象并创给外部,就可以这样做。当然,定义在方法中的内部类可以使设计多样化,用途绝不仅仅在这一点。
    下面有一个更怪的例子:
public class Goods2{
     private void internalTracking(boolean b) {
          if(b) {
               class TrackingSlip {
                    private String id;
                    TrackingSlip(String s) {
                         id = s;
                    }
                    String getSlip() { return id; }
               }
               TrackingSlip ts = new TrackingSlip("slip");
               String s = ts.getSlip();
          }
     }
     public void track() { internalTracking(true); }
     public static void main(String[] args) {
          Goods2 g= new Goods2();
          g.track();
     }
}
    你不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类TrackingSlip和其他类一样同时被编译,只不过它由它自己的作用域,超出了这个范围就无效,除此之外它和其他内部类并没有区别。
    ·匿名内部类
    java的匿名内部类的语法规则看上去有些古怪,不过如同匿名数组一样,当你只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:
    new interfacename(){......}; 或 new superclassname(){......};
    下面接着前面继续举例子:
public class Goods3 {
     public Contents cont(){
          return new Contents(){
               private int i = 11;
               public int value() {
                    return i;
               }
          };
     }
}
    这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象,看上去的确十分简洁。
    在java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:
frame.addWindowListener(new WindowAdapter(){
     public void windowClosing(WindowEvent e){
          System.exit(0);
     }
});
    有一点需要注意的是,匿名内部类由于没有名字,所以它没有构造函数(但是如果这个匿名内部类继承了一个只含有带参数构造函数的父类,创建它的时候必须带上这些参数,并在实现的过程中使用super关键字调用相应的内容)。如果你想要初始化它的成员变量,有下面几种方法:
    1.如果是在一个方法的匿名内部类,可以利用这个方法传进你想要的参数,不过记住,这些参数必须被声明为final。
    2.将匿名内部类改造成有名字的局部内部类,这样它就可以拥有构造函数了。
    3.在这个匿名内部类中使用初始化代码块。
    ·为什么需要内部类?
    java内部类有什么好处?为什么需要内部类?
    首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。
    不过你可能要质疑,更改一下方法的不就行了吗?
    的确,以此作为设计内部类的理由,实在没有说服力。
    真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题——没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。

posted @ 2006-03-02 20:54 killvin| 编辑 收藏

引言
在使用response的过程中经常会遇到跳转页面的事情,这个时候有两种情况供你选择
1。就是调用
ServletContext.getRequestDispatcher(java.lang.String).forward(request ,
response) ;
2。就是调用response.setRedirect(),可是这两个方法有什么不同呢?


看看TSS上关于这个问题的解释:
Difference request.forward() and response.sendRedirect .
Posted by: Kapil Israni on August 24, 2000 in response to Message #2253 1
replies in this thread
i suppose u r talking bout requestDispatcher.forward() here.

well basically both method calls redirect u to new resource/page/servlet.

the difference between the two is that sendRedirect always sends a header
back to the client/browser. this header then contains the
resource(page/servlet) which u wanted to be redirected. the browser uses
this header to make another fresh request. thus sendRedirect has a overhead
as to the extra remort trip being incurred. its like any other Http request
being generated by ur browser. the advantage is that u can point to any
resource(whether on the same domain or some other domain). for eg if
sendRedirect was called at www.mydomain.com then it can also be used to
redirect a call to a resource on www.theserverside.com.

where as in case of forward() call, the above is not true. resources from
the server, where the fwd. call was made, can only be requested for. but
the major diff between the two is that forward just routes the request to
the new resources which u specify in ur forward call. that means this route
is made by the servlet engine at the server level only. no headers r sent
to the browser which makes this very eficient. also the request and
response objects remain the same both from where the forward call was made
and the resource which was called.

i hope i have hit ur question right.

posted @ 2006-03-02 20:54 killvin| 编辑 收藏

Java Language Keywords

Here's a list of keywords in the Java language. These words are reserved — you cannot use any of these words as names in your programs. true, false, and null are not keywords but they are reserved words, so you cannot use them as names in your programs either.

abstract    |   continue    |   for    |    new     |   switch
assert***  |   default     |   goto*     |   package     |   synchronized
boolean    |   do     |   if     |   private     |   this
break         |   double     |   implements    |   protected    |   throw
byte            |   else     |   import     |   public  throws
case          |   enum****    |   instanceof    |   return     |   transient 
catch         |   extends     |   int     |   short     |   try
char           |   final     |   interface    |   static     |   void
class         |   finally     |   long     |   strictfp**    |   volatile
const*       |   float     |   native     |   super     |   while


*   not used
**   added in 1.2
***   added in 1.4 
****   added in 5.0                                    

 

Key: strictfp**

使用对象:类、方法

自Java2以来,Java语言增加了一个关键字strictfp,虽然这个关键字在大多数场合比较少用,但是还是有必要了解一下。

strictfp的意思是FP-strict,也就是说精确浮点的意思。在Java虚拟机进行浮点运算时,如果没有指定strictfp关键字时,Java的编译器以及运行环境在对浮点运算的表达式是采取一种近似于我行我素的行为来完成这些操作,以致于得到的结果往往无法令你满意。而一旦使用了strictfp来声明一个类、接口或者方法时,那么所声明的范围内Java的编译器以及运行环境会完全依照浮点规范IEEE-754来执行。因此如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,那就请用关键字strictfp。

你可以将一个类、接口以及方法声明为strictfp,但是不允许对接口中的方法以及构造函数声明strictfp关键字,例如下面的代码:

1. 合法的使用关键字strictfp

strictfp interface A {}

public strictfp class FpDemo1 {
    strictfp void f() {}
}

2. 错误的使用方法

interface A {
    strictfp void f();
}

public class FpDemo2 {
    strictfp FpDemo2() {}
}

一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。例如一个类被声明为strictfp,那么该类中所有的方法都是strictfp的。

 

Keys: volatile

使用对象:字段

介绍:因为异步线程可以访问字段,所以有些优化操作是一定不能作用在字段上的。volatile有时

可以代替synchronized。

 

Keys:transient

  使用对象:字段

  介绍:字段不是对象持久状态的一部分,不应该把字段和对象一起串起。

posted @ 2006-03-02 20:53 killvin| 编辑 收藏

引言

 记得当初参与某公司的ERP项目中,接触过异常框架这个概念,可是似乎并没有感觉到当时技术经理提出这个概念的意义,而且他也对这个概念似乎很"保守",虽然按照他的思路去执行,但没有理解的概念再实施起来的时候总是觉得很"别扭",而如今面对自己要设计咚咚了,不得不重新审视异常这个概念,JAVA异常的介绍文章在网络上非常的少,而对于如何构件J2EE的异常处理框架更显的稀少,于是就促使自己写下了这样的文章。

 本文只是自己的一些愚见,希望和大家相互学习。Email:Killvin@hotmail.com

概念

什么是异常?

异常(exception)应该是异常事件(exceptional event)的缩写。
异常定义:异常是一个在程序执行期间发生的事件,它中断正在执行的程序的正常的指令流。
当在一个方法中发生错误的时候,这个方法创建一个对象,并且把它传递给运行时系统。这个对象被叫做异常对象,它包含了有关错误的信息,这些信息包括错误的类型和在程序发生错误时的状态。创建一个错误对象并把它传递给运行时系统被叫做抛出异常。
一个方法抛出异常后,运行时系统就会试着查找一些方法来处理它。这些处理异常的可能的方法的集合是被整理在一起的方法列表,这些方法能够被发生错误的方法调用。这个方法列表被叫做堆栈调用(call stack)

运行时系统搜寻包含能够处理异常的代码块的方法所请求的堆栈。这个代码块叫做异常处理器,搜寻首先从发生的方法开始,然后依次按着调用方法的倒序检索调用堆栈。当找到一个相应的处理器时,运行时系统就把异常传递给这个处理器。一个异常处理器要适当地考滤抛出的异常对象的类型和异常处理器所处理的异常的类型是否匹配。异常被捕获以后,异常处理器关闭。如果运行时系统搜寻了这个方法的所有的调用堆栈,而没有找到相应的异常处理器。

 

怎么设计异常框架

任何的异常都是Throwable类(为何不是接口??),并且在它之下包含两个字类Error / Exception,而Error仅在当在Java虚拟机中发生动态连接失败或其它的定位失败的时候,Java虚拟机抛出一个Error对象。典型的简易程序不捕获或抛出Errors对象,你可能永远不会遇到需要实例化Error的应用,那就让我们关心一下Exception

Exception中比较重要的就是RuntimeException-运行时异常(当然这个名字是存在争议的,因为任何的异常都只会发生在运行时),为什么说这个类时很重要的呢?因为它直接关系到你的异常框架的设计,仔细看RuntimeException

A method is not required to declare in its throws clause any subclasses of RuntimeException that might be thrown during the execution of the method but not caught.

-可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。

也就是说你的应用应该不去“关心”(说不关心是不服责任的,但只是你不应该试图实例化它的字类)RuntimeException,就如同你不应该关心Error的产生与处理一样!RuntimeException描述的是程序的错误引起来的,因该由程序负担这个责任!(<B>从责任这个角度看Error属于JVM需要负担的责任;RuntimeException是程序应该负担的责任;checked exception 是具体应用负担的责任</B>)

那就有人会问,那我该关心什么!答案就是除了Error与RuntimeException,其他剩下的异常都是你需要关心的,而这些异常类统称为Checked Exception,至于Error与RuntimeException则被统称为Unchecked Exception.


异常的概念就这些了,即使你在网络上搜索也就不过如此,是不是感觉到有点清晰又有点模糊?那么怎么该如何在这样单薄而模糊的概念下设计J2EE的异常框架呢?


解决方案:J2EE异常框架

我们拿一个模拟的例子来说明异常框架的设计过程,比如我们要对外提供doBusiness()这个业务方法

public void doBusiness() throws xxxBusinessException

当客户端调用这样的方法的时候应该这样处理异常(包括处理RuntimeException , checked exception)
<B>记住,无论如何我们都不希望或者确切的说是不应该将RuntimeException这样的异常暴露给客户的,因为他们没有解决这个问题的责任!</B>
我们暂时将Struts中的某个Action看作时客户端,其中doExecute(....)要调用doBusiness()这个方法

public void doAction(......)
{
 try
 {

  xxx.doBusiness();
 }
 catch(Exception e)
 {
   if(e instanceof RuntimeException) 
   {
    // catch runtime exception
    // 你可以在这里将捕获到的RuntimeException
    // 将异常通知给某个负责此程序的程序员,让他知道他
    // 自己犯了多么低级的错误!


   }else
   {
    //checked exception such as xxxBusinessException
    //将这样的异常暴露给客户显示    

   }

 }
}

我们可以这样设计xxxBusinessException

public class xxxBusinessException extends ApplicationException
{
    public xxxBusinessException(String s){
        super(s);

};

import java.io.PrintStream;
import java.io.PrintWriter;
public class ApplicationException extends Exception {
       /** A wrapped Throwable */
       protected Throwable cause;
       public ApplicationException() {
           super("Error occurred in application.");
       }
       public ApplicationException(String message)  {
           super(message);
       }
       public ApplicationException(String message, Throwable cause)  {
           super(message);
           this.cause = cause;
       }
       // Created to match the JDK 1.4 Throwable method.
       public Throwable initCause(Throwable cause)  {
           this.cause = cause;
           return cause;
       }
       public String getMessage() {
           // Get this exception's message.
           String msg = super.getMessage();
           Throwable parent = this;
           Throwable child;
           // Look for nested exceptions.
           while((child = getNestedException(parent)) != null) {
               // Get the child's message.
               String msg2 = child.getMessage();
               // If we found a message for the child exception,
               // we append it.
               if (msg2 != null) {
                   if (msg != null) {
                       msg += ": " + msg2;
                   } else {
                       msg = msg2;
                   }
               }
               // Any nested ApplicationException will append its own
               // children, so we need to break out of here.
               if (child instanceof ApplicationException) {
                   break;
               }
               parent = child;
           }
           // Return the completed message.
           return msg;
       }
       public void printStackTrace() {
           // Print the stack trace for this exception.
           super.printStackTrace();
           Throwable parent = this;
           Throwable child;
           // Print the stack trace for each nested exception.
           while((child = getNestedException(parent)) != null) {
               if (child != null) {
                   System.err.print("Caused by: ");
                   child.printStackTrace();
                   if (child instanceof ApplicationException) {
                       break;
                   }
                   parent = child;
               }
           }
       }
       public void printStackTrace(PrintStream s) {
           // Print the stack trace for this exception.
           super.printStackTrace(s);
           Throwable parent = this;
           Throwable child;
           // Print the stack trace for each nested exception.
           while((child = getNestedException(parent)) != null) {
               if (child != null) {
                   s.print("Caused by: ");
                   child.printStackTrace(s);
                   if (child instanceof ApplicationException) {
                       break;
                   }
                   parent = child;
               }
           }
       }
       public void printStackTrace(PrintWriter w) {
           // Print the stack trace for this exception.
           super.printStackTrace(w);
           Throwable parent = this;
           Throwable child;
           // Print the stack trace for each nested exception.
           while((child = getNestedException(parent)) != null) {
               if (child != null) {
                   w.print("Caused by: ");
                   child.printStackTrace(w);
                   if (child instanceof ApplicationException) {
                       break;
                   }
                   parent = child;
               }
           }
       }
       public Throwable getCause()  {
           return cause;
       }
}

而"聪明"的读者肯定要问我那doBusiness()这个业务方法该如何包装异常呢?

 public void doBusiness() throw xxxBusinessException
 {
   try
   {
     execute1(); // if it throw exception1

     exexute2(); // if it throw exception 2

     .... .....

   }
   catch (exception1 e1)
   {
    throw new xxxBusinessException(e1);
   }
   catch(exception2 e2)
   {
    throw new xxxBusinessException(e2);
   }
   ........
 }

 也可以这样

 public void doBusiness() throw xxxBusinessException
 {
   try
   {
     execute1(); // if it throw exception1

     exexute2(); // if it throw exception 2

     .... .....

   }
   catch (Exception e)
   {
    // 注意很多应用在这里根本不判断异常的类型而一股脑的采用
    // throw new xxxBusinessException(e);
    // 而这样带来的问题就是xxxBusinessException"吞掉了"RuntimeException
    // 从而将checked excption 与unchecked exception混在了一起!

    // 其实xxxBusinessException属于checked excpetion ,它根本不应该也不能够理睬RuntimeException
    if(! e instanceof RuntimeException) throw new xxxBusinessException(e);
   }
 }


总结
 1。JAVA的异常分为两类: checked exception & unchecked excpetion
 2。应用开发中产生的异常都应该集成自Exception 但都属于checked excpetion类型
 3。应用中的每一层在包装并传递异常的时候要过滤掉RuntimeException!
 4。从责任这个角度看Error属于JVM需要负担的责任;RuntimeException是程序应该负担的责任;checked exception 是具体应用负担的责任
 5。无论如何我们都不希望或者确切的说是不应该将RuntimeException这样的异常暴露给客户的,因为他们没有解决这个问题的责任!

posted @ 2006-03-02 20:50 killvin| 编辑 收藏

Nasted Class 的介绍,请详见参考

今天讨论的不是不是内部类的概念,而是具体使用的一个场景-如何在内部类中返回外部对象

看一段代码

import java.util.LinkedList;
import java.util.List;


public class OuterClass
{

 private List listeners = new LinkedList();

 public void addListeners(IListener listener)
 {
  this.listeners.add(listener);
 }


 private OuterClass outer = this;  (1)
 private class InnterClass
 {
  public void publish()
  {
   //将事件发布出去 (2)
   for(int i=0;i < listeners.size();i++)
   {
    IListener listener = (IListener) listeners.get(i);
    listener.receiveEvent(outer);
   }

  }
 }


 public void execute()
 {
  InnterClass in = new InnterClass(); (3)
  in.publish();

 }
}

public interface IListener
{

public void receiveEvent(OuterClass obj);
}

你可能觉得这个例子很别扭,在哪里让你觉得难受呢?其实问题的关键就在于接口IListener的定义,这里需要给receiveEvent方法传递的参数是外部对象!(别激动,下面我会说明需要传递的一个场景)

场景

在一个GUI系统中,我们要在画板WorkSpace(WorkSpace实现了IListener接口)上产生一颗树,但树中的每个节点的产生(绘图)是我们不知道的算法,系统只为我们提供了一些绘图的接口,并返回元素的句柄!看来我们需要"包装"一下这个绘图的句柄Brush(其实我把它叫做笔刷,因为它只知道如何"刷"出图像来,就这点本事!)并对外提供节点Node这样一个通用的类。

此时Node与Brush的关系就很微妙了,不过我们可以抛开这些外表,看到Node与Brush其实就是外部类与内部类的关系!-第一步完成了:确定了两者的关系

然而,事情没有这么简单,Node类必须处理一些事件,而这些事件理所当然只有Brush能够看懂,而Node根本不知道这样的事件处理过程,现在有两个办法:办法一,让Node实现Brush所有的事件;办法二,把Brush返回回去,让它来处理自己的事件,看来办法二是个好主意,因为我可以不关心事件的种类!-第二步完成了:确定了事件处理的责任

还没完呢,你肯定不希望画板WorkSpace面对的是绘图的句柄Brush这样的对象,相反你只希望WokSpace只知道Node的存在!IListener接口中receiveEvent方法的参数定义为OuterClass 就由此而来!-第三步完成:接口的定义

public interface IListener
{

public void receiveEvent(OuterClass obj);
}

 既然说清楚了这个问题(应该比较清楚了吧?)那改如何实现这样一个蹩脚而有无可奈何的设计呢?让我们回忆一下内部类,内部类拥有访问外部类的方法与属性的权限

 private OuterClass outer = this;  - 这个对外部类的引用就是为内部类的访问准备的

 private class InnterClass
 {
  public void publish()
  {
   //将事件发布出去 

   for(int i=0;i < listeners.size();i++)
   {
    IListener listener = (IListener) listeners.get(i);
    listener.receiveEvent(outer);  - 这里不可以返回this,因为this代表的是内部类自己
   }
  }

 

 

参考

Java Nested class   http://blog.csdn.net/Killvin/archive/2006/01/10/574983.aspx

初识Java内部类    http://blog.csdn.net/killvin/archive/2006/01/10/574991.aspx

posted @ 2006-03-02 20:49 killvin| 编辑 收藏

当你显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的。某些情况下(父子表关联保存),当你在程序中并没有显式的使用save或者update一个持久对象,那么Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象,是一个尚未被持久化过的内存临时对象。例如:

java代码: 

  Session session = ...;
  Transaction tx = ...;
  
  Parent parent = (Parent) session.load(Parent.class, id);
  
  Child child = new Child();
  child.setParent(parent);
  child.setName("sun");
  
 10 parent.addChild(child);
 11 s.update(parent);
 12 
 13 s.flush();
 14 tx.commit();
 15 s.close();



在上例中,程序并没有显式的session.save(child); 那么Hibernate需要知道child究竟是一个临时对象,还是已经在数据库中有的持久对象。如果child是一个新创建的临时对象(本例中就是这种情况),那么Hibernate应该自动产生session.save(child)这样的操作,如果child是已经在数据库中有的持久对象,那么Hibernate应该自动产生session.update(child)这样的操作。

因此我们需要暗示一下Hibernate,究竟child对象应该对它自动save还是update。在上例中,显然我们应该暗示Hibernate对child自动save,而不是自动update。那么Hibernate如何判断究竟对child是save还是update呢?它会取一下child的主键属性 child.getId() ,这里假设id是 java.lang.Integer类型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate认为child是新的内存临时对象,发送save,如果不相等,那么Hibernate认为child是已经持久过的对象,发送update。

unsaved-value="null" (默认情况,适用于大多数对象类型主键 Integer/Long/String/...)

当Hibernate取一下child的Id,取出来的是null(在上例中肯定取出来的是null),和unsaved-value设定值相等,发送save(child)

当Hibernate取一下child的id,取出来的不是null,那么和unsaved-value设定值不相等,发送update(child)

例如下面的情况:

java代码: 

  Session session = ...;
  Transaction tx = ...;
  
  Parent parent = (Parent) session.load(Parent.class, id);
  Child child = (Child) session.load(Child.class, childId);
  
  child.setParent(parent);
  child.setName("sun");
  
 10 parent.addChild(child);
 11 s.update(parent);
 12 
 13 s.flush();
 14 tx.commit();
 15 s.close();



child已经在数据库中有了,是一个持久化的对象,不是新创建的,因此我们希望Hibernate发送update(child),在该例中,Hibernate取一下child.getId(),和unsave-value指定的null比对一下,发现不相等,那么发送update(child)。

BTW: parent对象不需要操心,因为程序显式的对parent有load操作和update的操作,不需要Hibernate自己来判断究竟是save还是update了。我们要注意的只是child对象的操作。另外unsaved-value是定义在Child类的主键属性中的。

java代码: 

 1 <class name="Child" table="child">
 2 <id column="id" name="id" type="integer" unsaved-value="null">
 3   <generator class="identity"/>
 4 </id>
 5 ...
 6 </class>



如果主键属性不是对象型,而是基本类型,如int/long/double/...,那么你需要指定一个数值型的unsaved-value,例如:

java代码: 

 1 unsaved-null="0"



在此提醒大家,很多人以为对主键属性定义为int/long,比定义为Integer/Long运行效率来得高,认为基本类型不需要进行对象的封装和解构操作,因此喜欢把主键定义为int/long的。但实际上,Hibernate内部总是把主键转换为对象型进行操作的,就算你定义为int/long型的,Hibernate内部也要进行一次对象构造操作,返回给你的时候,还要进行解构操作,效率可能反而低也说不定。因此大家一定要扭转一个观点,在Hibernate中,主键属性定义为基本类型,并不能够比定义为对象型效率来的高,而且也多了很多麻烦,因此建议大家使用对象型的Integer/Long定义主键。

unsaved-value="none"和
unsaved-value="any"

主主要用在主键属性不是通过Hibernate生成,而是程序自己setId()的时候。

在这里多说一句,强烈建议使用Hibernate的id generator,或者你可以自己扩展Hibernate的id generator,特别注意不要使用有实际含义的字段当做主键来用!例如用户类User,很多人喜欢用用户登陆名称做为主键,这是一个很不好的习惯,当用户类和其他实体类有关联关系的时候,万一你需要修改用户登陆名称,一改就需要改好几张表中的数据。偶合性太高,而如果你使用无业务意义的id generator,那么修改用户名称,就只修改user表就行了。

由这个问题引申出来,如果你严格按照这个原则来设计数据库,那么你基本上是用不到手工来setId()的,你用Hibernate的id generator就OK了。因此你也不需要了解当

unsaved-value="none"和
unsaved-value="any"

究竟有什么含义了。如果你非要用assigned不可,那么继续解释一下:

unsaved-value="none" 的时候,由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对child对象发送update(child)

unsaved-value="any" 的时候,由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对child对象发送save(child)

大多数情况下,你可以避免使用assigned,只有当你使用复合主键的时候不得不手工setId(),这时候需要你自己考虑究竟怎么设置unsaved-value了,根据你自己的需要来定。

BTW: Gavin King强烈不建议使用composite-id,强烈建议使用UserType。

因此,如果你在系统设计的时候,遵循如下原则:

1、使用Hibernate的id generator来生成无业务意义的主键,不使用有业务含义的字段做主键,不使用assigned。

2、使用对象类型(String/Integer/Long/...)来做主键,而不使用基础类型(int/long/...)做主键

3、不使用composite-id来处理复合主键的情况,而使用UserType来处理该种情况。


那么你永远用的是unsaved-value="null" ,不可能用到any/none/..了。

posted @ 2006-03-02 20:45 killvin| 编辑 收藏

先来点概念:

在Hibernate中,最核心的概念就是对PO的状态管理。一个PO有三种状态:

1、未被持久化的VO
此时就是一个内存对象VO,由JVM管理生命周期

2、已被持久化的PO,并且在Session生命周期内
此时映射数据库数据,由数据库管理生命周期

3、曾被持久化过,但现在和Session已经detached了,以VO的身份在运行
这种和Session已经detached的PO还能够进入另一个Session,继续进行PO状态管理,此时它就成为PO的第二种状态了。这种PO实际上是跨了Session进行了状态维护的。

在传统的JDO1.x中,PO只有前面两种状态,一个PO一旦脱离PM,就丧失了状态了,不再和数据库数据关联,成为一个纯粹的内存VO,它即使进入一个新的PM,也不能恢复它的状态了。

Hibernate强的地方就在于,一个PO脱离Session之后,还能保持状态,再进入一个新的Session之后,就恢复状态管理的能力,但此时状态管理需要使用session.update或者session.saveOrUpdate,这就是Hibernate Reference中提到的“requires a slightly different programming model ”

现在正式进入本话题:

简单的来说,update和saveOrUpdate是用来对跨Session的PO进行状态管理的。

假设你的PO不需要跨Session的话,那么就不需要用到,例如你打开一个Session,对PO进行操作,然后关闭,之后这个PO你也不会再用到了,那么就不需要用update。

因此,我们来看看上例:
java代码: 

 1 Foo foo=sess.load(Foo.class,id);
 2 foo.setXXX(xxx);
 3 sess.flush();
 4 sess.commit();



PO对象foo的操作都在一个Session生命周期内完成,因此不需要显式的进行sess.update(foo)这样的操作。Hibernate会自动监测到foo对象已经被修改过,因此就向数据库发送一个update的sql。当然如果你非要加上sess.update(foo)也不会错,只不过这样做没有任何必要。

而跨Session的意思就是说这个PO对象在Session关闭之后,你还把它当做一个VO来用,后来你在Session外面又修改了它的属性,然后你又想打开一个Session,把VO的属性修改保存到数据库里面,那么你就需要用update了。

java代码: 

  // in the first session
  Cat cat = (Cat) firstSession.load(Cat.class, catId);
  Cat potentialMate = new Cat();
  firstSession.save(potentialMate);
  
  // in a higher tier of the application
  cat.setMate(potentialMate);
  
  // later, in a new session
 10 secondSession.update(cat)// update cat
 11 secondSession.update(mate); // update mate
 12 



cat和mate对象是在第一个session中取得的,在第一个session关闭之后,他们就成了PO的第三种状态,和Session已经detached的PO,此时他们的状态信息仍然被保留下来了。当他们进入第二个session之后,立刻就可以进行状态的更新。但是由于对cat的修改操作:cat.setMate(potentialMate); 是在Session外面进行的,Hibernate不可能知道cat对象已经被改过了,第二个Session并不知道这种修改,因此一定要显式的调用secondSession.update(cat); 通知Hibernate,cat对象已经修改了,你必须发送update的sql了。

所以update的作用就在于此,它只会被用于当一个PO对象跨Session进行状态同步的时候才需要写。而一个PO对象当它不需要跨Session进行状态管理的时候,是不需要写update的。

再谈谈saveOrUpdate的用场:

saveOrUpdate和update的区别就在于在跨Session的PO状态管理中,Hibernate对PO采取何种策略。

例如当你写一个DAOImpl的时候,让cat对象增加一个mate,如下定义:
java代码: 

1 public void addMate(Cat cat, Mate mate) {
 2     Session session = ...;
 3     Transacton tx = ...;
 4     session.update(cat);
 5     cat.addMate(mate);
 6     tx.commit();
 7     session.close();
 8 };



显然你是需要把Hibernate的操作封装在DAO里面的,让业务层的程序员和Web层的程序员不需要了解Hibernate,直接对DAO进行调用。

此时问题就来了:上面的代码运行正确有一个必要的前提,那就是方法调用参数cat对象必须是一个已经被持久化过的PO,也就是来说,它应该首先从数据库查询出来,然后才能这样用。但是业务层的程序员显然不知道这种内部的玄妙,如果他的业务是现在增加一个cat,然后再增加它的mate,他显然会这样调用,new一个cat对象出来,然后就addMate:

java代码: 

 1 Cat cat = new Cat();
 2 cat.setXXX();
 3 daoimpl.addMate(cat,mate);



但是请注意看,这个cat对象只是一个VO,它没有被持久化过,它还不是PO,它没有资格调用addMate方法,因此调用addMate方法不会真正往数据库里面发送update的sql,这个cat对象必须先被save到数据库,在真正成为一个PO之后,才具备addMate的资格。

你必须这样来操作:

java代码: 

 1 Cat cat = new Cat();
 2 cat.setXXX();
 3 daoimpl.addCat(cat);
 4 daoimpl.addMate(cat, mate);



先持久化cat,然后才能对cat进行其他的持久化操作。因此要求业务层的程序员必须清楚cat对象处于何种状态,到底是第一种,还是第三种。如果是第一种,就要先save,再addMate;如果是第三种,就直接addMate。

但是最致命的是,如果整个软件分层很多,业务层的程序员他拿到这个cat对象也可能是上层Web应用层传递过来的cat,他自己也不知道这个cat究竟是VO,没有被持久化过,还是已经被持久化过,那么他根本就没有办法写程序了。

所以这样的DAOImpl显然是有问题的,它会对业务层的程序员造成很多编程上的陷阱,业务层的程序员必须深刻的了解他调用的每个DAO对PO对象进行了何种状态管理,必须深刻的了解他的PO对象在任何时候处于什么确切的状态,才能保证编程的正确性,显然这是做不到的,但是有了saveOrUpdate,这些问题就迎刃而解了。

现在你需要修改addMate方法:

java代码: 

1 public void addMate(Cat cat, Mate mate) {
 2     Session session = ...;
 3     Transacton tx = ...;
 4     session.saveOrUpdate(cat);
 5     cat.addMate(mate);
 6     tx.commit();
 7     session.close();
 8 };



如上,如果业务层的程序员传进来的是一个已经持久化过的PO对象,那么Hibernate会更新cat对象(假设业务层的程序员在Session外面修改过cat的属性),如果传进来的是一个新new出来的对象,那么向数据库save这个PO对象。

BTW: Hibernate此时究竟采取更新cat对象,还是save cat对象,取决于unsave-value的设定。

这样,业务层的程序员就不必再操心PO的状态问题了,对于他们来说,不管cat是new出来的对象,只是一个VO也好;还是从数据库查询出来的的PO对象也好,全部都是直接addMate就OK了:

posted @ 2006-03-02 20:44 killvin| 编辑 收藏

今天同事问我Struts如何解决文件上传的问题,一时间没有想起来,就到google查了一下,果然在Struts Wiki上就有非常详细的解释,抄录如下,详细的请看(http://wiki.apache.org/struts/StrutsFileUpload)



StrutsFileUpload
File Upload - Simple Example
HTML
This isn't specific to Struts, but gives a simple example of the HTML required to upload a single file.

Two things are needed in the html page. Firstly, the form needs to specify an enctype of multipart/form-data and secondly an form control of type file.

JSP
The above HTML can be generated using the Struts tags in the following way

<html:form action="/uploadMyFile.do" enctype="multipart/form-data">

Select File: <html:file property="myFile">

<html:submit value="Upload File"/>

</html:form>

ActionForm
The ActionForm needs a property of type FormFile.

Regular ActionForms
import org.apache.struts.upload.FormFile;

public class MyActionForm extends ActionForm {

private FormFile myFile;

public void setMyFile(FormFile myFile) {
this.myFile = myfile;
}

public FormFile getMyFile() {
return myFile;
}
}

Dyna ActionForms
In the struts-config.xml

<form-bean name="myForm" type="org.apache.struts.action.DynaActionForm">
<form-property name="myFile" type="org.apache.struts.upload.FormFile"/>
</form-bean>

Whats Needed in the Action
Nothing special really, just retrieve the FormFile from the ActionForm, as you would any other property, and process it as you like. You can get the file name, size and file contents from the FormFile.

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {

MyActionForm myForm = (MyActionForm)form;

// Process the FormFile
FormFile myFile = myForm.getMyFile();
String contentType = myFile.getContentType();
String fileName = myFile.getFileName();
int fileSize = myFile.getFileSize();
byte[] fileData = myFile.getFileData();
...
}

File Upload Configuration
The following parameters can be set in the <controller> element of the struts-config.xml to configure file upload:

bufferSize - The size (in bytes) of the input buffer used when processing file uploads. Default is 4096.

maxFileSize - The maximum size (in bytes) of a file to be accepted as a file upload. Can be expressed as a number followed by a "K", "M", or "G", which are interpreted to mean kilobytes, megabytes, or gigabytes, respectively. Default is 250M.

multipartClass - The fully qualified Java class name of the multipart request handler class to be used with this module. Defaults is org.apache.struts.upload.CommonsMultipartRequestHandler.

tempDir - Temporary working directory to use when processing file uploads.

Above taken from the Configuration section in the User Guide.

Plugging in an Alternative File Upload Mechanism
By default Struts uses Commons File Upload.

Alternative implementations can be plugged as long as they implement the org.apache.struts.upload.MultipartRequestHandler interface and Struts configured to use that implementation by specifying it in the multipartClass parameter in the <controller> element of the struts-config.xml

Fair Warning: The MultipartRequestHandler interface is almost certain to change in a Struts 1.3 or higher release.

posted @ 2006-03-02 20:43 killvin| 编辑 收藏

        以前总是认为php只是一种jsp的翻版,没有啥意思!不象J2EE的体系结构那样,有应用服务器的概念,只是一个简单的服务器脚本,而昨天的看法却让我大跌眼镜,其实php和java、python都处在一个层次上,甚至比java更有优势!不应该仅仅只是将php和jsp看作是同样的档次,这就好比是告诉写python的程序员,你们开发不了应用程序一样的让别人笑掉大牙,甚至怀疑你的脑子有毛病,zope就是一个很好的例子!!优秀的应用服务器,就是用Python开发的!
        
        我们更应该将php看作是一个语言,和python同样优秀(当然php和python的定位不同,也就是因为这样的定位,而让人产生的错觉!!)如果Php将自己定位如python一样!并在语言这个级别更贴近面向对象的思维模式,那Python也许就不会如此的火爆!
        
        看来php之父并没有将语言向python靠拢的意思,也就是说这样的语言目前的生存空间仅仅只限于服务器端的脚本!这是它的领地!但除非是它闭着眼镜,不然没有人会愿意看到html的代码里写这<?php>这样的代码!这就好比是在没有webwork 、struts这样优秀的web框架出现之前,jsp让人恶心的原因,如果Php的领域没有这样的MVC框架出现,那我情愿还是选择jsp!!出于学习我会选择Python,但永远不会选择php......
        
        告诉我你知道的php世界里优秀的web框架!也许我会改变看法!但我不会选择重新发明轮子!!

 

posted @ 2006-03-02 20:26 killvin| 编辑 收藏

Thinking in Python

You can download the current version of Thinking in Python here. This includes the BackTalk comment collection system that I built in Zope.

The page describing this project is here.

The current version of the book is 0.1. This is a preliminary release; please note that not all the chapters in the book have been translated.

The source code is in the download package. When you unzip everything (remember to use the -a flag if you're on Unix), the code will be put into subdirectories for each chapter.

This is not an introductory Python book. This book assumes you've learned the basics of Python elsewhere. I personally like Learning Python by Lutz & Ascher, from O'Reilly. Although it does not cover all the Python 2.0, 2.1 and 2.2 features that I use in this book, most books don't at this point.

However, Learning Python is not exactly a beginning programmer's book, either (although it's possible if you're dedicated). If you're just getting started with programming you might try the free, downloadable A Byte of Python.

posted @ 2006-03-02 20:23 killvin| 编辑 收藏

Python是一种不但成熟而且功能也十分强大的面向对象程序设计语言。它的功能性现在已经可以与Java相媲美了。尽管Python有着这样或者那样的优点,但是它仍然面对着这样的一种现实:真正了解Python的人给予了它很高的评价,但是实际上真正了解它的人并不多。在本篇文章里,我将会向您介绍一系列的Python编程资源,这样您就可以对这种编程语言有更多的了解,并且能够逐步的开始使用这个优秀的编程语言了。

开始的第一个步骤

如果您是刚刚开始了解Python编程语言,你现在就需要一套Python编程软件,这样能够让你更好的掌握它。正式的Python软件中包括了程序指令行,图形化的用户界面,范例程序代码以及一整套的相关文件。还有另外一个版本的Python软件,它的正式名称是ActivePython。这个叫做ActivePython的版本是由ActiveState公司提供的。ActiveState是生产Python软件工具以及相关软件的一家公司。但是,这两种不同的版本区别何在呢?

ActivePython版本对于Windows,Linux, 以及Solaris操作系统的二进制形式是可以直接使用的。但是常规的Python软件虽然比ActivePython支持的操作系统要稍微多一些,但是却只能够作为这些操作系统的源代码来进行下载。ActivePython版本比常规的Python软件增加了一些内容,并且附带有更大的标准资源库。如果对于Python软件来说您仅仅还处于入门阶段的话,我推荐您应该跳过对ActivePython的学习。

当你安装了Python软件之后,你会希望掌握它的编程方法,除非你只是喜欢单纯的在你的桌面上增加一个很少使用的可爱快捷方式而已。在国际互联网上已经有了不少关于Python编程语言的使用指南,请看下面的这些内容:

  • 正式的Python文件中包含了范围很大的使用指南,并且对您来说,可能是首选的应该浏览的网址。
  • Python即时通是一个“Python编程语言的最快的速成班”,这个网址让学习者通过在一些页面上对一些程序代码范例进行结构,输入,输出,分类以及变量的控制来熟悉这种编程语言。
  • 投身于Python编程语言的世界是一个综合性的Python软件使用指南。它是以电子书的形式来与大家见面的。这款电子书是为了那些有经验的使用Perl, Java,或者 Visual Basic编程语言的软件设计人员转向使用Python而编写的,所以它是很有针对性的。
  • Python编程语言的全方位接触是一篇在UnixWorld上登出的时间已经比较久的有关Python编程语言的文章。这篇文章向读者们讲述了如何使用Python编程语言来完成一些比较简单的小任务,比如说编制一个善意的电子邮件玩笑软件来让你的朋友们吓一跳或者大吃一惊。
有关Python编程语言总的说明以及资源

如果您已经开始接触Python编程语言并且已经有了一定的使用Python进行程序设计的经验之后,您可能会在这个过程中产生一些问题,或者您会希望得到有关Python编程语言更多的相关资料。为了满足您这方面的需要,我会向您提供一些网络上的资源作为您的文献资料库。下面列出的这些条都是其中比较好的一些网络资源:

  • Parnassus山的拱顶是一个巨大的Python程序代码库,其中包含了成千上万可以使用的Python程序模块。这些程序模块中有程序设计,图形化处理方法以及国际互联网程序设计数据库。在这个站点上还有一些已经完成的应用软件,并且可以作为程序源代码让您下载下来。
  • Python号星际旅行船是Digital Creations公司制作的一个有关Python编程语言的免费网络社区。其中包含了总的数据信息资料库,并且还有成员或者“其星际旅行船宇航员”目录,在这里能够和一些Python编程高手进行交流。
  • O’Reily网络的网络站点ONLamp.com中拥有关于Python程序开发的部分,在这个部分中,学习者能够找到有关的一些新闻,论坛,问题解答留言板以及许多相关的文章。
  • Faqts.com中有一个Python程序设计知识数据库,其中包含了可以随意查询的有关Python程序设计的问题以及解决方法。这些资料都是使用过Python语言的程序设计人员提供的。
  • Python-URL!是Dr.Dobb周刊的电子邮件摘要,其中包含了与Python程序设计语言相关的新闻,公告,以及国际互联网新闻组活动。
  • 有不少针对Python编程语言的电子邮件讨论列表。如果您不能在国际互联网上找到您所需要的答案,您可以通过这种方法从远方获取正确的解决方法。
一些植入Python软件的有用资源

Python编程语言拥有极好的扩充性,并且是一种很好的脚本语言。Python作为编程语言能够非常好的植入大量的应用软件。这是近来有关Python编程语言活动的中心内容,我们能够从theKompany中的VeePee了解到更多的信息。VeePee是一种使用图形用户界面的脚本环境,这是被设计用来植入Linux GNOME或者 KDE应用软件,并且能够对这两种应用软件提供脚本支持。我要一再提示您的是,ActiveState对于在.NET中使用Python编程语言拥有经过实验室检验的具体经验。这样就允许Python编程语言能够继承并且访问在.NET框架中的对象,并且为其它的.NET编程语言提供了一种脚本支持。

IDE(集成开发环境)以及编辑器

就像大家知道的那样,我是一个沉迷于IDE的人:当我编制程序的时候,如果有code completion,弹出式对话框,菜单以及工具条,这样我才会感觉舒服一些。我清除的知道在这个世界上还有许许多多像我一样的人。很幸运的一点是,对我们这些人来说,虽然我们沉迷于此,但是这个世界上有数量繁多的非常优秀的Python软件的IDE能够供我们使用,虽然其中的相当大一部分并不是免费的:

  • PythonWorks软件作品是一个具有商业品质的IDE。它具有一个Tkinter的图形化用户界面,程序代码仓库,程序调试器,“聪明的”编辑器以及在线帮助功能。并且它是同时支持windows,Linux,以及Solaris这三种操作系统的。其零售价格为395美元。其中还包括免费的评估服务。
  • Wing IDE是一个用户化的IDE,它能够支持宏,code completion,多面手程序调试器以及源文件管理工具。Wing对于Linux 以及Windows操作系统的零售价格大约为100美元,但是对于使用公开化源程序来说是免费提供的。现在,免费版本已经可以从多种渠道获得了。
  • PythonWin是一个免费的只能在Windows操作系统中使用的图形化用户界面创建工具。这个工具向程序设计人员提供了一个界面资料库,这个资料库只是针对微软公司的系列产品的。
  • 从我们在ActiveState公司中的朋友(你能证明他们百分之一百的热爱Python语言么?),我们能够获得免费的,经过实验室验证的插件IDE程序。这些程序都是为Visual Studio.NET特别设计的,并且已经被命名为Visual Python。请不要把Visual Python和theKompany的VeePee软件混为一谈,它们是两个完全不同的产品。

在本文中提到的这些资源能够帮助你正式的开始使用Python语言进行程序设计的工作,并且还能够帮助你找到获取这些资源的途径。你只要记住的一点是:没有人喜欢Spanish Inquisitionn(西班牙宗教法庭,在1480-1834年之间存在的天主教法庭,以残酷迫害异端著称)。你不会真的期望只是通过一篇文章就能够获取所有有关Python编程语言的知识吧?这种愿望是非常不切合实际的。

posted @ 2006-03-02 20:22 killvin| 编辑 收藏

PHP5的面向对象特性增强了很多,包括功能上和性能上。但是如果用一门面向对象语言来说,PHP还差很远,简单的举个例子,Java和Python都可以通过import来导入类,但是PHP只能用include这种原始的办法,因而没有办法区分命名空间,做大项目还是不行的。 此外PHP的致命缺陷是它只是Web Server的一个module存在的,没有App Server来进行复杂业务的管理。因此没有办法承担大型项目的开发和运行。像国内的eachnet原来是用PHP的,交易量和复杂度上升以后,后台全部用BEA的Tuexdo来做,PHP只是调用。(不过也有人认为,没有App Server也是一种优势,就是部署简单) Python和PHP的本质区别就在于Python有Zope这样的App Server,因此Python应该划入Java/C#的阵营,而和PHP要划清界限了。

posted @ 2006-03-02 20:20 killvin| 编辑 收藏

接触 Python 是两年前的事了。那时因为要写个程式来分析数值资料,开始的时候打算用自己最熟的 PHP,试了几下,很不顺手。后来转用了 Perl,开始的时候都挺顺利的。直至有一天整理程式,想把写好的几个东西整理成一个工具箱给自己用,试了半天就放弃了:根本是精神虐待。反省后觉得自己学 Perl 只会走捷径,打算上网找些基本的学习文件从头努力,但竟然找到了 Python 的网页。就这样开始了学 Python,而且是欲罢不能。现在我要写什么,基本上都是先考虑用 Python,一来实在好用,二来用 Python 写的程式总会有重用的机会,所以手头的工具越来越多,用起来就更得心应手了。

如果你以前只会用 Shell Script 或是 DOS Batch File 而想学些其他的东西,Python 会是你的首选,如果你己经会 Perl 或是 Tcl 或是别的什么,那 Python 会令你改变对 scripting language 的想法。

为什么要学 Python?

答:好玩。就是这样简单。无论是学 Python 还是用 Python,都是一件赏心乐事。当你写 shell scripts 写得鬼哭神号时,当你改 perl scripts 改得头昏脑涨时,有没有想过写 script 其实是很好玩的一件事。你可能从来不相信当你写一套严肃而功能强大的程式,你可以用一个如此好玩的程式语言来开发。更难令你相信的是,就算在程式开发的后期,甚至是几个月以后当你再次想修改它的时候,你依然都会觉得是一件好玩的事。

Python 能做什么?

答:倒不如问,Python 不能做什么? Python 已经差不多把一个 scripting language 作为整合工具 (gluing tool) 的强大功能完全发挥了出来,任何程式,只要有个给 Python 用的接口,Python 就可以把它据为己用,和其他的程式一块使用。而且

    · Python 有发展 XML 的大型函式库,并开始有大型系统的构建初型。 · 用 Python 写的 Zope,可以用来架设整个网站 · Python 语言自己是用 C 来写的,但现已有用纯 Java 写的 Python 语言实现,近期更有用微软 .NET 方式实现的 Python 翻译器,由此证明了 Python 语言定义的高度完整。 · Python 已移植到许多的操作平台上,包括 Linux、Windows、HP、Solaris、Irix、AIX、BeOS、甚至是 Cray。几乎和 Perl 一样多。 ·由于 Python 本身是 object oriented,所以很容易和 C++,Java 等整合,尤其在 Visual 当道的 Windows 系统。这点要比 Perl 好许多。

和其他语言的比较

一般的说法的这样的:

  • Shell script 太旧了,无论如何不能用来写程式。
  • Perl 擅长做服务器的管理,因为它的字串处理功能特别强大,同时有全面的编程接口和操作系统相连。自己用没问题,就是不懂人家在写什么。
  • PHP 擅长于网页,因为它可以嵌入在 HTML 文件中并支援各类资料库。但一离开网页就左支右绌。
  • Python 特别适合一般的需求,因为它语法简明,容易维护,有很多模组可用,而且容易做出图形介面。但它并不是 object oriented 并不纯,速度也较 Perl 慢一些。
  • Tcl/Tk 基本变数形态只有文字串,每次算术都是从文字变数字再变文字,鸣,没什么好提的了吧。
  • Java 太伟大了,我可不想每次开始写程式时都幻想要完成一个跨企业的解决方案,引入几十个标头档,写完了才明白原来我只需它读一个用户指定的文件,画两条直线就行了。唉,落差太大。
  • Ruby 太新了,要正式应用是将来的事。

但事实上我个人的感受最深的是它比较好玩的说法。虽然像是不认真,但一种可以让人寓编程于娱乐的程式语言,正是程式语言的设计终极嘛。

Python 的作者是数学家,而 Perl 的作者则是个天才程式员。因此这两种语言的风格回异:Python 语法铺排的很整洁,相同功能的程式由不同人写出来的样子都很像,这些一致的、前后呼应的程式对于系统的拓展和整合十分重要。因此阅读 Python 的程式感觉上很畅顺。Perl 的语法则很浓缩,而且经常会有些意想不到的巧思,可惜这些每每体现在字里行间的妙着对整个程式的可读性带来负面影响。因而一个短小的 Perl 程式往往令人爱不释手,但当它越来越长时总会变成程式员的永痛。

至于PHP,因它的发展历史一直是围绕着 web page,所以做网页设计是十分方便的,但是写一般的工作则平平无奇。比如说控制执行绪,处理进程间的讯息传送以致做图形用户介面等都久奉,这是因为从 WEB 的角度,这些并不重要:图形介面,HTML 就己经是了,而程式的执行和互相合作则是 Web Server 要考虑的事。

我推荐 Python 是因为我觉得它比较适合用作一般性程式编写和系统整合,尤其是在科学和教育领域中。我并不是说大家没本事学好 Perl,只是很多时候我们并不需要用到 Perl 的精妙之处,更多的时候用一个清楚直接的描述要比用一个智力题级的招数更有好处。除了语法的设计较为整洁外,Python 还有很好的对电脑图形的支援,也有十分快的数学阵列模组。

而它最方便的地方是很容易的把其他的程式整合在一起,譬如说你有个几年前写的 C 程式,只要修改很小的一部份就可以编译成 Python 可用的模组。事实上已经有自动帮你做这些修改的程式了,人们也己为许多各种好用的函式库做出了 Python binding,如 OpenGL、GTK、QT、GD、甚至于解方程组的、找频谱的都有。

而在使用了一段时间后,我开始体会下面的说法:

    ‘Python 是设计出来的,而 PHP 和 Perl 是累积而来的。’

虽然它们各自都有类似的功能,但在使用时会发现 Python 的写法总是较为一致,这是因为 Python 从一开始便考虑了它整个的架构。相反,像 Perl 或 PHP 则有很多功能都是后来慢慢加上去的,所以到后期 Perl 的发展越来越难,以致 Perl 6 几乎要从头设计。而 PHP 则现在才开始头痛于构思新的函数名称 (所有的 PHP 函数都是全域定义的,所以每加一个函数都有可能和已经定义了的名字重复)。为了解决问题,它们各自都开始注意加强 class, object 的支援。但很明显的是,在程式成熟后再加这些基本功能会令整个语言的框架变得头重脚轻,无论是写起来还是用起来都有些不稳的感觉。

然而 Python 在一开始的时候就把这些考虑了进去:Python 根本就是以物件导向作为设计的基础。因此在加了许许多多的功能和模组后,它的风采仍旧,清新依然。

好话说了不少,光说不练可是不行。参考程式示范区的实际例子,看看 Python 到底是怎样一回事。

posted @ 2006-03-02 20:17 killvin| 编辑 收藏

javascript的调试相当繁琐,alert不能有效地起到调试作用,原因在于:
1, alert只能在开发期使用,不能在产品交付中继续alert debug info
2, 就算在开发期,频繁的alert会导致调试效率低下。
一个简单的log就会有很大帮助。下面就是我在现实生活中使用的javascript log方法。
把它包含在一个全局import的js里面,以后代码就可以采用log()的方式来进行了。

当然,若谁有时间改造为log4j那样的可以按照分类打开或关闭log,再分上info,error什么的log级别就更好了。不过下面的简单方法就够我用拉。

common.js

function log(v)
{
     var s = "<li><font color=blue>"+new Date()+"</font> "+v+"</li>";
     logPanel_log.innerHTML+=s;
}

window.onhelp = function()
{
     logPanel.style.display=logPanel.style.display=='none'?"block":"none";
     return false;
}

document.write(
 "<div id=logPanel style='display:none;text-align:right'>"+
    "<button onclick=\"logPanel_log.innerHTML='';\">clear</button>&nbsp;"+
    "<button onclick=\"logPanel_log.innerHTML+='<hr>';\">HR</button>"+
    "<div id='logPanel_log'></div></div>");

它覆盖了默认的IE 按F1键显示IE help的行为,现在按下F1就能够显示log信息了。

摘自 redsaga满江红 http://www.blogcn.com/User6/caoxg/blog/5705836.html

posted @ 2006-03-02 20:15 killvin| 编辑 收藏

JavaScript說分明 

http://taiwan.cnet.com/enterprise/technology/0,2000062852,20103386,00.htm
朱仲傑撰  2005/12/28

 

最近因為AJAX受到Google等網路服務業者的愛用而聲名大噪。也讓這個其實存在很久的程式語言再度成為討論話題。


JavaScript可以說是全世界使用最普遍的一個語言。雖然就統計資料(註)上來看,用JavaScript為主的專案數量只排在第九名,但我相信就應用的層面來看,絕對遠超過Java與.Net。JavaScript也是最讓人混淆的一個語言,即便是專業的網頁工程師,也不見得每位都能正確的了解它。網頁工程師對它又愛又恨,使用者最能體驗到它所帶來的好處,但常常也是被它搞的昏頭轉向。到底它是個什麼樣神奇的語言呢?

JavaScript的前世今生

提到JavaScript,大家第一個聯想到的就是Java,但一切的誤解也就從這開始。要說它跟Java完全沒關係嘛,也不盡然。JavaScript原來的名稱叫LiveScript,是Netscape(網景)為了它的LiveWare平台所發展出來的一套語言。後來之所以改名叫JavaScript,主要是因為當年Netscape與Sun合作,共同開發網頁上的腳本語言,而剛好Sun正在開發Java語言,初期而且也是以網頁上的應用Java Applet為主,所以LiveScript也就改名為JavaScript。第一個擁有執行JavaScript的瀏覽器Netscape 2.0,發表日期也跟Java一樣,同樣都是1995年,所以算起來今年也是JavaScript的十歲生日。

JavaScript的版本非常多種,最原始的是由Netscape一路發展過來的,後來NetScape把它交給ECMA製定為標準(ECMA-262),而ECMA所製定出來的稱之為ECMAScript;最喜歡自己也搞一套的微軟,也有自訂的Jscript。理論上,有了標準大家就應該要遵守,不過到目前看來,實際上並非如此。各家瀏覽器相容的程度不一,造成現在混亂的局面,同一個網頁用不同的瀏覽器瀏覽有時結果不盡相同。

JavaScript沒有你想像中的簡單

JavaScript是屬於prototype-based的一種語言,而prototype-based又是物件導向程式設計語言(Object-Oriented Programming Language)的一個分枝,所以嚴格說起來,JavaScript也是一種物件導向程式設計語言。光看到「物件導向」這四個字,我想很多人頭皮都已經開始發麻了,不過JavaScript之所以不簡單並不是因為這一點,而是因為它的結構鬆散和型態鬆散(loose typing)所導致。

你若寫過Java程式的話或許就知道,Java程式必需要Java虛擬機器(JVM)上執行,在執行Java程式之前,它必需是被編譯過的;在編譯的過程中,Java會檢查許許多多的條件,例如最基本的語法、變數的宣告等等。

JavaScript一樣得依存在一個容器(Container)中才能執行,我們常見到的容器就是瀏覽器(Web Browser),跟Java不同的是,JavaScript的程式不需要事先編譯,它是屬於直譯式的語言,你可以把它想像成看到一行程式碼就執行一行。因為少了編譯時期的檢查,JavaScript就比Java「自由」許多,你可以在網頁上隨意的位置上安插JavaScript程式、可以隨意的建立新的變數來使用;你不用分清楚什麼是數值,什麼是字串、什麼又是物件,通通可以加加(+)在一起,這就會造成相當不嚴謹的開發過程。

自由是必需要付出代價的,前面提到相容性的混亂,在這又有語法結構上的混亂,這下子可以說是亂上加亂了。網頁工程師之所以對JavaScript又愛又恨,愛是因為透過適當的JavaScript,可以在網頁上創造出許許多多特殊的效果,讓使用者在瀏覽這個網頁時,能有更多的互動。恨是因為市場上極少有以JavaScript為主的整合開發工具(IDE),因為剛剛所說的混亂,造成JavaScript程式很難開發和除錯。

JavaScript之所以普及,是因為寫JavaScript程式的使用者,不見得是寫Java、C/C++、VB等受過基本程式設計訓練的程式設計師,往往網頁的美工人員才是使用JavaScript的大宗。他們不會也沒時間去學習完整的JavaScript,而是有需要時才在網頁上東加一些、西加一點,這些加上去的JavaScript可能是網頁編輯器所提供的元件、或是網路上分享出來的程式片段。JavaScript一點都不簡單,只是大家都很偷懶,用最簡單的方式來使用它罷了。

AJAX應用為JavaScript注入強心劑

JavaScript十年來沒有特別受歡迎,因為WWW盛行以來它和HTML一樣已變成網頁裡最基本的元素,直到AJAX的出現,讓JavaScript大大的受到眾人的注視。AJAX的全名是 Asynchronous JavaScript and XML,JavaScript就是其中之一的主角,AJAX並不是一項新的技術,而是將一些舊有的技術結合起來做一種巧妙的應用,賦予網頁的一股新動力。

AJAX這個名詞最早是出現在Adaptive Path網站上,一篇由 Jesse James Garrett於2005年2月18號所寫的文章中所提出的(http://www.adaptivepath.com/publications/essays/archives/000385.php)。AJAX是客戶端(瀏覽器)的技術,簡單的說,就是用JavaScript與後端伺服器做資料的溝通,資料採用XML的格式,JavaScript動態地從後端拿到XML格式的資料,重新整理、重組後再顯示到網頁上。AJAX基本上是無關Java或.Net的,甚至PHP也可以跟AJAX結合在一起使用。網路上、雜誌裡介紹AJAX的文章相當的多,在這我就不贅述。在這要提醒大家注意的是,因為AJAX,JavaScript又開始大量的應用在網頁裡,前面所提及的混亂又被突顯了出來。混亂不一定只是負面的,所謂時勢造英雄,我們期待有人能出來一統江湖。

JavaScript的其它應用

JavaScript只能夠運用在網頁上嗎?其實蘋果電腦(Apple)除了硬體上的創意外,在作業系統上的表現一直以來也是使用者的最愛。在新的Mac OS X 10.4裡,有個新的功能叫-Dashboard,它可以讓你執行一些小程式,例如月曆、天氣預報、地圖查找等。這些小程式稱為Widget,而撰寫Widget的主要語言就是JavaScript,當然還可以配合HTML、CSS甚至Java Applet。隨著Mac OS X的使用者日益成長,撰寫Widget已成為一個新興的軟體業。Dashboard其實不是第一個實踐這個概念的程式,早在Mac OS X 10.2時,就有一個叫Konfabulator的軟體做出這樣的平台,在當時大受歡迎。後來Apple在Mac OS X 10.4內建一樣功能的Dashboard時,還飽受外界的批評和指責,認為Apple是抄襲Konfabulator的。隨著Mac OS X直接支援Wedget,Konfabulator漸漸的淡出Mac的舞台。不過Yahoo看上了它,把它給買了下來,而在不久前重新推出Windows/Mac版的Yahoo!Webget,這下Windows的使用者也能享受Widget所帶來的便利和好處。

下一步

世界上有所謂的Java工程師、C/C++工程師、VB工程師等?,好像沒有JavaScript工程師這樣的頭銜,我個人覺得他應該是被需要的。JavaScript其實並不好學,教育訓練是個商機,開發除錯平台也是一個商機。這年頭流行認證,它也是可以著墨的,例如辦理類似SCJP、MCSE這樣的認證考式(事實上是有JavaScript Certification,不過沒像其它認證這麼有公信和正式就是了)。唯有專門的訓練課程及認證才能真正落實JavaScript的標準化,這樣寫出來的JavaScript程式才不會相容性不足,造成用某種瀏覽器才能觀看的特定網頁的情況。

而另一種是像無障礙網頁這種的認證,若你的網頁通過JavaScript相容性審合,那麼在網頁上就可以放上一個標章,証明你的網頁是相容於各種瀏覽器的。以上這些如果能由國際大廠來推動,成功的機會才會比較大,好奇的是,這麼多年來,怎麼沒人想幹這件事?!也許是吃力不討好吧。

註: TIOBE Programming Community Index for December 2005 http://www.tiobe.com/tpci.htm 相關連結:

ECMA-262
http://www.ecma-international.org/publications/standards/Ecma-262.htm

Spidermonkey
http://www.mozilla.org/js/spidermonkey/

Rhino
http://www.mozilla.org/rhino/

Dashboard
http://www.apple.com/macosx/features/dashboard/

Yahoo!Wedgets
http://widgets.yahoo.com/

posted @ 2006-03-02 20:09 killvin| 编辑 收藏

Buffalo介绍

Web正随着Amowa/Ajax的引入开发开始变得有趣!

Buffalo是Amowa思想实现的关键组件。Buffalo中定义了Web远程调用的传输基础,并且将远程调用对象完整的序列化到了本地,成为可以被JavaScript编程触及的对象。Buffalo中的重要组件 - BuffaloBinding,提供了将JavaScript对象绑定到HTML元素的能力。这种绑定将是无侵入的,只需要在HTML元素中加入若干个不影响排版的属性,即可将数据与界面绑定。

Buffalo1.1之前的版本重要的功能主要集中于远程调用与Java对象/JavaScript对象之间的双向序列化,这个特性已经被众多的应用久经考验,被认为是健壮的。它大大减少了在Java对象与JavaScript对象之间需要考虑的细节,开发者可以更用心地进行业务设计和界面设计。1.2版本的推出,引入了新的众多的特性,包括:新的重新改写的Spring集成;与流行javascript库prototype的集成;支持浏览器前进后退按钮,等等。Buffalo努力使Web2.0的概念在这个小小的轻量级的框架中得到体现,让你的应用具备更丰富的表现。

 

问题

可是在使用Buffalo传输对象的过程中我发现Buffalo并不能够很好的传递用户自定义的对象,不光如此在与作者的讨论过程中我还发现很多的人对于Javascript程序的规范根本不屑一顾,似乎选择了Javascript本身就是选择了"自由编程",可是真的如此吗?

其实Javascript是一个比较纯粹的OO语言,并不想一些报道中所说的那样,对于封装、继承、多态Javascript同样可以完成,既然它是OO的语言,就理所应当的按照某种规范来编写代码,正如JavaScript說分明 中说得那样,编写Javascript不是一件简单的事情,关键是很多人乱用了这个语言,从而导致给很多的朋友以为写Javascript就是很没有水准的事情(偶以前也是如此),但我最近发现用Javascript这样的动态语言没有一个很好的语言规范,你开发出来的应用只能够停留在小型系统之上,而对于比较大型的应用开发,这样写出来得代买根本无法复用!

再来说说Buffalo传输对象的问题,以下是我自定义的Node对象

function Node(_id , _type)
{

 //log("create node object!!!");
 //============================== property =================================//
    //=========================================================================//
 var id = _id;
 var type = _type; //<- type的出现在OO世界里本身就是背叛
 var brush = null //节点的物理表现句柄
 
 var rList = new Array();//左节点列表
 var lList = new Array();//右节点列表
 
 var rLine = new Array();//节点的左线列表
 var lLine = new Array();//节点的右线列表

 var x = 0;//节点的x坐标
 var y = 0;//节点的y坐标

 var m = false; //标识节点是否可移动
 var r = false; //标识节点是否能够建立联系

 var name = ""; //节点的名称

 //================================ method =================================//
    //=========================================================================//

 this.getType = function()
 {
  return type;
 }
 this.setType = function(_t)
 {
  type = _t;
 }
 this.setName = function(_n)
 {
  name = _n;
 }
 this.getName = function()
 {
  return name;
 }
 this.setId = function(_i)
 {
  id = _i;
 }
 this.getId = function()
 {
  return id;
 }
}

然而在使用Buffalo的时候Node对象根本就无法传递,而导致这个问题的原因竟然是我没有"自由的编写Javascript"!其实问题就出在doStructXML 方法上,作者在这里有意的"避开"了这个问题,所以在传递Node的时候对于其私有的方法Buffalo是根本就不考虑的,FAINT!

原先

 doStructXML : function(data){
  var boClass = data[Buffalo.BOCLASS];
  var boType = boClass ? boClass : "java.util.HashMap";
  var xml = "<map>\n";
  xml += "<type>" +boType+ "</type>\n";

  for (var i in data){
   if (data[i] != boType) {
    if (typeof(data[i]) == "function") continue; /* the function shouldn't transfered. */
    xml += this.getParamXML(this.dataTypeOf(i),i)+"\n";
    xml += this.getParamXML(this.dataTypeOf(data[i]),data[i]) + "\n";
   }
  }
  xml += "</map>\n";
  return xml;
 },

修改后

 doStructXML : function(data){
  var boClass = data[Buffalo.BOCLASS];
  var boType = boClass ? boClass : "java.util.HashMap";
  var xml = "<map>\n";
  xml += "<type>" +boType+ "</type>\n";

  for (var i in data){
   if (data[i] != boType) {
    if (typeof(data[i]) == "function")
    {
     /*判断方法名称*/
     if(i.substring(0,3) == 'get')
     {
      var _name = i.substring(3,i.length).toLowerCase();
      var _value = data[i]();
      //alert("functin name[" + this.dataTypeOf(_name) + "]=" + _name
      // + " | value[" + this.dataTypeOf(_value) + "]=" + _value);

      xml += this.getParamXML(this.dataTypeOf(_name),_name)+"\n";
      xml += this.getParamXML(this.dataTypeOf(_value),_value) + "\n";
     }
     continue; /* the function shouldn't transfered. */

    }
    
    xml += this.getParamXML(this.dataTypeOf(i),i)+"\n";
    xml += this.getParamXML(this.dataTypeOf(data[i]),data[i]) + "\n";
   }
  }
  xml += "</map>\n";
  return xml;
 }

 

总结

1。Javascript的编写是否应该遵守一定的规范,其实属于程序员自身休养的问题,但我还是觉得复用JAVA的编码规范不是坏事情!

2。时刻记住Buffalo是建立在burlap协议的基础上的,我们应该把注意力放在burlap上而不仅仅只是Buffalo!

posted @ 2006-03-02 19:46 killvin| 编辑 收藏

不知道已经被欺骗了多少次了,自己依然还是很留恋CSDN的Blog空间,是不是自己太虚荣了?无论如何都不想再次的被忽悠!

posted @ 2006-03-02 19:24 killvin| 编辑 收藏