Saturday, August 26, 2006

JSF中请求过程的生命周期


前面我们讲了JSF中包含的:组件、事件、监听器以及很多好用的组件,这些功能都使得JSF的开发变得更简单。那么这些部分的执行过程是什么样的呢?为了更容易理解,我们讲分析JSF框架的底层Servlet API,这对于你更好的理解JSF的执行过程是有好处的。了解了这些内容也有助于你开发出更好的应用程序,因为你明白底层到底是如何运行的。当然如果你是一个WEB界面开发人员,你完全可以跳过这个部分。
在这一节中我们讲讨论JSF是如何对请求作出响应的。或者说,由包含JSF组件的页面发出的请求是如何被JSF处理的。
图2.4是一个状态图,它显示了当接收到来自客户端发出的请求后JSF是如何处理的,即JSF的请求处理过程。这个过程开始于JSFservlet接收到一个来自客户端的请求(记住JSF是建立在Servlet基础之上的)

图2.4

列表2.2对以上每一个状态进行了注释,其中主要有六个状态,每一个状态之后多会有相应的时间产生。JSF将这些事件交给相应的监听器进行处理,不管监听器是处理某些业务逻辑还是组件,都能够继续跳转到最终的状态,显示响应结果。监视器也可以跳过最终的状态直接显示一个响应结果,比如它可以返回一个二进制内容,执行一个页面跳转或者返回其他与JSF有关的内容比如XML文档或者是一个HTML。
以上六个步骤中有四个可以产生消息,它们分别是:接收请求值(Apply Request Values),执行验证(Process Validations),更新模型值(Update Model Vlaues),和提交应用程序(Invoke Application)。不管是否带有消息,这些状态都能够向用户反馈响应,除非监听器、装饰器或者组件自身直接返回响应。这些状态之后是一个依据时间主线提交应用程序的过程。一系列的组件、校验过程、支持Bean和模型对象将被更新。简单的讲,JSF为你完成了大部分工作:它捕获了请求的详细信息并把它们传输给包含组件和时间的高层视图,而且还更新了相关对象的属性。
表2.2 JSF 各个状态的请求响应过程

状态
描述
触发的事件
还原页面(Restore View)
为选择的页面查找或创建一个组件树。一些组件,比如HtmlCommandButton组件在此状态下将产生动作事件(或者其他事件)
状态事件
接收请求值(Apply Request Values)
比较请求传输的值并更新相应组件的值,也可以使用转换器。如果出现异常将抛出一个异常消息。同时会产生来自请求参数的事件。
状态事件
数据模型事件
动作事件
处理验证(Process Validations)
对每个组件执行验证。可能会产生验证消息。
状态事件
数据模型事件
值变化事件
更新模型值(Update Model Values)
更新与组件相关的所有模型对象和支持Bean的值。可能会产生异常消息。
状态事件
数据模型事件
提交应用程序(Invoke Application)
调用注册的监听器。缺省的监听器将执行命令组件(比如HtmlCommandButton)的事件方法或者选择下一个要显示的视图。
状态事件
动作事件
返回相应(Render Response)
显示当前逻辑指定的下一个响应页面。
状态事件

为了让你对此有一个更清晰的了解。我们将通过一个hello world的例子展示JSF处理请求过程的整个生命周期。这个例子来自于我们前面章节讲述的hello.jsp。列表2.1显示了实际上Http的处理过程。
列表2.1 Http 的请求过程
POST /jia-hello-world/faces/hello.jsp HTTP/2.1 (1)接收来自URL的请求
Host: deadlock:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:2.2.1)
Gecko/20021130
Accept: text/xml,application/xml,application/xhtml+xml,text/html;
q=0.9,text/plain;q=0.8,video/x-mng,image/png,
image/jpeg,image/gif;q=0.2,text/css,*/*;q=0.1
Accept-Language: en-us, en;q=0.50
Accept-Encoding: gzip, deflate, compress;q=0.9
Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66
Keep-Alive: 300
Connection: keep-alive
Referer: http://deadlock:8080/jia-hello-world/faces/hello.jsp (2)同样是接收来自URL的请求
Cookie: JSESSIONID=58324750039276F39E61932ABDE319DF (3)会话标记
Content-Type: application/x-www-form-urlencoded
Content-Length: 92
welcomeForm%3AhelloInput=64& (4)组件值
welcomeForm%3AredisplayCommand=Redisplay&
welcomeForm=welcomeForm
我们并不打算讨论Http处理的详细过程,不过上表其中几行与我们的讨论是有关的:
(1)请求数据来源于/jia-hello-world/faces/hello.jsp 这个连接的POST数据。
(2)这里的referer(引用)是由请求页面产生的,他与/jia-hello-world/faces/hello.jsp是相同的页面。
(3)这里的Cookies将被Servlet容器使用,它用于将请求数据映射到一个指定的会话中。在这个例中JSF使用Servlet会话来存储当前的视图(页面也可以保存在由客户端控制的一个值对象中,比如隐藏字段中)。
(4)这是非常重要的一个部分,这里包含了JSF传输的实际参数值(&用于分隔不同的参数,%3A将被解析为“:”冒号)。首先是一个名为 welcomeForm:helloInput的参数,并且它的值是64,这个值是通过浏览器页面输入的,第二个参数名为welcomeForm: redisplayCommand,他的值是“Redisplay”,最后一个参数是welcomeForm,它的值也是welcomeForm。在下面 的讲解中我们将逐步介绍这些值是如何处理的。
一旦JSF接收到请求,它就会创建并实例化一个javax.faces.context.FacesContext对象。这个对象代表当前的请求状态,并且负责处理底层Servlet request对象的各个部分,事件监听器可以通过它获得当前页面的操作句柄,以此来进行增加消息、记录事件等等操作。在JSF中它代表了请求处理生命周期的整个过程。我们将在下面的讲解中依次讨论每一个状态。


Thursday, August 24, 2006

JSF中的导航功能

之前我们讲述的概念中所有操作都是在一个单独的页面上。在实际的Web应用开发中,这是不太可能的。Web应用的开发会有很多页面,我们必须提供一种方法实现不同页面之间的切换。实现从一个页面到另一个页面切换的机制被称为“导航”。

JSF中有一套非常完善的导航系统。导航控制器根据请求实现的返回值确定下一个页面跳转到哪里。对于给定的页面,一个导航规则定义了根据业务逻辑的动作事件判断页面应该如何跳转。根据导航规则发生的每一个页面跳转被称为一个导航事件。导航规则定义在JSF的配置文件中。下面这个例子演示了针对longin.jsp页面的两个导航事件:success和failure的一个导航规则。

<navigation-rule>
     <from-view-id>/login.jsp</from-view-id>
     <navigation-case>
     <from-outcome>success</from-outcome>
     <to-view-id>/mainmenu.jsp</to-view-id>
     </navigation-case>
    <navigation-case>
    <from-outcome>failure</from-outcome>
    <to-view-id>/login.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

如你所见,每一个出口对应一个JSP页面,这里你不需要通过编码实现这些规则。如果你曾经使用过Struts你对这种定义方式一定不会陌生。你会发现,所有的导航事件只映射一个出口(对应一个jsp),也就是说,如果将来需要进行变更你只需要修改需要变更的那一个页面就可以了而不是修改多处。

现在,JSF中的几个基本概念已经介绍完了。在随后的讲解中我们会看到更多的例子。已经这些概念集中到一起是如何使用的。

Wednesday, August 23, 2006

Java7中将增加闭包的支持

现在越来越多的动态语言比如:PythonRuby等受到了大家的欢迎,Java中也衍生出了Groovey,在这些语言中都提供了“闭包”的支持。在Java未来将发布的最新版本Dolphin也将增加“闭包”的功能(closure)

类似GroovyPythonRuby这样的语言中都存在闭包的概念,JavaC++C#这样的语言都不支持闭包,什么是闭包呢?记得以前看到的一篇文章中这样说过“对象是附带过程的数据,闭包是附带数据的过程”。听起来有点抽象,简单一点讲闭包就是“一段封闭的代码”(block),下面是Java7规范草案中有关闭包的一段代码:

int(int) plus2b = (int x) {return x+2; };

闭包可以包含参数和返回结果,这比起Java中的匿名内部类要要简单灵活的多。个人认为Java中增加闭包函数除了增加灵活性;而且更易于对代码进行抽象;同时可以减少临时对象对内存的开销,虽然Java有自动垃圾回收机制,但是有时候在一些应用程序中临时对象的存在可能就是性能的瓶颈。目前Java7中的闭包部分的规范还在起草阶段,期望未来的Java中可以增加更多的动态语言的支持。

如果你对此有兴趣,可以下载规范草案

http://blogs.sun.com/roller/resources/ahe/closures.pdf

什么是闭包

有很多人问我,“闭包是什么意思”。是否可以通过实际的开发语言举一个具体的例子,闭包到底意味着什么?
如果你没使用过闭包进行编程,或者你在过去的10年里对Java接触的较少的话,闭包的含义确实较难理解。下面让我从一个实际的例子开始,逐步的向你介绍什么是闭包。

问题的提出

假设你现在要开发一个这样的应用程序,它需要维护保存在一个列表中的文档,每一个使用这个程序的用户需要对这些文档添加注释。为了讲解闭包的原理,在这个例子中假设有两组互不相干的文档列表。这时,如果你从一个用户那里获得一组文档的注释,需要从上面的两组文档列表中获得相应的文档注释内容:

class Document { ... }
class DocAnnotation { ... }
class Person { ... }
class Documents {
    static List allDocuments();
}
class Persons {
    static Set allPersons();
    static Person GEORGE_W_BUSH = ...;
}
class DocAnnotations {
    static Map> allAnnotations = ...;
}

现在,针对上面的程序你可能会问诸如:乔治.布什在这些文档中是否提到有关伊拉克的机密问题。为了实现这个需求,在这个假想的程序中我们可以这样作:

boolean bushMarkedAnyIraqDocsSecret() {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(GEORGE_W_BUSH).iterator();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        if (doc.mentions("Iraq") && docAnn.marked("secret")) {
            return true;
        }
    }
    return false;
}

我们可以将上面的需求进一步抽象为,我们要找的文档是什么,我们要找的文档注释是谁写的以及我们要找的文档注释属于哪种类型,抽象后的方法如下:

boolean personAnnotatedOnKeyword(Person person, String ann, String key) {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(person).iterator();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        if (doc.mentions(key) && docAnn.marked(ann)) {
            return true;
        }
    }
    return false;
}
通过上面的抽象,我们已经尽可能的避免了代码的重复,如果再提出类似的问题,只要变化一下参数就可以了。对方法或类的抽象是非常必要的,因为这可以在我们需要重构的时候尽量减少代码的修改。比如,如果我们需要把一个人的注释表现形式改成Map而不是List,又或者希望DocAnnotation包含一个Document的引用,那么我们就必须修改上面方法中的循环。所以如果将这段循环放在一个单独的地方那么维护起来就方便的多了。

JDK5中对循环进行抽象
下一步要作的是对循环本身进行抽象。Java中提供了很多方便的循环结构,比如最近提供的包含Iterable 接口的for-each循环。在这里可以使用它,但是我们需要介绍在上面的例子中我们需要进行迭代循环的是什么类型:

class DocAndAnnotation {
    final Document doc;
    final DocAnnotation docAnn;
    DocAndAnnotation(Document doc, DocAnnotation docAnn) {
        this.doc = doc;
        this.docAnn = docAnn;
    }
}


现在我们可以提供一个只进行一次循环就包含了文档和注释的集合。


Collection docsWithAnnotations(Person person) {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(person).iterator();
    List result =
        new ArrayList();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        result.add(new DocAndAnnotation(doc, docAnn));
    }
    return result;
}


这样我们就可以写一个更特别的循环,就像下面这样:


boolean personAnnotatedOnKeyword(Person person, String ann, String key) {
    for (DocAndAnnotation docAndAnn : docsWithAnnotations(person)) {
        if (docWithAnn.doc.mentions(key) && docWithAnn.docAnn.marked(ann)) {
            return true;
        }
    }
    return false;
}
目前为止,上面的方法存在一个问题:每次它都会把全部的文档和注释装入列表集合中,
尽管可能我们的答案在第一个注释文档中就可以找到。我们可以作一些改进,那就是创建一个
“懒加载”的迭代器取代上面的集合。我们重写了docsWithAnnotations如下:
Iterable docsWithAnnotations(Person person) {
final Iterator docI =
Documents.allDocuments().iterator();
final Iterator annI =

DocAnnotations.allAnnotations.get(person).iterator();

return new Iterable() {

public Iterator iterator() {

return new Iterator() {

public boolean hasNext() {

return docI.hasNext() && annI.hasNext();

}

public DocAndAnnotation next() {

return new DocAndAnnotation(docI.next(), annI.next());

}
public void remove() {
throw new UnsupportedOperationException();

}

};

}

};
}
现在你不需要修改personAnnotatedOnKeyword它同样可以运行。
但是新的问题又出现了,上面的方法产生了一大堆小的、临时的垃圾对象。
Iterator, Iterable, DocAndAnnotation对象的存在只是临时性的,
它们的作用就是简单的把数据从一个地方搬到另一个地方。
这是不是什么大问题。
HotSpot有很多垃圾回收机制,
它们可以很好的将这些临时对象进行重新整理和释放。但是在有些应用中它们可能是性能的瓶颈。


有没有好的循环方式可以避免这一问题呢?答案是肯定的,Java有一个标准的习惯用法用于处理这一问题。方法就是控制循环的内部回路。较之在personAnnotatedOnKeyword方法中执行循环更好的方式是,我们提供一个接口,由接口中的方法执行循环并把值传给由personAnnotatedOnKeyword提供哦的一段代码。就像这样:

interface WithDocumentAndAnnotation {
void doIt(Document doc, DocAnnotation docAnn);
}void docsWithAnnotations(
Person person, WithDocumentAndAnnotation body)
{

Iterator docI = Documents.allDocuments().iterator();

Iterator annI =

DocAnnotations.allAnnotations.get(person).iterator();

while (docI.hasNext() && annI.hasNext()) {

Document doc = docI.next();

DocAnnotation docAnn = annI.next();

body.doIt(doc, docAnn);

}

}


客户端可以通过提供一个实现这个接口内部类来执行循环:

boolean personAnnotatedOnKeyword(
        Person person, final String ann, final String key) {
    class MyBody implements WithDocumentAndAnnotation {
        boolean result = false;
        public void doIt(Document doc, DocAnnotation docAnn) {
            if (doc.mentions(key) && docAnn.marked(ann)) {
                result = true;
            }
        }
    }
    MyBody body = new MyBody();
    docsWithAnnotations(person, body);
    return body.result;
}

这种方法解决了临时对象内存分配的问题(尽管它还存在问题),但是这种方法还是没有解决我们开始提出的问题,那就是不管是否我们需要找的文档就在第一个文档中它还是会装入所有的文法。我们可以通过修改WithDocumentAndAnnotation接口的方法,让它返回一个boolean值,用来判读循环是否需要继续执行。这就需要修改客户段程序中docsWithAnnotations的很多部分来适应新的API,然而有时这是不可行的。看来我们应该修改接口的功能,但是我们发现客户端使用不同的控制流程来迭代整个文档和它们的注释。现在,我们向读者描述了上面练习的详细内容。

通过闭包抽象上面的循环
闭包提供了更便利的方法来抽象上面的循环:

void docsWithAnnotations(Person person, void(Document,DocAnnotation) block) {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(person).iterator();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        block(doc, docAnn);
    }
}

上面的方法中除了没有WithDocumentAndAnnotation接口之外和我们最后一个JDK5版本的例子非常类似。
让我们来看看客户端是什么样子:
boolean personAnnotatedOnKeyword(
Person person, final String ann, final String key) {

docsWithAnnotations(person, (Document doc, DocAnnotation docAnn) {

if (doc.mentions(key) && docAnn.marked(ann)) {

return personAnnotatedOnKeyword: true;
}
});

return false;
}


就这样,如果我们采纳功能修改建议的话,客户端还可以是这样:

boolean personAnnotatedOnKeyword(
Person person, final String ann, final String key) {


docsWithAnnotations(person) (
Document doc, DocAnnotation docAnn) {

if (doc.mentions(key) && docAnn.marked(ann)) {

return true;

}

}

return false;

}
上面代码中仅有的临时对象是闭包对象和两个用于实现docsWithAnnotations迭代器对象。如你所见,docsWithAnnotations的调用者可以使用控制层的操作,比如docsWithAnnotations的实现必须首先确定哪一个类型的控制层是必须的,否则什么都不返回。
这样,只有很少的临时对象需要处理。因此HotSpot虚拟机只需要处理很少的异常,避免了内存的开销。
上面几个版本的迭代向我们展示了闭包的优美之处:它可以让你抛开繁复的代码写出更好的抽象。




Tuesday, August 22, 2006

groovy中的闭包

groovy中引入了闭包的概念(closure) 。这是一个很好的功能,类似与Java中的内部匿名类,但是它可以支持状态的自动传入传出,而且还支持参数,这比起内部匿名类更加方便。据说Java7中也将支持闭包,期待中。

Sunday, August 20, 2006

翻译:JakataStruts 和JavaServerFace

翻译来源http://java.sys-con.com/read/260036.htm
设计一个包含有序选择的冗长列表需要非常复杂的设计模式。这种模式需要将可选项显示在一个列表中,同时还需要在另一个地方进行选择,所以需要用户的可以方便的进行选择和修改.
这种模式通常称为双重列表或者叫双重列表框选择器.它又被称为选择概要或者列表创建者模式.按照Java Look and Feel的设计规则,它又被称为增加-移除模式。
标准的Struts实现,需要试用JSP,Java和JavaScript。JavaServerFace不需要JSP技术,但是比起Struts要简单的多。
列表1是使用Struts技术的JSP代码,列表2是使用JavaServerFace技术的JSP代码。JSF的支持Bean,配置文件和其他文件的代码可以从JDJ网站下载。
Jakarta Struts的实现方式
使用Jakarta Struts技术,JSP需要定义一个表格来显示树状列。在第一列和第三列,可选列通过<html:select>标签和<html:optionCollection>来实现。为了实现国际化要求,label标签使用了JSP标准表情库中的<fmt:message>标签。很多人发现结合Struts和JSTL标签一切使用功能非常强大。
<table border="0" cellpadding="0" cellspacing="5">
<tr align="middle" valign="center">
<td>
<fmt:message key="titles.available"/><br />
<html:select property="availableValues"
multiple="true" size="7"
style="width:80px;" styleId="available"
onchange="doUpdate( false, true );">
<html:optionsCollection
property="availableList"/>
</html:select>
</td>
...
<td>
<fmt:message key="titles.chosen"/><br />
<html:select property="chosenValues"
multiple="true" size="7"
style="width:80px;" styleId="chosen"
onchange="doUpdate( true, false );">
<html:optionsCollection
property="chosenList"/>
</html:select> </td>
</tr>
</table>
Struts的表单Bean包含了用于JSP的“可见列表”,“可见值”,“可选列表”和“可选值”属性。
private List availableList = new ArrayList();
private String[] availableValues = new String[ 0 ];
private List chosenList = new ArrayList();
private String[] chosenValues = new String[ 0 ];
...
public List getAvailableList()
public void setAvailableList( List list )
public String[] getAvailableValues()
public void setAvailableValues( String[] list )
public List getChosenList()
public void setChosenList( List list )
public String[] getChosenValues()
public void setChosenValues( String[] list )
“availableList ”和“chosenList ”属性通过标签值Bean的方式封装了列表的可见值。“availableValues ”和“chosenValues ”以字符串数组的形式存储了可选的值。
对于增加-选择模式来说,不会关心这些可选值。对于服务器来说我们只要告诉它列表中显示什么值而无需告诉它那个值被选中了。然而当表单被提交后,只有被选中的值会被提交给服务器。这是通常使用的设计方法,但是这种设计模式存在问题。如图1.
下面有集中方法可以解决这个问题。一种方法是在每次列表内容发生变化的时候都向服务器提交一次值。者引发了过度的页面引用和网络负载。另一种方法是使用AJAX技术与服务器进行通信。这减少了页面的刷新,但是仍然会代理网络的负载。直到用户完成列表变更之前都不应该产生网络负载。
最好的解决方案是把选中的值存储在一个隐藏字段中。使用这种方法当表单提交时只向服务器提交一次值。在这个例子中我们把选中的值存储在一个隐藏字段中。利用这个隐藏字段,两个列表中见的按钮可以这样实现:

<td>
<br />
<input type="button" style="width:100px;" id="add" onclick="
doMove( 'available', 'chosen', false );" value="<fmt:message key='add'/>" /><br />
<input type="button" style="width:100px;" id="addAll" onclick=
"doMove( 'available', 'chosen', true );" value="<fmt:message key='addAll'/>" /><br />
<br />
<input type="button" style="width:100px;" id="remove" onclick=
"doMove( 'chosen', 'available', false );" value="<fmt:message key='remove'/>" /><br />
<input type="button" style="width:100px;" id="removeAll" onclick=
"doMove( 'chosen', 'available', true );" value="<fmt:message key='removeAll'/>" />
<html:hidden styleId="chosenItem" property="chosenItem" />
</td>
这个按钮通过HTML标签<input>实现。<html:hidden>字段存储了选中的值。被选中的值通过JavaScript方法“doMove()”进行保存,所有4个按钮都执行这个方法。

/**
* Move selected items between lists.
* <p>
* @param sourceId ID of source list
* @param destId ID of destination list
* @param all true iff moving all
*/
function doMove( sourceId, destId, all )
{
// Move the items between the lists.
var sourceElem = document.getElementById( sourceId );
var destElem = document.getElementById( destId );
for( var i = 0; ( i < sourceElem.length ); )
{ if( sourceElem.options[ i ].selected || all )
{ var newOption = document.createElement( "OPTION" );
newOption.text = sourceElem.options[ i ].text;
newOption.value = sourceElem.options[ i ].value;
destElem.options[ destElem.length ] = newOption;
sourceElem.remove( i );
}
else
i++;
}
// Update the button states.
doUpdate( false, false );
// Store the chosen items in a hidden field.
var chosenItem = document.getElementById( "chosenItem" );
var chosenList = document.getElementById( "chosen" );
chosenItem.value = "";
for( var i = 0; ( i < chosenList.length ); i++ )
{ chosenItem.value += chosenList.options[ i ].value + '|';
}
}
上面的方法非常易于理解,使用起来也很方便。按钮只要在需要的时候才会显示。比如如果没有列表值被选中,那么“Add”和“Remove”按钮就无法使用。当显示列表和选择列表都是空的时候,那么“Add All”和“Remove All”按钮就无法使用。
“doUPdate()”函数根据用户的选择和列表中包含的内容判断按钮是否可以显示。如图2
“doUpdate()”方法由上面由列表控件的OnChange方法触发的“doMove()”方法调用。或者在body的onload方法中调用。

/**
* Update the button states based on whether
* lists have contents and selected items.
* Deselect list items if requested to ensure
* at most one list contains selections.
* <p>
* @param offAvailable deselect available list
* @param offChosen deselect chosen list
*/
function doUpdate( offAvailable, offChosen )
{
// Get the lists and deselect if requested.
var availableList = document.getElementById( "available" );
var chosenList = document.getElementById( "chosen" );
if( offAvailable ) availableList.selectedIndex = -1;
if( offChosen ) chosenList.selectedIndex = -1;
// Update the button states.
document.getElementById( "addAll" ).disabled =
( availableList.length == 0 );
document.getElementById( "removeAll" ).disabled =
( chosenList.length == 0 );
document.getElementById( "add" ).disabled =
( availableList.selectedIndex < 0 );
document.getElementById( "remove" ).disabled =
( chosenList.selectedIndex < 0 );
}
使用JavaScript函数,列表中包含的内容被正确的更新,用户选择的值被保存在一个隐藏字段中。在这个例子中它被用于控制两个列表中值和用户的选择。
列表中的值被存储在表单Bean中的一个“languageList”属性里。这个列表中包含了所有可能的选项。move方法利用“chosenItem”列表中的值控制了“可见”和“可选”列表中的值。

private List languageList = new ArrayList();
private String chosenItem = "";
...
public List getLanguageList()
public void setLanguageList( List list )
public String getChosenItem()
public void setChosenItem( String items )
public void move( List sourceList, String[] sourceValues, List destList )
...
"languageList" ,"chosenItem" 属性和 "move"方法通过表单提交的Action中的“execute”方法执行。在Struts中是StrutsAction类的实例。

/**
* Populate the lists from the hidden field.
* <p>
* @param mapping action mapping
* @param form action form
* @param request HTTP servlet request
* @param response HTTP servlet reponse
* @throws Exception
*/
public ActionForward execute( ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response ) throws Exception
{
// Populate available list from language list.
ExampleForm eForm = ( ExampleForm )form;
eForm.getAvailableList().clear();
eForm.getAvailableList().addAll( eForm.getLanguageList());
// Populate chosen list from hidden field.
eForm.getChosenList().clear();
eForm.move( eForm.getAvailableList(),
eForm.getChosenItem().split( "\\|" ),
eForm.getChosenList());
}
JavaServerFace实现
那么JavaServerFace中是如何实现上面的模式呢?JSF由一群曾经设计过Struts的人设计,所以我们可以较容易的从Struts过度到JSF。
JSF中的JSP也包含了两个列表,因为JSF的标签更加紧密。代替<table>,<tr>和<td>标签,JSF使用了<h:panelGrid>标签。代替Struts中的<fmt:message>标签JSF使用了<h:outputText>。代替<html:select>和<html:optionsCollection>标签,JSF使用了<h:selectManyListBox>和<f:selectItems>。

<h:panelGrid columns="3" rowClasses="center">
<h:outputText value="#{bundle.available}" />
<h:outputText value="" />
<h:outputText value="#{bundle.chosen}" />
<h:selectManyListbox style="width:100px; height:120px;"
id="available" value="#{example.availableValues}"
onchange="doUpdate( false, true );">
<f:selectItems value="#{example.availableList}"/>
</h:selectManyListbox>
...
<h:selectManyListbox style="width:100px; height:120px;"
id="chosen" value="#{example.chosenValues}"
onchange="doUpdate( true, false );">
<f:selectItems value="#{example.chosenList}"/>
</h:selectManyListbox>
</h:panelGrid>
就像Struts中实现的那样,<h:selectManyListBox>标签映射了支持Bean中的“availableList”“availableValues”和“chosenList”以及“chosenValues”属性,他们等同于Struts中的相应属性。
就像Struts实现中的一样,四个按钮也由标准HTML的<input>标签实现。
在JSF中这些标签还必须附上<f:verbatim>标签。除此之外这些标签和Struts中的没什么不同。这里有一点与Struts类似的就是在每个JavaScript方法中经常需要包含一个“form:”前缀。

<h:panelGrid columns="1">
<f:verbatim>
<input type="button" style="width:100px;"
id="add" onclick="doMove( 'form:available', 'form:chosen', false );"
value="<fmt:message key='add'/>" /><br />
<input type="button" style="width:100px;"
id="addAll" onclick="doMove( 'form:available', 'form:chosen', true );"
value="<fmt:message key='addAll'/>" /><br />
<br />
<input type="button" style="width:100px;"
id="remove" onclick="doMove( 'form:chosen', 'form:available', false );"
value="<fmt:message key='remove'/>" /><br />
<input type="button" style="width:100px;"
id="removeAll" onclick="doMove( 'form:chosen', 'form:available', true );"
value="<fmt:message key='removeAll'/>" />
</f:verbatim>
<h:inputHidden id="chosenItem" value="#{example.chosenItem}" />
</h:panelGrid>
除此之外,JSF中的doMove方法和doUpdate方法没什么不同。
JSF的优势是它控制了提交事件。在JSF中你可以明确的定义一个按钮具体执行那个请求。
<h:commandButton action="#{example.submit}" value="#{bundle.submit}" />
“example:submit”方法会读取隐藏字段中内容并更新列表。因为这个方法直接关联在一个提交按钮上,所以你不必单独实现一个Action对象。
public String submit()
{
// Populate the available list from the language list.
availableList.clear();
availableList.addAll( languageList );
// Populate the chosen list from the hidden field.
chosenList.clear();
move( availableList, chosenItem.split( "\\|" ), chosenList );
...
return( "success" );
}
JSF中不需在学什么,虽然标签不同,但是通常和JSP中的类似。Java代码也差不多。
需要强调的是JSF中的组件架构。如果你下载了免费的SUN Java Studio Creator,你会发现它包含了一整套可以在你的GUI中任意增加-删除的组件。SUN的组件包含很多功能,比如“Move Up”和“Move Down ”按钮可以改变列表中值的顺序。我们还可以列举很多这样的有用组件。
结论

这篇文章展示了一个从两个列表中取值的标准UI设计模式。通过Jakarta Struts和JavaServer Face两种方式实现。
JSF提供了一种从Struts平滑过度的方法。JSP的标签非常简单那;支持Bean的代码也和Struts类似;并且,如果你需要使用JavaScript在JSF中的用法也是一样的。
你可以把JSF当成是一个简单的,组件化的Struts。它增加了很多有用的特性,并且减少了复杂性。
对于大多数刚接触Web项目的开发人员,JavaServer Faces是一个很好的选择。对于熟悉Struts的开发人员,过度到JSF也很容易。

Thursday, August 17, 2006

存储过程写完了

今天下午用了2个小事的时间终于把导数据的存储过程写完了,明天开始测试。哈哈,好高兴!

Java 将支持Visual Basic

SUN负责SUN平台小组的负责人Graham Hamilton在一次访谈中提到JAVA SE7(命名代码:海豚)将支持Visual Basic 。
SUN不准备克隆某个固定版本的VB,它将致力于在未来的Java平台中增加通用的VB.NET的支持。“如果你对VB.Net熟悉的话,你会发现这将是非常容易学习的语言”Hamilton说对于Basic语言支持的基础实现已经在开发中。
这意味着Java将有VB语言的特性,而不是实现VB语言本身。基于这一点,我认为SUN不会提供基于VB代码的编译器。不管怎么说这都是件好事,尤其是对于以前从事过VB开发的人,而对于像我一样的从未使用过VB的人也不是什么坏事,毕竟这只是一个新增的特性。你怎么看?

Wednesday, August 16, 2006

JSF 中的消息机制

之前我们讨论了不同的JSF组件,那么如果发生错误是会怎样呢?现在最大的问题是如何让UI组件显示一个恰当的错误消息。这个消息可以分为两种:一种是应用程序错误(比如应用程序逻辑错误,数据库错误,连接错误等等),另一种是用户输入错误(比如文本框输入校验和空值校验等等)。

应用程序错误一般会产生一个新的页面以便显示错误消息;而用户输入错误一般会回显原来的页面,然后把错误信息显示在一个文本中。通常你需要在不同的页面抛出相同的错误信息,所以你必须保证对于同样的错误用户看到的消息是一样的。你一定不希望在一个页面中向用户显示“请输入电话号码”而在另一个页面中却显示“电话号码是必须输入的”这样的消息。

JSF提供了消息机制来处理这一问题。一个消息包含了:摘要信息、详细信息和错误级别信息。消息可以自动定制为用户当期所使用的语言。你的应用程序中任何UI组件、校验器、转换器、事件和监听器都可以加入消息。你可以通过组件或者应用程序代码来控制当期的消息输出。

消息不一定非要显示错误信息,他们同样可以用于显示其他信息。比如事件监听器可以增加一个显示记录被成功保存的消息。消息本身可以关联到一个特定的组件(比如输入错误)也可以是应用程序级的消息。

你可以通过HtmlMessage组件为一个特定的组件增加显示错误信息的供。你一定还记得Hello Wold 应用成中的HtmlMessage组件吧:

上面这个标签会显示所有由helloInput组件产生的错误消息。你也可以使用HtmlMessage组件显示非UI组件的消息。

消息机制提供了一种适当的方法向用户显示错误信息或者其他信息。消息是JSF校验器的内部类型,一旦校验错误就会向产生一个错误消息。当你向希望向用户输出一个消息的时候这也是一个好办法,因为你不用关心消息是怎么产生的,你只需给事件监听器增加一个消息,这个消息就会自动显示出来。后面我们会讲解如何增加应用程序消息和如何在代码中控制消息输出。

Python的内容搬家了

为了更好的归类Blog文章,以后的Python和wxPython 的文章都发布到专门的blog :Python BLog