问题
前一阵子使用JSF开发web应用程序的过程中,碰到一个需求:A页面上存在一个链接,用户点击链接会被重定向B页面。页面B上存在一个单选框,如果是通
过A页面的链接过来,会把单选框置为“选择”的状态。这是非常典型的页面转向,根据JSF的页面转向配置,以及对JSF隐含对象param的介绍,下面的
代码“貌似”可行:
A页面:
<
h:commandLink
value
="Add"
action
="add"
>
<
f:param
name
="type"
value
="student"
/>
</
h:commandLink
>
B页面:
<
h:form
>
<
h:selectOneRadio
id
="type"
value
="#{param.type}"
>
<
f:selectItem
itemlabel
="student"
itemvalue
="student"
/>
<
f:selectItem
itemlabel
="teacher"
itemvalue
="teacher"
/>
</
h:selectOneRadio
>
<
h:commandButton
id
="add"
action
="#{backingBean.add}"
/>
</
h:form
>
编译、部署、重新刷新页面。不错,B页面上单选框的状态能根据是否来自A页面的链接呈现选中或否的状态:一切看上去都很美,似乎已经完成了功能开发。但是,等等,让我们提交表单。浏览器刷新了一遍,又回到了这个页面。通过检查后台数据库以及日志文件,我们发现:
- 数据库里面并没有添加新的记录
- 系统也没有按照配置的navigation转向正确的页面
- glassfish的日志文件中没有add方法执行打印的日志,也没有任何异常信息
这三点说明,#{backingBean.add}方法并没有调用,原来可以工作的添加功能出现了bug。JSF在处理页面提交请求的过程中发生了什么?让我们来调试一下。
原则
在软件开发中,调试的目的是解决“如何定位系统问题所在”的问题。一般意义上,解决问题的原则,套用胡适先生的话,就是“大胆假设,小心求证”;套用《麦
肯锡方法》,则是“以事实为基础,以假设为导向,结构化推理”。具体来看,调试是这样一种分析问题的方法,面对复杂的问题,通过逐步确定正确或者错误的事
情,缩小问题范围,直到定位问题所在为止。把事情确定化,也可以细分为以下步骤:
在调试过程中,上面的步骤周而复始,并借助于严密的逻辑论证来推动,直到定位最终的问题原因为止。同时,因为调试的过程中,开发人员面对的是已经“编码完成”的系统。“编码完成”的系统可以从如下两个层面来看分解:
如何高效调试不仅仅是调试工具的问题,更是人对技术和业务领域的理解问题。在面对具体问题的时候,是采用“步步为营”,还是“分而治之”,都是
依赖于当时的具体问题,以及开发人员对问题场景的理解程度和技术熟悉程度。那么,高效地调试应该是什么样子呢?我觉得应该是这样的:
- 划定问题域边界
- 选择确定的出发点
- 借助其他已经确定的点走查问题域,缩小问题域
好,来看看针对JSF的这个问题如何调试。
步骤
我们先来划定我们初始的问题域:JSF请求提交后,JSF不能正常调用后台方法进行处理。我们想知道,JSF处理请求过程中哪个地方出问题了。那么
我们确定的点是什么呢?JSF规范。因为我们使用的是SUN开发的JSF
RI,所以它必然满足JSF规范。在规范中,JSF的请求处理过程一共分成六个阶段:
- Restore View
- Apply Request Values
- Process Validations
- Update Model Values
- Invoke Application
- Render Response
我们可以定义一个PhaseListener,注册到faces-configs.xml文件里面,看整个请求过程发生了什么?通过查看
glassfish的日志文件,我们发现update model values之后就直接render response,没有 invoke
application。
如果一切正常,应该是从第一步执行到第六步,但现在跳过了第五步,直接从第四步到了第六步,是哪里出现了问题?好,从“JSF的处理过程”到“第四步
Update Model Values”,我们已经缩小了问题域的范围,现在确定的点已经有JSF规范和 “Update Model
Values”了。继续,从JSF规范对步骤“”中寻找“Update Model Values”的说明:
If any of the updateModel() methods that was invoked, or an
event listener that processed a queued event, called renderResponse()
on the FacesContext instance for the current request, clear the
remaining events from the event queue and transfer control to the
Render Response phase of the request processing lifecycle. Otherwise,
control must proceed to the Invoke Application phase.
这里提到如果我们在updateModel()方法或者事件监听器里面调用了FacesContext的renderResponse()方法,就会从事
件队列里面直接清空剩下的事件,转向Render Response步骤。但是我们没有注册任何的事件监听器,也没有自定义任何组件的
updateModel()方法,那就只能是在系统组件的updateModel()方法里面抛出异常被JSF引擎捕获,然后直接 render
response。现在进一步缩小范围了,让我们来看看Javaapi doc里面是如何介绍UIInput.updateModel() 方法的。
Call setValue() method of the ValueExpression to update the value that the ValueExpression points at.
问题转移到javax.el.ValueExpression的setValue()方法,我们来看看这个方法的API:
Evaluates the expression relative to the provided context, and sets the result to the provided value.
Throws:
PropertyNotFoundException - if one of the property resolutions failed
because a specified variable or property does not exist or is not
readable.
再来看看组件的ValueExpression,我们写的是“${param.key}”,从文档里面可以得知param就是
externalContext.getRequestParameterMap(),而
ExternalContext.getRequestParameterMap()的文档描写是这样的:
Return an immutable Map whose keys are the set of request
parameters names included in the current request, and whose values (of
type String) are the first (or only) value for each parameter name
returned by the underlying request.
因为表单提交时的request跟之前页面转向时的Request肯定不是一样,那是否由于该ValueExpression导致的问题。让我们来验证一下,把B页面上单选框组件的值改成字符串字面值“student”。现在B页面的单选框组件就变成了:
<
h:form
>
<
h:selectOneRadio
id
="type"
value
="student"
>
<
f:selectItem
itemLabel
="student"
itemValue
="student"
/>
<
f:selectItem
itemLabel
="teacher"
itemValue
="teacher"
/>
</
h:selectOneRadio
>
<
h:commandButton
id
="add"
action
="#{backingBean.add}"
/>
</
h:form
>
部署,运行。不错,现在的页面组件能保持选中的状态,也能顺利创建新纪录,日志文件中也有add方式的执行信息:说明的确是因为#{param.key}
表达式的求值出错导致异常。这里的#{param}已经不再是上一步的#{param},自然无法从externalContext的
RequestParameterMap里面找到参数名为type的值。因此,JSF运行到这里,因为无法取到参数值去更新页面的单选框组件,所以就跳出
了处理过程。
现在,回过头来看一下问题的原因:JSF在处理请求的时候,会对页面组件树上的所有组件进行递归更新,它会根据组件定义的EL表达式来重新计算值,更新组
件状态,以保证JSF页面组件的状态性。我们得到的教训是param等JSF隐含对象或许能用,但最好不要放在JSF组件里面。“进什么庙,拜什么神”,
我们还是选择JSF推荐的backingbean来保持组件的值。
结语
软件调试是一项很有意思的活动,常常给开发人员带来解谜般的快感,或者一团乱麻的纠结。导入代码、设置断点、逐步调试并不是最好的办法,清楚地划分问题
域,找准确定点可能会事半功倍。当然,在找出水面下面的暗礁之后,别忘记给自己、给其他人mark上这块区域的暗礁位置,能极大减少以后触礁的痛苦。
相关推荐
本教程将深入讲解如何通过JSF实现一个简单的登录程序,这是初学者接触JSF时的典型起点。 在JSF中,我们通常会使用XML配置文件(faces-config.xml)来定义应用程序组件和导航规则,以及Java类(如Managed Beans)来...
总之,"jw-0719-jsf.zip_JSF"项目展示了如何在一个典型的J2EE环境中,结合JSF和Spring框架构建一个Web应用,并利用MySQL数据库存储和处理数据。这样的架构提供了良好的模块化和可扩展性,同时也便于维护和调试。通过...
通过对典型应用场景的分析,比如用户登录界面、产品列表展示等,可以更好地理解如何有效地使用 JSF 构建复杂的应用程序。 **4.2 最佳实践** - **代码复用**:充分利用 JSF 的组件模型,创建可复用的 UI 组件。 - *...
【标题】"semeru_jsf_maven"项目是一个基于Java技术栈的Web应用程序开发示例,它结合了Semeru Runtime(一个开源的Java运行时环境)与JavaServer Faces (JSF) 和Maven构建工具。这个项目旨在提供一个完整的、可运行...
- **项目结构**:一个典型的JSH整合项目会包含JSF的视图层(.xhtml文件)、Spring的服务层(.java Bean)和Hibernate的数据访问层(.hbm.xml映射文件及对应的DAO)。同时,还需要配置web.xml、faces-config.xml和...
8. **MyEclipse集成**:标签中提到了"Myeclipse",这是一款集成开发环境,可能用于创建和调试这个ICEfaces项目。在MyEclipse中,有专门的支持工具和插件来简化ICEfaces应用的开发过程。 通过分析这个ICEfaces ...
4. **MyEclipse IDE**:一个强大的Java EE集成开发环境,支持多种框架,如JSF、Spring、Hibernate等,方便开发、调试和部署Web应用。 5. **项目结构**:典型的Java Web项目结构,包括源码、资源、配置文件和依赖库等...
它简化了开发过程,提供了一种结构化的解决方案,使得开发者能够更高效地构建可维护和可扩展的Web应用。在"用Struts框架实现的网上购书程序"中,我们可以探讨以下几个关键知识点: 1. **Struts框架**:Struts通过...
JavaEE是一种广泛应用于开发企业级应用的技术框架,它基于Java平台,提供了丰富的组件和服务来构建分布式、高安全性的系统。本篇测验题涉及到的知识点涵盖了JavaEE的基础概念、架构、组件、打包方式以及Web应用开发...
【基于JSP的教务管理系统】是一个典型的Web应用程序,它利用了Java Web技术栈来实现对教学活动的全面管理。这个系统的核心目标是提供一个高效、便捷的平台,以协助教育机构进行课程安排、学生信息管理、成绩录入、...
- **综合案例**:提供了一个综合性的示例,展示了如何结合使用JSF和Portlets来构建应用。 #### 5.6 总结 - **章节总结**:回顾了本章学习的内容。 #### 5.7 资源 - **资源列表**:列出了一些有用的资源链接。 ###...
6. **测试与调试**:使用JDeveloper内置的测试工具来进行单元测试和功能测试,确保应用程序的正确性。 7. **部署与发布**:将应用程序打包并部署到服务器上,进行最终的功能验证和性能优化。 #### 结论 综上所述,...
它支持Java、JSP、Servlet、JSF等技术,使得开发、调试和部署过程更为便捷。在易买网项目中,myEclipse10被用来编写和运行JSP和Servlet代码。 JSP(Java Server Pages)是一种动态网页技术,它允许开发者在HTML中...
【Java Web项目源码 企业门户网站】是一个典型的Java Web应用程序,用于构建企业的在线展示平台。在深入探讨这个项目之前,我们需要了解Java Web开发的基础知识。 Java Web开发是使用Java技术来创建服务器端Web应用...
【Java+Web项目企业事务管理系统源码.zip】这个压缩包包含了一个典型的企业级事务管理系统的源代码,适合学习和理解企业级应用的开发流程。该系统以Java编程语言为基础,结合Web技术,构建了一个功能完善的事务处理...
【JavaWeb物资管理系统项目源码】是一个典型的JavaWeb应用程序,用于管理和跟踪组织内部的物资流动。这个系统的核心目的是提高物资管理的效率,确保资源的有效分配和利用。在深入解析源码之前,我们先来了解一些关于...
【基于JSP的实验室设备管理系统】是一篇计算机毕业论文,主要探讨了如何利用JSP(Java Server Pages)技术构建一个在线实验室设备管理系统。该系统旨在提高实验室设备管理的效率,通过电脑化操作实现设备的使用、...
JavaWeb实现工资管理系统是一个典型的Web应用程序开发项目,主要用于管理和处理企业员工的工资数据。这个系统通常包括用户界面、后台数据库以及连接这两者的服务器端代码。在这个项目中,开发者使用JavaWeb技术来...
JSF是一种更高级的视图技术,它提供了一整套组件和事件处理机制。 8. **部署与运行**:Java Web应用部署在Web服务器上,如Tomcat、Jetty等。应用的结构通常遵循一定的规范,如WAR文件格式,包含了Web-INF目录下的...
【基于J2EE的门诊划价系统】是一个典型的IT项目,它主要利用Java企业级平台(J2EE)的技术来构建一套适用于医疗机构的信息化解决方案。J2EE是Oracle公司(原Sun Microsystems)推出的用于开发分布式、多层架构的企业...