2006年6月27日

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| 编辑 收藏