本文是对Sun JSF RI中的样例程序cardemo的讲解,如有转载,请声明出处!
样例程序:
下载Sun JSF RI:
https://javaserverfaces.dev.java.net/files/documents/1866/78307/jsf-1_2_07.zip
将其解压缩,在子目录中包含了samples目录,samples目录中包含了cardemo应用程序。
Sun JSF RI中附带的cardemo样例程序的主要功能是提供了汽车的购买流程。主要包含以下子功能,根据链接、按钮或者地图选择区域,进入不同区域语言的汽车一览表,在汽车一览表中,包含了汽车的标题、描述等信息。选择某一种类的汽车,可以进入到汽车详细说明页面,对汽车的各种指标参数进行了选择,可以自定义要选择的指标参数来计算汽车的价格。选择汽车后,可以进入个人用户注册和购买页面,最后完成购买。
图片流程如下:
主页面:

可以通过地图、链接和按钮进入不同语言的汽车列表页面。选择English链接或者English按钮,进入汽车一览表:

上图中的四种车型分别为Jalopy(老爷车)、Luxury(豪华汽车)、Roadster(赛车)和SUV(越野车)。通过点击More按钮,进入到汽车的详细介绍,选择Jalopy汽车:

在汽车详细信息的初始页面,提供了最基本的汽车配件,即基础价格,可以通过点击按钮来选择不同的种类,例如自定义、标准、高性能、豪华,从而价格也不同。
在自定义情况下,可以通过选择不同的选项,然后重新计算价钱:

在选择购买后,会进入购买确认页面:

在用户信息页面填写购买用户的信息:

如果在上图中注册的内容有问题的话,会提示错误信息:

输入正确的Credit Card Number后,可以完成购买:

应用程序结构讲解:
本样例程序涉及到的知识有本地化、转换器、验证器、页面跳转、Bean等内容,下面一一介绍。
首先介绍Java代码:
CarBean类
这个bean封装了一个car模型,包括car的价格和设备选项。系统允许用户自定义这个bean的属性,通过CarCustomizer类来协助完成。
数据访问
这是系统中唯一个进行数据的复杂持久化的bean,在现有的实现中,持久化数据存储在ResourceBunder实例中。
一共使用了三种类型的ResourceBundle文件:
<ModelName>:这个包含了模型的本地化信息。有多个文件来支持不同的locale,例如Jalopy_de.properties
<Common_properties>:包含了对所有模型都适用的本地化信息。
<ModelName_options>:包含了模型的非本地化信息,包括非本地化选项。对于所有的locales,只有一个文件,例如Jalopy_options.properties
所有的文件都遵循以下的规范:
key
key_componentType
key_valueType
key是car的一个属性,例如basePrice;keyComponentType是UIComponent子类的组件类型,用于在页面中表示这个属性,例如SelectManyMenu;key_valueType是UIComponent组件的值的类型,例如java.lang.Integer,对于所有的非String值类型。
当bean被实例化的时候,加载上述的属性文件,并迭代每个属性文件中的key。对于每个key,查看componentType,然后使用Application创建该类型的组件。然后将UIComponent实例存储在componentsMap中,并以key作为它的key。查找该key的valueType,对于非java.lang.String类型,使用Application创建Converter实例,如果成功生成,那么使用它将key对应的value转换成合适的类型,并作为UIComponent实例的value。
CarBean共有四个属性,分别为:
private ResourceBundle resources = null;
/** Price data */
private ResourceBundle priceData = null;
/**
* Keys: String attribute name, such as engine. Values: UIComponent
* for the attribute
*/
private Map<String, UIComponent> components = null;
/**
* Keys: String attribute name, such as engine. Values: String value
* of the component named by key in our components Map.
*/
private Map<String,Object> attributes = null;
其中resources和priceData分别在初始化的时候进行加载,components和attributes在初始化的时候将components中的UIComponent进行实例化,并进行值得填充。
其中,在属性文件中的格式如下:
title=Duke's Stripped-Down Jalopy
title_componentType=javax.faces.Output
title_valueType=java.lang.String
在加载属性文件后,获取ResourceBundle对象,然后进行值得填充:
private void initComponentsFromProperties(FacesContext context,ResourceBundle data) {
// populate the map
for (Enumeration<String> keys = data.getKeys(); keys.hasMoreElements();) {
String key = keys.nextElement();
if (key == null) {
continue;
}
// skip secondary keys.
if (key.contains("_")) {
continue;
}
String value = data.getString(key);
String componentType = data.getString(key + "_componentType");
String valueType = data.getString(key + "_valueType");
// create the component for this componentType
UIComponent component =
context.getApplication().createComponent(componentType);
populateComponentWithValue(context, component, componentType,
value, valueType);
components.put(key, component);
}
}
在priceData中存储了各种类型的配件的价格,在Custom.properties、Standard.properties、Performance.properties和Deluxe.properties四个文件分别保存了不同的组件的配置情况,然后在OptionPrice.properties中定义个各个组件的价格。
例如:Standard.properties的内容为:
sunroof=true
sunroof_disabled=true
cruisecontrol=true
cruisecontrol_disabled=true
keylessentry=true
keylessentry_disabled=true
securitySystem=false
securitySystem_disabled=true
skirack=true
skirack_disabled=true
towPackage=false
towPackage_disabled=true
gps=false
gps_disabled=true
engine=V4
brake=Disc
suspension=Regular
speaker=4
audio=Standard
transmission=Auto
而OptionPrice.properties的内容如下:
V4=100
V6=200
V8=300
Disc=100
Drum=200
Regular=150
Performance=300
4=100
6=200
Standard=100
Premium=200
Auto=300
Manual=200
sunroof=100
cruisecontrol=150
keylessentry=100
skirack=200
securitySystem=100
skiRack=200
towPackage=200
gps=200
用户在页面组合配件,然后使用配件价格的和加上基础价格(basePrice)作为汽车的总价格(currentPrice)。
也就是说,updatePrice操作应该在重新计算价格的时候被调用。
CarCustomizer类
该类是一个协助类,用于对CarBean进行自定义。该类从属性文件中读取所有的设置。该类初始化时,默认使用Custom.properties作为ResourceBundle。该类有两个成员变量,分别为:
/**
* The options of the CarBean.
*/
private ResourceBundle bundle = null;
/**
* for the display of specified button,selected or not.
*/
private String buttonStyle = null;
该类中的实际的业务方法为public void customizeCar(CarBean toCustomize),用于对一个CarBean进行自定义。它从Custom.properties、Standard.properties等文件中读取属性,然后去Jalopy_options.properties、SUV.properties文件中去比较,然后设置组件的值。
CarStore类
这个类是应用程序的主要Bean。它维护了两个Map对象,一个是以模型名为key,以CarBean为value;另外一个是以package为key,以CarCustomizer为value。其中包含CarBean实例的Map在多个页面中被使用。如下所述:
应用程序中的很多页面使用这个bean作为引用值和引用表达式。
"chooseLocale"页面使用actionListener属性来指向chooseLocaleFromMap和chooseLocaleFromLink方法。
"storeFront"页面使用值绑定表达式来讲四种知名的模型的汽车的信息显
"carDetail"页面使用值绑定表达式将当前选定的模型信息显示。同样适用action属性来提交用户的package选择。
"confirmChoices"页面使用值绑定表达式来将用户当前的选择显示。
该类的属性如下:
/**
* <p>The locales to be selected for each hotspot, keyed by the
* alternate text for that area.</p>
*/
private Map<String, Locale> locales = null;
/** <p>The currently selected car model.</p> */
private String currentModelName = DEFAULT_MODEL;
/**
* <p>The car models we offer.</p>
* <p/>
* <p>Keys: Strings that happen to correspond to the name of the
* packages.</p>
* <p/>
* <p>Values: CarBean instances</p>
*/
private Map<String, CarBean> carModels = null;
/**
* <p>Keys: Strings that happen to correspond to the name of the
* Properties file for the car (without the package prefix).</p>
* <p/>
* <p>Values: CarBeanCustomizer instances</p>
*/
private Map<String, CarCustomizer> carCustomizers = null;
在进入页面后,可以根据不同的区域语言选择进入不同语言的汽车页面,这是通过设置UIViewRoot的locale属性完成的:
public void chooseLocaleFromMap(ActionEvent actionEvent) {
AreaSelectedEvent event = (AreaSelectedEvent) actionEvent;
String current = event.getMapComponent().getCurrent();
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(locales.get(current));
resetMaps();
}
public void chooseLocaleFromLink(ActionEvent event) {
String current = event.getComponent().getId();
&