Apache MyFaces项目包含了几个关于JavaServer技术的子项目如果需要了解更多关于JavaServer Faces的知识请参考MyFaces对于JSF的介绍
Apache MyFaces项目提供了以下的功能
JavaServer Faces的实现(MyFaces API和MyFaces Impl模块)
用于构建JSF相关的web应用程序的组件库例如MyFaces TomahawkMyFaces TrinidadMyFaces Tobago
JavaServer Faces扩展包例如MyFaces Orchestra
与其他技术和标准的集成模块(用于开发移动客户端在本文和后续文章中对这部分功能不进行介绍)
可以在如下位置下载和MyFaces Impl及其各个子项目的发布包及样例程序MyFaces API
在JSF的子项目中Core子项目视JSF规范的一个实现其他的项目实现了相关的规范例如Portlet Bridge或者为其他的JSF实现添加了扩展注意不仅仅是MyFaces Core同时MyFaces的扩展例如Tomahawk可以和任意的JSF实现协同工作例如Sun Reference Inplementation
MyFaces Corex可以在Tomcat 下工作但是MyFaces Core x要求 Tomcat 环境
MyFaces项目不依赖于其他的项目它是一个独立的程序
JSF FAQ
javaxfacesSTATE_SAVING_METHOD的值为client和server时的区别?
简而言之server端状态降将UI组件的持有的信息在HTTP Session中保存而client状态将UI组件持有的信息保存在返回给用户的页面的隐藏域(hidden field)
client端状态对于非常大数量的用户来说比较实用因为不需要在服务器端保存用户的状态从而节省了内存但是缺点是在网络中为每个请求传送了更多的数据
即使选择的是client端状态任何一个sessionscoped实体仍然存在于http session中这个标记只是影响JSF实现将UI组件持有的数据存放在什么位置
任何实现了StateHolder的组件必须实现saveState(FacesContext)和restoreState(FacesContext Object)方法来帮助JavaServer Faces Implementation来保存和恢复跨越多个请求的组件的状态
为了设置值必须实现saveState方法这个方法在render响应阶段被调用在这期间响应的状态被设置用来处理后需的请求下面是MapComponent组件的该方法的实现
public Object saveState(FacesContext context) {
Object values[] = new Object[];
values[] = supersaveState(context);
values[] = current;
return (values);
}
该方法初始化了一个数组用来持有保存了的状态然后保存所有与MapComponent关联的状态的所有内容
实现StateHolder的组件必须提供restoreState方法该方法用来恢复保存在saveState方法中保存的组件的状态restoreState方法在恢复视图阶段被调用在此期间JavaServer Faces Implementation会检查是否在最后render response阶段保存了状态并判断是否需要恢复以便进行下次的后退下面是MapComponent组件的restoreState方法
public void restoreState(FacesContext context Object state) {
Object values[] = (Object[]) state;
superrestoreState(context values[]);
current = (String) values[];
}
该方法的参数为FacesContext和Object实例表示存储组件状态的数组这个方法将Object数组中保存的数据进行设置
当在自定义的组件类中实现这些方法时确保在部署描述符中指定了需要将这些状态保存在哪里即client还是server如果状态时保存在client整个视图的状态都被提供给页面的一个hidden field
可以通过设置javaxfacesSTATE_SAVING_METHOD上下文参数(contextparameter)
浏览器总是显示一个到上一个页面的链接?
默认情况下JSF在内部使用forward操作来在页面之间导航
所以当一个用户首先访问页面A然后取回一些内容这些内容会被包装在表单中表单以页面A作为提交的地址
当用户接着执行操作提交页面这时JSF收到post请求回复页面A的视图并执行postback处理作为postback处理的一部分页面A的逻辑可能通知JSF框架应该显示页面B
默认情况下JSF会执行一个内部forward到页面B使页面B被取回作为用户提交表单的结果
这时用户在屏幕上看到页面B但是浏览器只知道它将数据发送给了页面A所以浏览器导航栏上显示的URL是页面A的URL虽然显示的内容是页面B不幸的是HTTP/HTML不能使浏览器知道应该显示页面B的URL因此作为JSF的默认行为浏览器的URL经常是实际显示内容的页面的上一页
不仅仅对于用户来说是奇怪的同时加入收藏夹的时候也比较困难注意收藏夹的位置一般来说是没有关系的因为JSF应用程序通常是可交互的并且是状态丰富的
另外一个缺点是在JSF视图中使用关联链接是不安全的(例如样式表的相对路径或者图标)当浏览器提交内容到A并获取数据共页面B使用时在返回页面中的relative links都会被作为相对于它(浏览器)所知道的最后的url所以第一次查看页面B时所有相对链接都是相对于页面A的如果A和页面B不在一个目录中那么就会出现问题
这个问题的一个解决办法就是使用<redirect/>标记来定义导航规则这样会使用http redirect命令来告诉浏览器获取新页面从而取代执行内部的forward 但是这样做效率比较低因为浏览器需要使用第二次请求来获取它的内容而不是在针对初始的post请求在响应时立即获取它的内容另外一个重要的问题就是获取页面B的内容是在一个单独的请求中这样就没有办法从A页面中传递任何的请求范围(requestscoped)数据在这样的目的使用request范围的变量非常方便也是很常用的
Tomahawk sandbox库中包含了s:redirectTracker组件可以临时将requestscoped变量保存在http session中这样使用<redirect/>规则就可以在不丢失request范围变量的情况下使用但是这要求存在http session并且效率低下
MyFaces FAQ
什么是shared项目?
如果myfacescoretomahawktobagotrinidad都是完全独立的项目那么就不需要shared代码项目每个项目都在各自的名称空间内维护代码但是会存在大量的重复代码并且工作量的浪费因为它们都属于myfaces项目所以多个子项目可以共用的代码放在shared项目中以便减少开发和维护的工作量
但是这些子项目又都有各自的独立释放周期并且tomahawk/tobago/trinidad都应该运行在任意的JSF实现上而不仅仅是myfacescore目前使用的解决办法是重命名shared的类的报名代码被重命名成orgapachemyfacesshared_implorgapachemyfacesshared_tomahawk等等这样每个子项目就可以在发布的时候包含所有的支持类而不是一个独立的共享jar文件也不需要关心相同类的定义的沖突对于特定项目的升级不会影响到相同环境下的其他项目
注意最近的MyFaces项目的释放都是用了shared库并在源代码的jar文件中包含了shared项目的源代码所以不需要添加额外的源代码jar文件
如何从MyFaces获取格式良好的HTML输出?
JTidy项目提供了一个ServletFilter可以将响应的消息在输出前进行重新的格式化Mozilla Firefox浏览器提供了一个扩展的View Formatted Source功能
在一些版本的MyFaces中可以通过设置orgapachemyfacesPRETTY_HTML来在webxml文件中启用pretty输出但是这个选项从来都没有被很好的支持因为需要所有的renderer来支持以便其工作可能在以后的MyFaces发布中移除
MyFaces Core和tomahawk发布包中的版本号表示什么?
MyFaces Core使用三个部分来表示版本例如但是这个值和普通的版本号计数是不同的强两个数字表示了JSF规范的版本因为二进制的JSF规范的API没有改变前两位数字相同的发布包被认为是兼容的所有使用JSF指定特征的既存代码会同样可以使用
Tomahawk库也适用相同格式的版本号但是因为JSF 规范是向后兼容的即兼容JSF 规范所有Tomahawk发布的版本中x同样可以工作在JSF 上注意tomahawk释放不保证二进制向后兼容
为什么DataModel不是可序列化的?
DataModel类(在UIData组件中使用)在显示和恢复视图阶段不需要保存任何任何状态因此不需要将它定义为可序列化的
如果需要定义可序列化的managed bean并且它包含一个DataModel类型的成员变量那么将成员变量定义为transient
为什么时间显示不正确?
JSF规范要求默认的date>String转换器使用标准的UTC时区也叫做GMT时区
MyFaces 或者早期的发布并没有遵循JSF规范它们默认的使用了服务器的时区
可以通过显示试用转换器来进行时区的控制例如
<f:convertDateTime timeZone=Antarctica/South_Pole />
或者
<f:convertDateTime timeZone=#{beantimeZone} />
#{beantimeZone}返回字符串id或者TimeZone实例
当然也可以注册自定义的converter来覆盖标准的converter使自定义代码适用于所有的时间到字符串的转换
如何在一个managed bean中访问另外一个managed bean?
有两种方法来实现访问同一个webapp中的其他managed bean
使用依赖注入在faces配置文件中定义managed beansmanaged bean的属性可以被声明成到其他managed bean的引用
<managedbean>
<managedbeanname>neededBean</managedbeanname>
<managedbeanclass>fqntoNeededBean</managedbeanclass>
<managedbeanscope>session</managedbeanscope>
</managedbean>
<managedbean>
<managedbeanname>usingBean</managedbeanname>
<managedbeanclass>fqntoUsingBean</managedbeanclass>
<managedbeanscope>request</managedbeanscope>
<managedproperty>
<propertyname>neededBean</propertyname>
<value>#{neededBean}</value>
</managedproperty>
</managedbean>
限定条件如下
using bean必须的生命周期小于或者等于被引用的needed bean
using bean必须具有setter方法以needed bean作为参数
beans不能管理彼此的依赖
使用查询机制下面的代码可以在MyFaces 中来显示的通过名字查询任意的managed bean
FacesContext facesContext = FacesContextgetCurrentInstance();
NeededBean neededBean
= (NeededBean) facesContextgetApplication()
getVariableResolver()resolveVariable(facesContext neededBean);
在MyFaces 中不使用上述的方法而是推荐使用
ELContext elContext = FacesContextgetCurrentInstance()getELContext();
NeededBean neededBean
= (NeededBean) FacesContextgetCurrentInstance()getApplication()
getELResolver()getValue(elContext null neededBean);
同样可以使用这个代码来计算任意的JSF表达式
FacesContext facesContext = FacesContextgetCurrentInstance();
NeededBean neededBean
= (NeededBean)facesContextgetApplication()
createValueBinding(#{neededBean})getValue(facesContext);
如何得知一个managed bean的属性是否都被设置?
managed bean必须具有一个默认的无参构造器所有的managedproperty声明会调用合适的setter方法但是通常在所有的bean属性被定义后进行一些初始化的操作
Spring提供了postinistialisation回调功能任何实现InitializingBean的bean会调用afterPropertiesSet方法在JSF中没有准确的等价操作但是有一些接近的方式
定义bean的setter方法例如public void setInitialized(boolean state)
将下面的属性作为managed bean的最后一个属性
<managedbean>
<managedproperty>
<propertyname>initialized</propertyname>
<value>true</value>
</managedproperty>
</managedbean>
JSF规范要求managed properties根据它们声明的顺序进行初始化所以setInitialized方法会在所有其他属性被调用后进行设置
PhaseListener为什么会被调用两次?
JSF规范要求任何JSF实现框架在启动时自动加载/WEBINF/facesconfigxml所以没有必要添加如下的context参数
<contextparam>
<paramname>javaxfacesCONFIG_FILES</paramname>
<paramvalue>/WEBINF/facesconfigxml</paramvalue>
</contextparam>
如果在webxml文件中配置了上述信息会迫使JSF实现加载配置文件两次所以注册了每个phase listener两次
dataTables的Action listener和action命令没有被调用?
如果action源(h:commandLinkh:commandButton)没有被提供那么Action Listeners和actions就不会被激活当action源在dataTable上时并且dataTable的value属性指向一个请求范围的数据源那么action源在接下来的请求中就没有被提供例如
<h:dataTable value=#{requestScopedBeandataModelwrappedData} />
<h:column>
<h:commandLink value=click here action=#{backingBeanwillNotFire} />
</h:column>
</h:dataTable>
action源没有被rendered是因为数据源在随后的请求中不存在了(在第一次响应完成后被垃圾回收器进行了回收)
为了解决这个问题使用t:saveState标记或者将request范围的bean放在session范围内
<t:saveState value=#{myRequestScopedBeandataModelwrappedData} />
日历树等不能工作并发生javascript脚本错误?
这是需要配置MyFacesExtensionFilter
一些MyFaces组件不仅仅包含了HTML可能需要额外的支持脚本样式表图片等等这些资源包含在MyFaces的jar文件中Extensions Filter添加所需的代码和URL来提供这些资源给生成的HTML
一些其他的组件例如文件上传需要解析Multipart请求这也是由Extensions Filter来完成的
使用ExtensionFilter有如下的好处
将MyFaces的组件和应用程序良好的隔离
不需要在页面或者webapp中添加MyFaces其他的组件相关的代码或者资源
为MyFaces开发组提供了灵活的更新组件保持透明及向后兼容的功能
为页面开发人员减轻了压力
只加载对使用组件的资源
处理MyFaces资源缓存
可以通过如下方式配置extension filter
在webxml文件中将filter映射到JSF页面例如*jsp以便使filter可以更新页面中的资源链接同时映射filter到/faces/myFaces/ExtensionResources/*路径这样可以处理页面独立的资源例如图片javascript脚本文件以及样式表等等下面是一个配置的例子
<filter>
<filtername>MyFacesExtensionsFilter</filtername>
<filterclass>orgapachemyfaceswebappfilterExtensionsFilter</filterclass>
<initparam>
<paramname>maxFileSize</paramname>
<paramvalue>m</paramvalue>
<description>Set the size limit for uploaded files
Format: bytes
k KB
m MB
g GB
</description>
</initparam>
</filter>
<! extension mapping for adding <script/> <link/> and other resource tags to JSFpages >
<filtermapping>
<filtername>MyFacesExtensionsFilter</filtername>
<! servletname must match the name of your javaxfaceswebappFacesServlet entry >
<servletname>Faces Servlet</servletname>
</filtermapping>
<! extension mapping for serving pageindependent resources (javascript stylesheets images etc) >
<filtermapping>
<filtername>MyFacesExtensionsFilter</filtername>
<urlpattern>/faces/myFacesExtensionResource/*</urlpattern>
</filtermapping>
同样也可以将使用urlpattern来代替serlvetname元素但是仍然需要/faces/myFacesExtensionResource/*这个映射
<! extension mapping for adding <script/> <link/> and other resource tags to JSFpages >
<filtermapping>
<filtername>MyFacesExtensionsFilter</filtername>
<urlpattern>*jsf</urlpattern>
</filtermapping>
使用这个过滤器不会对性能造成太大的影响即不会延迟响应时间但是由于需要在将响应写出到客户端之前在内存中将整个response进行缓存会增加内存的使用
如果只是使用标准的JSF组件而不是用MyFaces扩展的组件以t:打头的那么就不需要使用这个filter否则的话就需要进行配置
ExtensionFilter not correctly configured Error?
如果发生如下的错误
javalangIllegalStateException: ExtensionsFilter not correctly configured JSF mapping missing JSF pages not covered
并且所有的配置已经正确的设置那么检查以下内容
确保配置正确参考
如果使用Servlet 那么不能使用jsp:forward或者requestgetDispatcher()forward来跳转到某个页面因为没有执行extensions filter作为替代方法要使用responsesendRedirect方法
使用tomahawk:popup标记时发生NullPointerException在HtmlPopupRendererencodedEnd
facet的名字需要硬编码成popup
库的依赖情况
MyFaces核心包和组件库
当发布MyFaces类库的时候也就是说不是Core框架它会和当前的MyFaces CoreSun Mojarra(即Sun JSF RI)的释放版本兼容
同时支持一些其他的版本但是不做任何保证
Getting Started
开始Apache MyFaces的第一步应该是查看一下样例应用程序可以在查看这些程序或者可以通过自定义部署来完成可以通过以下方式来部署
下载Tomcatx/Tomcatx
MyFaces例子下载最新的webapp文件(tomahawkXXXexampleszip)
将MyFaces样例文件解压缩到指定目录
将任何之前在Tomcat中发布过的MyFaces应用程序清除同时清空Tomcat的work目录并且保证类路径或者Tomcat的lib(common/lib or shared/lib)中不存在jsfapijar或者jsfimpljar(也就是说Sun API和实现)将simplewar文件或者其他的例子拷贝到Tomcat安装目录的webapps目录启动Tomcat
也可以使用MyFaces 运行Sun JSF RI样例程序具体介绍参考这里
如何在自定义的web应用程序中使用MyFaces?建议步骤
在MyFaces Wiki网站查看与开发环境中servlet容器的兼容性
然后可以使用样例程序来开始开发例如blankwar将blankwar解压缩后就构成了需要工作的目录结构
安装和配置
在没有网络连接的情况下使用Tomahawk
tomahawkjar文件包含了METAINF/facesconfigxml该文件使用了到webfacesconfig__dtd的PUBLIC引用这使得在应用程序服务器启动的时候访问来参考该DTD文件这个问题的显示如下(从Tomcat日志中获取)
javaxfacesFacesException:Cant parse configuration file:jar:file:/<webcontextpath>/WEBINF/lib/tomahawkjar !/METAINF/facesconfigxml
解决这个问题的办法如下
在tomahawkjar文件中添加METAINF/webfacesconfig__dtd
修改同目录的facesconfigxml的到DTD的引用
<!DOCTYPE facesconfig SYSTEM webfacesconfig__dtd>
Apache Tomcat作为Servlet容器
Apache Tomcat x(不包括)
Apache Tomcat x可以和MyFaces共同工作所有需要的jar文件已经在MyFaces提供的war文件中包含
如果在启动时看到空白页面那么需要移除jspjar和commonsel因为Tomcatx将这些文件放在容器外造成了和MyFaces的war文件提供的jar文件沖突
专题项目
JSF状态管理
StateManager管理状态但是作为终端用户必须告诉它需要保存什么这可以通过实现Serializable接口(正确的使用transient变量)或者实现StateHolder来完成
数据在JSF中以两种不同的方式存储在特定范围的bean中或者在组件树中特定范围的bean经常是自解释的组件的状态保存在响应中然后再请求到达时被恢复有效的方式是让数据存储在组件的page范围只要页面不改变的话注意组件保存值的绑定和方法的绑定通过使用EL表达式(#{})所以他们指向的beans不在页面范围内存储到组件树中
MyFaces有一个SaveState组件叫做<t:saveState>允许将数据作为组件树的一部分存储
Immediate属性是如何工作的?
immediate属性可以被用来达成如下的功能
允许commandLink或者commandButton来将用户导航到其他页面而不需要处理当前页面的输入域中的数据特别是这允许在发生验证错误的情况下进行导航典型的例子就是cancel按钮
允许commandLink或者commandButton在忽略一些输入域的验证来触发后台逻辑这是上面所说的更普遍说法
使一个或多个输入组件具有验证高优先级这样的话如果同一个页面中有些低优先级的输入包含不合法的输入时验证也不会进行这样可以减少错误消息的显示
在讨论immediate之前首先来看一下JSF请求处理生命周期
Restore View – 创建或者回复前一个页面
Apply Request Values – 将组件的提交值设置为请求值
Process Validations – 转换和验证组件的值如果提交的值是合法的那么将组件的值设置为提交的值
Update Model Values – 将后台bean的值设置为组件的值
Invoke Application – 执行actionListeners和actions
Render Response – 返回响应
很多开发人员认为使用immediate标记是用来使组件跳过Process Validations阶段使用immediate属性的目的是使组件在Apply Request Values阶段被处理
使用immediate属性意味着组件的值会在applyrequestvalues阶段被验证也就是说在其他的nonimmediate组件的值之前(这些验证是在processvalidators阶段被验证)任何被标记为immediate的输入组件如果发生炎症错误都会导致处理在完成applyrequestvalues后跳转到render阶段也就是说如果任何immediate组件验证失败那么nonimmediate组件的错误消息不会被显示另外如果immediate组件的新的值和既存的value属性的值不同那么会激活一个ValueChangedEvent但是这个事件会在ApplyRequestValues阶段最后执行而不是在ProcessValidations阶段最后特别的这意味着任意关联到这个组件的ValueChangeListener都会在其他的immediate !UICommand组件的ActionListener之前执行(假设command组件在页面的后面发生)
将输入组件设置为immediate并不影响模型的更新任何的新的值仍然会在Update Model阶段被注入(也就是说在任何immediate命令组件执行之后)注意也可以使用ValueChangeListener直接更新模型
使用immediate属性使组件ActionListener或者action方法在applyrequestvalues阶段的最后被执行也就是说在任意非immediate值的验证和后台bean被更新之前执行
如果是返回一个导航字符串的form的action方法那么
任意非null字符串会使得生命周期直接运行到renderresponse阶段意味着任意非immediate组件的验证永远不会被执行这就是为什么immediate命令组件会以自然的方式来实现cancel操作它甚至在页面中输入域验证失败的情况当然也就没有什么update model阶段也就是说用户输入的数据被丢弃
null返回值会导致处理正常进行也就是说非immediate组件被验证然后执行update model(如果不发生验证错误)
如果想让actionListener方法返回void必须调用
facesContextrenderResponse();
在使用immediate输入组件时最重要的问题就是用户新输入的数值并不是总能在model中访问因为updatemodel阶段还没有执行
对于页面中的非immediate输入组件immediate命令组件的action方法访问用户输入数据的唯一方式就是通过使用组件绑定和通过名称查询来获取指定的UIComponent对象然后调用getSubmittedValue方法来获取用户提供的原始字符串这个值没有被转换成它的目标类型也不会被验证
对于immediate输入组件进行了转换和验证的步骤使用对应的UIComponnet组件是可能获取转换后的值如果组件在页面中位于UICommand组件的前面并且触发了ValueChangeListener这样就会执行ValueChangeListener
警告如果action方法更新模型但是不进行导航那么在输入组件的值通过验证并更新模型时都会覆盖后台bean的值
任何immediate 组件的验证失败都不会停止immediate命令组件的执行这和nonimmediate输入组件和命令组件大不相同
学习指南
当学习JSF的时候不需要仔细查看具体的实现下面建议了一个类/方法的列表对学习JSF和MyFaces如何实际工作的很有帮助同时提到了对典型方法的一个简短描述
javaxfaceswebappFacesServlet
init方法用于启动基本的faces它演示了如何使用FactoryFinder来创建LifeCycle和FacesContext工厂
service方法演示了LifeCycle对象控制整个JSF处理
ponentUIViewRoot
queueEvent方法在组件决定激活一个valuechange事件(或者其他类型的事件)时被调用事件队列的有意思的地方是
ponentUIInput的验证方法
orgapachemlHtmlButtonRendererBase的decode方法
ponentUIComponentBase
getRenderer方法演示了如何使用当前视图中的renderkitid组件声明的组件家族和组件声明的renderertype名称来决定使用哪一个renderer
javaxfaceswebappUIComponentTag
createComponentInstance演示了在JSP标记引用UIComponent组件并且组建不存在于view众时如何实例化这个组件通过调用ApplicationcreateComponent(String)注意该createComponentInstance方法调用自己的getComponentType()方法这个方法典型的实现了JSF终端标记类例如orgapachlHtmlCommandButtonTag
ponnetUIInput
processDecodesprocessValidatorsprocessUpdates方法演示了表单数据是如何变成模型的数据通过转换和验证的处理对于多数的组件实际的工作过程为Apply ValuesProcess Validators和Update Model阶段注意数据从表单中开始加载到组件的提交值字段转换成组件的本地值字段然后拷贝到后台bean很多条件影响到process的出口例如rendered stateimmediate stateconversion errors和validation errors
结合DataTable和ActionListeners
如果在dataTable的某一行包含command link或者command button可以从javaxfaceseventActionListener中轻松的获取bean
<h:dataTable value=#{ResultsBeanhitSethits} var=hit>
<h:column>
<h:commandLink>
<f:actionListener type=netjavaOrderActionListener />
<h:outputText value=Order />
</h:commandLink>
</h:column>
</h:dataTable>
可以通过下面的java代码来获取bean
public class OrderActionListener implements ActionListener {
public void processAction(ActionEvent anEvent) throws AbortProcessingException {
YourBeanClass tmpBean = null;
// 事件的getComponent方法返回command link或者command button
UIComponent tmpComponent = anEventgetComponent();
// 遍历command link或者command button的父组件出口父组件为UIData
while (null != tmpComponent && !(tmpComponent instanceof UIData)) {
tmpComponent = tmpComponentgetParent();
}
// 如果事件组件不为空并且父组件为UIData那么取每行的数据如果是Bean实例那么强制转换
if (tmpComponent != null && (tmpComponent instanceof UIData)) {
Object tmpRowData = ((UIData) tmpComponent)getRowData();
if (tmpRowData instanceof YourBeanClass) {
tmpBean = (YourBeanClass) tmpRowData;
//TODO Implementation of your method
}
}
//TODO Exception Handling if UIData not found or tmpRowBean of wrong type
}
}
通过Link或者Button的参数来执行方法
一个典型的情景
表格显示了对象的集合需要通过点击edit link或者button来跳转到要编辑的记录的详细页面
如果熟悉Struts或者其他的MVC框架那么可能会考虑到传输一个主键来作为请求参数在请求页面的URL中被包含
<a /appContext/someActiondo?id=&userAction=prepareEdit>Edit</a>
可以使用JSTL来生成上面的内容
<c:url value=someActiondo var=url>
<c:param name=id value= />
<c:param name=userAction value=prepareEdit />
</c:url>
<a ${url}>Edit</a>
在JSF中有很多方法来处理这种情况下面列举了三种关于第一种解决方式还存在争议这里列举出来的目的是这可能是开发人员最先想到的
> 使用<f:factionListener >和UIData的getRowData()
可以参考上面的集成DataTable和ActionListeners来完成
> 使用f:param来传递参数
使用JSF完成的第一个思路可能就是模拟已经将参数传递给了链接可以通过在commandButton或者commandLink中使用f:param标记
<t:dataTable var=emp >
<h:commandLink id=editLink action=#{employeeActionprepareEdit}>
<h:outputText value=#{msgedit}/>
<f:param name=id value=#{empid}/>
</h:commandLink>
然后获取处理请求参数的句柄
FacesContext context = FacesContextgetCurrentInstance();
Map map = contextgetExternalContext()getRequestParameterMap();
String employeeID = (String) mapget(id);
上述的方式是可行的但是需要开发人员处理String可能需要自己编写转换器另外需要添加额外的代码来获取map保存的参数有更简洁的方法来完成
> 使用<t:updateActionListener />