JSF 2简介:JSF领导
副标题#e#
Java™ Enterprise Edition (Java EE) 6 包括了许多像 JSF 2 这样成果强大的技能。Contexts and Dependency Injection(CDI)是这些技能中的一个,它在很洪流平上尺度化了在其他框架酝酿多年的一些观念。
在本文中,我向您展示如何综合利用 JSF 2 和 CDI 来实现一个在线小考试领导。CDI 为我带来了依赖注入、producer 要领和一个 conversation 浸染域。我利用了这三者来实现一个领导,您可以轻松地将其用于任何一个多选的在线测试。
本系列并非只存眷于 CDI。我将先容如何:
利用 facelets 模板来最小化代码并最大化重用
Ajax 化领导以便得到更为顺畅的用户体验
利用 CDI 的依赖注入来简化代码
实现并利用 CDI producer 要领来在视图中无缝地利用 beans
操作 CDI 的 conversation 浸染域来实现多请求用户用例
本文中的全部示例源代码均可下载获得。请拜见 运行示例代码 侧栏得到下载链接以及指向陈设指导的一个指针。
考试领导
图 1 显示了运行中的这个考试领导:
图 1. 考试领导
最初,这个应用措施只包括一个可开启领导的单独链接:<h:commandLink value="#{msgs.startWizard}" action="#{wizard.start}"/>。这个链接的文本(Start the wizard)来自一个属性文件并由该链接值内的 msgs.startWizard 表达式代表。国际化是 JSF 101 circa 2004,所以我在这里就不再过多涉及这些细节了。只需留意到整个应用措施都是当地化了的就足够了,而且所有字符串均从 messages.properties 文件拉出。
Start the wizard 链接会将用户带到这个考试领导页面,在这里,用户会被提问,一次一个问题,如 图 1 底部的两个图片所示。我通过一些简朴的 Ajax 和一个处事器端 bean 节制此领导按钮的启用状态,我在本文的 Ajax 部门将向您详示。
图 2 显示了最后的一个问题,后跟用户谜底的一个总结。当用户处于最后一个问题时,只有 Finish 按钮是启用的;单击该按钮会将用户带到总结页面。
图 2. 总结页面
#p#副标题#e#
相识了这个考试领导如何事情后,我将向您展示它是如何实现的。
考试应用措施
这个考试应用措施的文件如图 3 所示:
图 3. 此应用措施的文件
我用一个 JSF 2 模板(/templates/wizardTemplate.xhtml)实现此考试领导,领导视图(/quizWizard/wizard.xhtml)利用了这个模板。
除了上述模板和视图外,我尚有针对领导的每个构成块的 facelets — 全部处于 quizWizard 目次:
头部(/quizWizard/heading.xhtml)
问题(/quizWizard/question.xhtml)
单选按钮(quizWizard/choices.xhtml)
Next、Previous 以及 Finish 按钮(quizWizard/controls.xhtml)
index.xhtml facelet 用 Start the wizard 链接启动这个应用措施,而 done.xhtml facelet 则显示了问题和谜底的总结。
对付客户机,就先容这么多。在处事器上,应用措施有三个 bean,我们接下来将接头个中的两个。
此应用措施的两个 question bean
Question bean,如清单 1 所示,实际上是一个问题、一组谜底选项以及一个谜底:
清单 1. Question bean
package com.clarity;
import java.io.Serializable;
public class Question implements Serializable {
private static final long serialVersionUID = 1284490087332362658L;
private String question, answer;
private String[] choices;
private boolean answered = false; // next button is enabled when answered is true
public Question(String question, String[] choices) {
this.question = question;
this.choices = choices;
}
public void setAnswer(String answer) {
this.answer = answer;
answered = true;
}
public String getAnswer() { return answer; }
public String getQuestion() { return question; }
public String[] getChoices() { return choices; }
public boolean isAnswered() { return answered; }
public void setAnswered(boolean answered) { this.answered = answered; }
}
此应用措施在 Questions 类内还包括了一个问题数组,如清单 2 所示:
清单 2. Questions bean
#p#分页标题#e#
package com.clarity;
import java.io.Serializable;
import com.corejsf.util.Messages;
public class Questions implements Serializable {
private static final long serialVersionUID = -7148843668107920897L;
private String question;
private Question[] questions = {
new Question(
Messages.getString("com.clarity.messages", "expandQuestion", null),
new String[] {
Messages.getString("com.clarity.messages", "hydrogen", null),
Messages.getString("com.clarity.messages", "helium", null),
Messages.getString("com.clarity.messages", "water", null),
Messages.getString("com.clarity.messages", "asphalt", null)
}),
new Question(
Messages.getString("com.clarity.messages", "waterSGQuestion", null),
new String[] {
Messages.getString("com.clarity.messages", "onedotoh", null),
Messages.getString("com.clarity.messages", "twodotoh", null),
Messages.getString("com.clarity.messages", "onehundred", null),
Messages.getString("com.clarity.messages", "onethousand", null)
}),
new Question(
Messages.getString("com.clarity.messages", "numThermoLawsQuestion", null),
new String[] {
Messages.getString("com.clarity.messages", "one", null),
Messages.getString("com.clarity.messages", "three", null),
Messages.getString("com.clarity.messages", "five", null),
Messages.getString("com.clarity.messages", "ten", null)
}),
new Question(
Messages.getString("com.clarity.messages", "closestSunQuestion", null),
new String[] {
Messages.getString("com.clarity.messages", "venus", null),
Messages.getString("com.clarity.messages", "mercury", null),
Messages.getString("com.clarity.messages", "mars", null),
Messages.getString("com.clarity.messages", "earth", null)
})
};
public int size() { return questions.length; }
public String getQuestion() { return question; }
public void setQuestion(String question) { this.question = question; }
public Question[] getQuestions() { return questions; }
}
清单 1 和 清单 2 均没有什么出格之处 — 它们只是提供应我处事器上的一列问题 — 不外个中有一点值得一提,即我借助编程的方法用 helper 要领从一个资源包拉出字符串。您可以通过 下载此代码 来相识这个要领是如何事情的,而在 Core JavaServer Faces 则可以更深入地阅读到相关信息。
以上就是对此应用措施的 bean 的全部先容了,只有一点需要增补,即 Wizard bean,它充当了此领导的节制器 。它内里的代码是此应用措施内惟一真正有趣的 Java 代码。我在 CDI: Dependency injection and conversations 部门,还会接头这个 Wizard bean。
您对应用措施内的文件和这些 question bean 有所相识后,我接下来将向您显示我是如何实现这个领导的视图的。
模板和视图
对付大大都的领导,您可以安心地归纳出领导的布局,如图 4 所示:
图 4. 领导的布局
清单 3 显示了封装该布局的一个模板:
清单 3. 领导模板 (templates/wizardTemplate.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>
<ui:insert name="windowTitle">
#{msgs.windowTitle}
</ui:insert>
</title>
</h:head>
<h:body>
<h:outputStylesheet library="css" name="styles.css" target="head"/>
<ui:insert name="heading"/>
<div class="wizardPanel">
<div class="subheading">
<ui:insert name="subheading"/>
</div>
<div class="work">
<ui:insert name="work"/>
</div>
<div class="controls">
<ui:insert name="controls"/>
</div>
</div>
</h:body>
</html>
这个考试领导的实现则如清单 4 所示:
清单 4. 领导 facelet (quizWizard/wizard.xhtml)
#p#分页标题#e#
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/wizardTemplate.xhtml">
<ui:define name="heading">
<ui:include src="heading.xhtml"/>
</ui:define>
<ui:define name="subheading">
<ui:include src="question.xhtml"/>
</ui:define>
<ui:define name="work">
<ui:include src="choices.xhtml"/>
</ui:define>
<ui:define name="controls">
<ui:include src="controls.xhtml"/>
</ui:define>
</ui:composition>
模板都相当简朴。它们插入由视图界说的页面的某些部门。在本例中,清单 3 内所示的模板插入的是由 清单 4 内的视图所界说的 heading、subheading、work 和 controls 节。在模板内封装视图的常见特性会让建设新视图更为轻便 — 在本例中,就是新范例的领导。
清单 5 显示了这个考试领导的 heading 节:
清单 5. heading (quizWizard/heading.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<div class="heading">
#{msgs.quizTitle}
</div>
</ui:composition>
清单 6 显示了 subheading 节:
清单 6. subheading (quizWizard/question.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.or g/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:panelGrid columns="1" id="question">
#{wizard.cursor+1}. #{questions[wizard.cursor].question}?
</h:panelGrid>
</ui:composition>
视图抽象
模板让您可以或许封装视图的一些共有特性,让您得以重点存眷于视图之间的变革。好比,这个领导模板就提供了窗口标题、样式表以及 — 通过 <div> 元素 — 每个视图的整体布局。正是由于对共有特性的封装,您才可以通过简朴地插入页面的各个部门来轻松实现新的视图。
这个领导模板界说了视图的布局,却没有界说外观。外观被进一步封装在 CSS 内,为您提供了另一个条理的修改视图的根基模板的自由。
清单 5 内的 heading 显示了这次考试的标题,在本例中标题是 Science Quiz,而 清单 6 内的 subheading 则显示了问题。清单 6 内引用的 wizard.cursor 是一个指向当前问题的游标(假如您愿意,也可称之为索引)。该游标的基数为零,所以 #{wizard.cursor+1} 显示的将是题号,而 #{questions[wizard.cursor].question} 显示的是问题。
#p#分页标题#e#
完成了这些需要提前举办的筹备(好比处事器端 bean 和模板)后,我接下来将向您展示一些真正有趣的对象了:此领导的 Ajax 是如何实现的,以及此领导是如何利用 CDI 的。先从 Ajax 开始。
Ajax
此考试领导内的所有用户交互城市导致 Ajax 挪用,当这些挪用返回时,只泛起页面的适当部门。Ajax 挪用所做的一件工作就是节制此领导按钮的启用状态。图 5 显示了在第一个问题和第二个问题期间,此领导按钮的启用状态:
图 5. 考试领导的按钮
这个领导的 Ajax 被整齐地封装在两个 facelet 文件内。清单 7 显示了 choices.xhtml:
清单 7. 选项
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:form id="choices">
<h:panelGrid columns="2">
<h:selectOneRadio value="#{questions[wizard.cursor].answer}"
layout="pageDirection">
<f:selectItems value="#{questions[wizard.cursor].choices}"/>
<f:ajax render=":buttons"/>
</h:selectOneRadio>
</h:panelGrid>
</h:form>
</ui:composition>
当用户选择一个单选按钮时,JSF 就会向处事器举办一次 Ajax 挪用并会在一个 backing-bean 属性内记录这次单选按钮的选择(问题的谜底)。当挪用返回时,JSF 就会更新领导的按钮。
清单 8 显示了 controls.xhtml:
清单 8. 控件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:form id="buttons">
<h:panelGrid columns="4" styleClass="wizardControls">
<f:ajax render=":question :choices buttons">
<h:commandButton id="next"
styleClass="wizardButton"
value="#{msgs.nextButtonText}"
disabled="#{not wizard.nextButtonEnabled}"/>
actionListener="#{wizard.nextButtonClicked}"/>
<h:commandButton id="previous"
styleClass="wizardButton"
value="#{msgs.previousButtonText}"
disabled="#{not wizard.previousButtonEnabled}"
actionListener="#{wizard.previousButtonClicked}"/>
</f:ajax>
<h:commandButton id="finish"
styleClass="wizardButton"
value="#{msgs.finishButtonText}"
disabled="#{not wizard.finishButtonEnabled}"
action="#{wizard.end}"/>
</h:panelGrid>
</h:form>
</ui:composition>
当用户单击 Next 或 Previous 按钮时,JSF 就会向处事器举办一次 Ajax 挪用,而且当此 Ajax 挪用返回时,JSF 就会更新问题、问题的选择(单选按钮)以及按钮自己。
Finish 按钮不是一个 Ajax 按钮,因为单击它会导航到 done 页面。
请留意清单 7 和 8 中对 wizard bean 有许多引用。该 bean 实际上是这个考试领导的一个节制器。我在本文竣事之前来先容一下这个 bean。
CDI: Dependency injection and conversations
#p#分页标题#e#
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 浸染域,所以它的生命期会跟着对话的竣事而竣事。
#p#分页标题#e#
虽然,您可以避开 conversation 浸染域并在用户会话中实现您本身的虚拟对话。实际上,在 conversation 浸染域呈现之前,许多开拓人员正是用这种做法来保持其应用措施内的多请求用例的状态。CDI 让您可以制止手动记账(bookkeeping)。
最后,留意到我利用了 CDI 注入来将一个对话注入到这个受管 bean,所以我可以用编程的要领来启动和终止一次对话。资源注入让我可以专注于举办工具自己的工作,而不是忙于建设工具并节制其生命周期的乏味事情。
竣事语
在本文中,我用很少的代码阐释了许多重要观念 — Ajax 领导、模板、依赖注入、conversation 浸染域。借助于 JSF 2 和 CDI,您就可以通过最少的尽力和最洪流平的机动性和可重用性实现结实的可重用 web 应用措施。
JSF 2 简介 系列在这个夏季将告一段落。我在秋天还会带来更多内容,继承帮您加强您的 JSF 能力。
本文配套源码