JSF 2简介:JSF向导
2010-10-26 12:59:18 来源:Web开发网当用户单击 Next 或 Previous 按钮时,JSF 就会向服务器进行一次 Ajax 调用,并且当此 Ajax 调用返回时,JSF 就会更新问题、问题的选择(单选按钮)以及按钮本身。
Finish 按钮不是一个 Ajax 按钮,因为单击它会导航到 done 页面。
请注意清单 7 和 8 中对 wizard bean 有很多引用。该 bean 实际上是这个测验向导的一个控制器。我在本文结束之前来介绍一下这个 bean。
CDI: Dependency injection and conversations
CDI 可被描述为打了激素的 JSF 托管 beans。作为 Java EE 6 的一个组件,CDI 可以说是在 Spring 内酝酿许久的一些概念的标准化,比如依赖注入和拦截器。实际上,CDI 和 Spring 3 有很多类似的特性。
CDI 让您能够通过松散耦合(loose coupling)和强类型化(strong typing)为关注点解除耦合。这样一来,您就得以从日常的 Java 编程的辛劳中解放出来,比如实例化对象和控制对象的生命期。
从 JSF 的角度,CDI 的一个特别吸引人之处是 conversation 作用域。conversation 作用域是 Seam 最早提出来的,指的是一个生命期可通过编程方式控制的作用域,它让您能够从请求和会话之间的全有或没有的两难中逃离出来。
此向导对 CDI 的所有使用都位于 Wizard bean 内,如清单 9 所示:
清单 9. Wizard bean
package com.clarity;
import java.io.Serializable;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.inject.Produces;
import javax.faces.event.ActionEvent;
import javax.inject.Inject;
import javax.inject.Named;
@Named()
@ConversationScoped()
public class Wizard implements Serializable {
private static final long serialVersionUID = 1L;
private Questions questions = new Questions();
private int cursor = 0;
@Inject
private Conversation conversation;
@Produces @Named
public Question[] getQuestions() {
return questions.getQuestions();
}
public void nextButtonClicked(ActionEvent e) {
incrementCursor();
}
public void previousButtonClicked(ActionEvent e) {
decrementCursor();
}
public void incrementCursor() { ++cursor; }
public void decrementCursor() { --cursor; }
public int getCursor() { return cursor; }
public void resetCursor() { cursor = 0; }
public boolean getNextButtonEnabled() {
return cursor != questions.size() - 1 &&
(questions.getQuestions())[cursor].isAnswered();
}
public boolean getPreviousButtonEnabled() {
return cursor > 0;
}
public boolean getFinishButtonEnabled() {
return cursor == questions.size() - 1 &&
(questions.getQuestions())[cursor].isAnswered();
}
public String start() {
conversation.begin();
return "quizWizard/wizard";
}
public String end() {
conversation.end();
return "/done";
}
private void setCurrentQuestionUnanswered() {
Question currentQuestion = (questions.getQuestions())[cursor];
currentQuestion.setAnswered(false);
}
}
此测验向导应用程序几乎所有的有趣代码都位于 清单 9 内。首先,Wizard bean 具有一些方法,能控制向导按钮启用状态,正如我在前一章节中所讨论的。它还具有另外一些方法,当用户单击 Next 或 Previous 按钮时,JSF 就会调用这些方法,分别前进到下一个问题,或移回至前一个问题。
但表面上,Wizard bean 最有趣的一点是其对 CDI 的使用。首先,本系列从始至终都使用了 @Named 注释的 CDI 实现(它实际由 JSR 330, Dependency Injection for Java 定义)来代替 @ManagedBean。两个注释都创建一个可从 JSF 表达式语言访问的设置了作用域的 bean。但 CDI 的受管 bean 的情形则更为复杂,所以如果您使用的是一个 Java EE 6 兼容的服务器,那么应该优先选用 @Named 而非 @ManagedBean。
如果仔细研究 清单 6 和 清单 7,就会发现我用 JSF 表达式语言访问了一个名为 questions 的 bean。您可能还记得我在 清单 2 中实现了一个 Questions 类。不过,在 清单 2 中并未出现过 @Named 注释。在通常情况下,注释的缺少会导致一个错误,但在本例中,questions bean 来自别处 — 它由 Wizard.getQuestions() 方法生成。该方法由一个 @Produces 注释,这意味着如果您在表达式语言中引用这个 bean,那么 JSF 就会调用该方法来获得这个 Questions bean。
之后是 Wizard bean 对 conversation 作用域的使用。应用程序欢迎页面内的 Start the wizard start Wizard bean 的 start() 方法,此方法通过调用 conversation 的 begin() 方法开始一次对话。该方法会将当前请求(实际上是一次持续单个请求的对话)提升成一个长时间运行的对话,此对话直到超时或有人调用 conversation 的 end() 方法才会结束。由于我已经为 Wizard 指定了 Conversation 作用域,所以它的生命期会随着对话的结束而结束。
当然,您可以避开 conversation 作用域并在用户会话中实现您自己的虚拟对话。实际上,在 conversation 作用域出现之前,很多开发人员正是用这种做法来保持其应用程序内的多请求用例的状态。CDI 让您可以避免手动记账(bookkeeping)。
最后,注意到我使用了 CDI 注入来将一个对话注入到这个受管 bean,所以我可以用编程的方法来启动和终止一次对话。资源注入让我可以专注于进行对象本身的事情,而不是忙于创建对象并控制其生命周期的乏味工作。
结束语
在本文中,我用很少的代码阐释了很多重要概念 — Ajax 向导、模板、依赖注入、conversation 作用域。借助于 JSF 2 和 CDI,您就可以通过最少的努力和最大程度的灵活性和可重用性实现健壮的可重用 web 应用程序。
JSF 2 简介 系列在这个夏季将告一段落。我在秋天还会带来更多内容,继续帮您增强您的 JSF 技巧。
本文配套源码
更多精彩
赞助商链接