上一节,我们谈论了JSF EL表达式,及如何使用它们来引用位于应用的各种范围之内的对象。在传统的Java Web应用中,那些对象可能是使用 <jsp:usebean> 标签或者在 Java 代码中创建的。而在JSF应用中,可以在应用配置文件中配置和初始化bean。支持这种特征的机制,称为受管bean创建工具,它很简单:无论何时引用bean,如果该bean不存在,则创建一个,并初始化它,然后将其保存在合适的应用范围内(如果bean已存在,则直接返回之)。
被配置来使用受管bean创建工具的bean称为受管bean。当工具创建针对你设计的页面的后台bean时,它们通常会为你在配置文件中配置那些bean。但也可以对应用中要使用的其他bean使用这个特征,包括领域对象,如User,或者像RegistrationWizard 或者 ShoppingCartBean之类的模型对象。
使用受管bean创建工具允许你:
l 在中心位置(应用配置文件)中声明所有的bean,并初始化其所有的属性;
l 控制保存bean的范围(应用,会话或者请求);
l 在不改变代码的情况下,改变bean的类或者初始值(仅需要在配置文件中进行必要的修改);
l 使用值绑定表达式初始化bean属性。这样做有很多令人激动的优点,比如将后台bean关联到业务和状态管理对象,或者对子对象进行初始化;
l 使用常规的JSF EL表达式访问受管bean。
通常后端开发人员将负责初始受管bean配置。但是前端开发人员和系统管理员可能会负责随后的修改。
注解 你不能使用JSTL或者JSP 2.0表达式语言引用受管bean,除非该bean已被受管bean创建工具或者其他手段(比如Java代码或者JSP <jsp:usebean>标签)创建、初始化,并保存在某个应用范围之中。为此,通常,尽可能使用JSF EL表达式更安全。
受管bean 可以通过 JSF 配置文件中的 <managed-bean> XML元素进行配置。因为在JSF配置文件中配置它们,你可以手工编辑它们也可以使用相关的编辑工具。关于应用配置见3.4.1节,其中还有相关工具的屏幕截图。即使有工具能够将你从编辑 XML中解放出来,你也应该了解这一节,以便理解受管bean是如何工作的。
所以,如果有个org.jia.examples.UserBean类,我们可以在配置文件中使用下面的代码片断来配置它:
这段代码在会话范围中以关键字"user"配置了org.jia.examples.UserBean类的一个实例。它也将其firstName属性初始化为"Mark",将其lastName属性初始化为"Pippins"。
这样,我们就可以从 UI 组件中使用常规 JSF EL 表达式来访问这个bean:
这样就声明了一个HtmlOutputText组件,其值包括一个EL表达式,而该表达式则引用了 user.firstName和user.lastName属性。当这个组件被初始化时,受管bean创建工具将实例化新的UserBean类的实例,设置firstName属性为"Mark",并且设置lastName 属性为 "Pippins",然后将对象以关键字User保存储在应用的会话范围中。如果一个组件在对象创建后引用它,该对象就不需要重新创建(假定这之间没有被删除)。
上面声明的HtmlOutputText 组件将显示字符串 “Hello Mark Pippins!”。当然这并不是什么多么令人兴奋的功能——因为一般来说你并不会初始化user对象的firstName和lastName属性,但你却获得一个好主意。
|
通常,使用受管bean创建工具配置那些不需要在应用启动时创建和初始化的bean,是个好主意。而那些具有很长的潜在初始化时间的对象,比如需要建立数据库连接的数据访问对象,则最好在应用启动时创建(也许还可以使用ServletContextListener,见第12章的例子)。这是因为工具在对象首次被引用时才创建它,这样在用户与具有较长初始化时间的对象交互时,就可能出现不希望的延迟。
到这里,我们已经讨论了受管bean的基础知识。下面来详细讨论之。
3.3.1 声明受管bean
任何具有无参数构造器的 Java 类都可以被声明为受管bean。你需要知道的只是想要用来作为标识符、类名以及你希望使用的范围的名字就可以了。标识符是实际用在JSF EL 表达式中的名字,如"user"或者"ShoppingCartBean"。类名是Java类名称的全限定名,如com.foo.bar.ShoppingCart。范围则是任何有效的JSF Web应用范围:应用、会话或者请求。还有第四个范围选项:none,就是告诉受管bean创建工具不要将bean放入任何范围中。这在多个 bean相互关联时特别有用,下面会讨论这种情形。
这三个信息分别对应JSF配置文件中的<managed-bean-name>,<managed-bean-class>,和 <managed-bean-scope> 元素。它们都必须位于 <managed-bean>元素中。如图3-6所示。
图3-6也显示了三个可选元素:<description>,<displayname>和<icon>元素。通常只需使用<description>元素来帮助其他开发人员理解特定的受管bean的目的;其他属性则主要为工具所用。
图3-6 要配置受管bean,要使用<managed-bean>元素。这个元素有三个可选的子元素:<description>、<display-name>以及<icon>。它还需要求一个<managedbean-name>元素、一个<managedbean-class>元素和一个<managed-bean-scope>元素
当然,正如上一节的例子所示,也可以配置 bean的属性(而且还可以设置很多内容)。但是我们先来看一个简单例子:
这段代码告诉受管bean创建工具在第一次访问org.jia.ptrack.web.AuthenticationBean类时,创建一个该类的新实例,并且将其以名字authenticationBean存放在会话范围中。只要使用同一个会话,就使用同一个AuthenticationBean实例。这个实例也可以通过JSF EL表达式"#{authenticationBean}",或者更加直接地,通过表达式"#{sessionScope.authentica- tionBean}"对其进行引用。
|
有些情况下,你需要做的只是配置对象自身,不需要初始化任何属性。当然,如果确实需要的话,你也可以初始化它们。
1.初始化简单对象
你可以初始化任何暴露为JavaBean属性的受管bean变量(附录C有关于JavaBean的详细信息)。其实只有读/写属性(即同时具有获取和设置属性)能够用于受管bean创建工具。任何未被显式初始化的属性都会保持其默认值。
至少,你需要指定属性的name及其value。而值可以为null,字面字符串或者值绑定表达式。每个属性都由 <managed-property> 元素(它嵌套在 <managed-bean> 元素中)来初始化。你可以用 <propertyname> 元素来指定属性的名称。而值则可以通过 <value> 元素或者 <null-value> 元素来设置。这些元素如图3-7所示。
图中也显示了可选的工具元素<description>、<displayname>和<icon>。通常,你只需要使用 <description> 元素即可。还有一个可选元素——<property-class>,它用来指定属性的Java 类或者基本类型。受管bean创建工具不需要这些元素,因为它可以根据属性本身推定属性的类型。然而,某些高级的 JSF 实现可能在某些情况下需要它们(比如自动产生受管bean时)。如果已使用了<property-class>元素,记住就不能再使用<null-value>元素,除非<property-class>是对象而不是基本类型(如int、boolean等)。
图3-7 可以通过<managed-property>元素初始化受管bean属性(每个<managed-bean>元素都可以有0或多个<managed-property>元素)。其可选属性是<description>、<display-name>、<icon>和<property-class>。必需的元素是<property-name>与<value>或<null-value>
下面的例子中,配置了UserBean对象并且设置了它的一些属性:
这段代码创建了一个新的org.jia.examples.UserBean实例brokeUser,并将之保存在请求范围中。这个例子类似于分别初始化user对象的firstName和lastName属性为字面字符串值"Joe"和"Broke"。我们还初始化了balance属性,这是个int类型,设置为0。最后,设置了favoriteAnimal属性,这是个String,通过<null-value>元素设置为null。注意我们不能将balance属性初始化为null,因为它是基本类型。要访问Joe Broke的账户余额,我们可以使用表达式"#{brokeUser.balance}"。
我们使用值绑定表达式将这些属性关联到其他对象,而不使用静态值。很快就会讲到它了,但是我们首先来看如何初始化类型为List,数组和Map的属性。
2.初始化列表和数组属性
如果有个数组或者List类型的属性,你可以用默认值来初始化它。如果属性被设置为null,创建工具将创建List或数组,并初始化它。如果不是null,工具将只是简单地添加你定义的值到集合中。这样你指定了多个值,而不是指定单一的属性值。你可以在<list-entries>元素中嵌套<value>或<null-value>元素来进行配置,而<list-entries>元素是<managed-property>元素的子元素。如图3-8所示。
图3-8 要初始化List或数组类型的受管bean属性,可以使用<list-entries>元素并在其中包含0个或者多个<value>或者<null-value>元素。可选地,你还可以通过<value-class>元素对所有元素指定Java类
警告 即使指定了<property class>元素,如果属性是null,JSF也总是以一个新创建的 ArrayList来实例化它。然而如果对象已经创建了一个不同的类型对象,JSF 将不会以 ArrayList来替换它。
假定我们的UserBean类也有个favoriteSites属性,这是表达其喜爱站点的字符串列表。如果想要为favoriteSites属性提供默认的值列表,可以这样定义:
你会看到,这里将多个<value>元素嵌套在<list-entries>元素中。每个<value>对应一个在设置属性之前添加到列表中的元素。如果favoriteSites属性还未被初始化,则列表中的第一个项目将是“http://www.jsfcentral.com”。这个条目可以通过JSF EL表达式"#{user.favorite- Sites[0]}"来访问。我们也可以使用 HtmlDataTable 组件来将整个列表显示在表格中,该组件显示表格化的数据:
这样就会对列表中的每一个条目显示一个单行单列的表格,在浏览器中可能会像这样:
然而,如果favoriteSites属性已经初始化(在构造器中)为单一值,如“http://www.yahoo.com”,则列表可能是:
而表达式 "#{user.favoriteSites[0]}"引用的却是“http://www.yahoo.com”。
前一例子中,所有值都保存为String对象,这是默认设置。如果想要将所有的值都转换为特定类型,你可以使用<value-class>属性。下面是所有的值都是整数的列表属性:
所有这些值在保存到列表之前都被转换为java.lang.Integer对象。除此之外,初始化过程的处理将与前一例中的方式一样。
在这些例子中,我们假定属性是List对象。它们也可以是数组——配置和行为是一样的,除了工具将创建一个数组而不是一个ArrayList。事实上,你可以在Java 代码中改变属性的实际类型,只要它是List和数组其中之一,受管bean创建工具并不在乎(然而工具不会为你调整数组的大小,所以必须小心考虑这些改变是否会影响到你的应用)。
这就是有关配置List或者数组属性的全部内容。幸运的是,配置Map属性与此很相似。
3.初始化Map属性
你也可以通过受管bean创建工具来初始化Map属性。如果属性在对象被创建时为null,JSF 将创建一个新的HashMap并组装它。否则,它将在现有的Map中添加项目。初始化Map属性需要指定一个或多个map条目。每个map条目都有一个关键字和一个值。在JSF配置文件中,可以指定<map-entries>元素(相当于<list-entries>元素)以及其子<map-entry>元素。每个<map-entry>元素都具有<key>和<value>(或者<null-value>)子元素。这种结构如图3-9所示。
假定上一节的例子中的喜好站点列表是个名为favoriteSitesMap的Map属性,其中每个条目的关键字是站点的名称,而值则是站点的实际URL。我们可以这样定义:
图3-9 要初始化Map类型的受管bean属性,可以使用<map-entries>元素并在其中设置一个或者多个<map-entry>元素。每个<map-entry>都可以有一个<value>或<null-value>元素。可选地,你还可以通过<value-class>元素对所有值指定Java类
我们未在<managed-property> 元素之下直接使用<list-entries> 或者<value> 元素,而是使用<map-entries>元素和<map-entry> 子元素来定义Map属性中的所有条目。每个 <map-entry>元素表达将要添加到Map中的键/值(key/value)对。如果UserBean未在其构造器中初始化它的属性,工具将创建新的HashMap,添加map条目,然后设置属性。
对于这个受管bean配置,我们可以使用JSF表达式 "#{user.favoriteSitesMap ['java.net']}"访问 java.net的URL。我们也可以用HtmlOutputText 组件显示整个Map:
这样可以显示其下的Map的toString方法的结果,结果可能像这样:
这并不是很正式的输出,但是至少我们看到,这正是在配置文件中指定的favoriteSites- Map属性的内容。如果UserBean已经初始化了这些属性,这些值将被添加到Map中的已有条目集中。
前一个例子中,键和值都是String。这在很多情况下是很好的,但是有时需要特殊的类型,比如关键字是整数,或者值是日期类型。为支持这些状况,你可以告诉工具分别使用<key-class> 和 <value-class> 元素将关键字和值转换为特定的类型。下面是来自前一节的例子中的favoriteNumbers属性,并且实现为一个Map:
这里,我们指定<key-class>元素来确保,所有的关键字转换为 java.lang.Integer 对象。所有不能正确转换的值都将导致错误。这里,值描述了关键字数字的重要性。如果重复了关键字,工具将覆盖前一个值。注意这里我们使用<null-value> 元素来指示null值(null关键字是不允许的)。
3.3.2 将List和Map声明为受管bean
之前,我们已经谈论了将JavaBean定义为受管bean。你也可以将任何List或者Map声明为受管bean(不支持将数组作为独立的受管bean)。你必须要做的就是,指定一个具体的类型(如 ArrayList或者HashMap),并且指定list或者map条目而不是单独的属性。List元素示于图3-10中。
下面是我们已经很讨厌了的favoriteSites列表,声明为单独的受管bean而不是UserBean对象的属性:
图3-10 要将List声明为受管bean,你必须指定<managed-bean-class>为一个具体的List实现,并且使用<list-entries>元素作为<managed-bean>元素的子元素。每个<list-entries>元素都可以有一个或者多个<value>(或<null-value>)元素以及一个<value-class>元素
注意我们使用<managed-bean-class>元素指定一个具体的List实现,即java.util. ArrayList。另外,<list-entries> 元素和子元素嵌套在<managed-bean>元素中,而不是在<managed-property>元素中(关于初始化列表属性的一般信息,参考3.3.1节中初始化列表属性的内容)。
针对Map的配置元素如图3-11。
图3-11 要将Map声明为受管bean,你可以在<managed-bean-class>元素中的值中指定一个具体的Map类,并且使用<map-entries>元素作为<managed-bean>元素的子元素。每个<map-entries>元素可以有一个或者多个<map-entry>元素,每个都具有一个<key>和一个<value>(或者<null-value>)元素,<map-entries> 元素也可以有可选的<keyclass>和<value-class>元素
下面是UserBean的直接配置为受管bean的favoriteSitesMap属性:
可以看到,通过<managed-bean-class>属性,我们指定了一个具体的Map实现HashMap。<map-entries>元素被定义为<managed-bean>元素的子元素,并且这里未使用<managed-property>元素(关于初始化Map的一般信息,参看3.3.3节)。
提示 不要忘了指定一个具体的List或者Map实现(比如ArrayList、Vector、HashMap和TreeMap)。JSF 不能直接实例化List或者Map,因为它们是接口。
所有的受管bean示例都包括静态值——字符串或者整数。然而,受管bean真正的强大之处在于可以通过值绑定表达式来初始化属性。
3.3.3 通过值绑定表达式设置值
当你指定受管bean值时——不管是属性、列表值,还是Map值,你都可以使用值绑定表达式。因为值绑定表达式是引用JavaBean属性或者集合元素的JSF EL表达式,它们也适合于初始化受管bean属性——它们允许你将对象彼此联系起来。
当然,也可以在 Java代码中彼此联系对象。但是如果使用工具,你可以随时改变这些关系而不用重新编译应用。另外,可以发现使用工具比编写Java代码简单,这全赖于你自己的安排。
代码清单3-5展示了三个受管bean的配置:名为defaultFavoriteSites的ArrayList,名为newUser的UserBean以及名为exampleForm的TestForm。newUser的favoriteSites属性保存在会话中,而它是通过值绑定表达式来与defaultFavoriteSites关联的。因为defaultFavoriteSites是声明在范围none中的,所以它不保存在任何地方,其创建的目的就是针对设置newUser的favoriteSites属性的。
exampleForm保存在请求范围中,并且有个user属性,通过表达式引用newUser。所以这个声明创建了一组如图3-12所示的相关对象:exampleForm引用newUser,而后者则引用defaultFavoriteSites。
代码清单3-5 三个通过JSF EL表达式互相引用的受管bean
在这个简单例子中,表达式全部引用了其他受管bean。这不是实际需求——你可以使用任何有效的值绑定表达式,即意味着可以将属性关联到衍生自隐含对象的值(比如,初始化参数、请求参数、会话值,等等)以及混合文本和执行算法的表达式(见第2章,关于值绑定表达式和JSF表达式语言的详细信息)。
在这个例子中,对每个对象都指明了范围,是因为受管bean并不能引用生命周期比自己还要短的对象。在例子中,exampleForm引用了newUser。这是可以的,因为 newUser 是保存在会话中,而exampleForm保存在请求中。换言之,exampleForm是可以引用生命周期比它自己长的newUser对象的。当请求完成,exampleForm被删除,newUser将继续存在。然而,newUser却不能引用exampleForm,因为exampleForm的生命周期要短些;在newUser被删除前,exampleForm可能已经被删除很久了。表3-7解释了何时可以引用不同范围内的对象。
图3-12 在代码清单3-5中声明的受管bean形成了一个不同范围中相关对象的图
表3-7 可允许的受管bean之间的关系
存储在下面范围中的对象 | 可以引用下面范围中的对象 |
none | none |
application | none,application |
session | none,application,session |
request | none,application,session,request |
注解 受管bean创建工具不支持循环引用。换言之,你不能使对象A引用对象B,而又让对象B引用对象A。
受管bean是JSF的一个极其强大的特征。通常开发工具都使用这个创建工具来自动连接后台bean。但是如果更进一步看,你可以在中心位置配置应用中的大部分对象。你甚至可以消除,对在Java代码中连接特定范围中的对象的需要。关于使用受管bean创建工具的更多例子,详见第13章。
受管bean是JSF的一个关键特征,它通过消除Java代码中的创建和配置对象的需求来简化开发。JSF的导航系统也可减少需要编写的代码量