Apache MyFaces项目包含了几个关于JavaServer技术的子项目。如果需要了解更多关于JavaServer Faces的知识,请参考MyFaces对于JSF的介绍。
Apache MyFaces项目提供了以下的功能: JavaServer Faces的实现(MyFaces API和MyFaces Impl模块) 用于构建JSF相关的web应用程序的组件库,例如MyFaces Tomahawk、MyFaces Trinidad、MyFaces Tobago。 JavaServer Faces扩展包,例如MyFaces Orchestra。 与其他技术和标准的集成模块。(用于开发移动客户端,在本文和后续文章中对这部分功能不进行介绍。) 可以在如下位置下载和MyFaces Impl及其各个子项目的发布包及样例程序。MyFaces API 中国网管联盟www、bitsCN、com 在JSF的子项目中,Core子项目视JSF规范的一个实现,其他的项目实现了相关的规范,例如Portlet Bridge,或者为其他的JSF实现添加了扩展,注意,不仅仅是MyFaces Core。同时,MyFaces的扩展,例如Tomahawk,可以和任意的JSF实现协同工作,例如Sun Reference Inplementation。 网管网bitsCN_com
JSF FAQ 中国网管联盟www、bitsCN、com MyFaces FAQ 1. 什么是shared项目? 如果myfaces-core、tomahawk、tobago、trinidad都是完全独立的项目,那么就不需要shared代码项目,每个项目都在各自的名称空间内维护代码。但是,会存在大量的重复代码,并且工作量的浪费。因为它们都属于myfaces项目,所以多个子项目可以共用的代码放在shared项目中,以便减少开发和维护的工作量。 但是,这些子项目又都有各自的独立释放周期。并且,tomahawk/tobago/trinidad都应该运行在任意的JSF实现上,而不仅仅是myfaces-core。目前使用的解决办法是重命名shared的类的报名。代码被重命名成org.apache.myfaces.shared_impl、org.apache.myfaces.shared_tomahawk等等。这样每个子项目就可以在发布的时候包含所有的支持类,而不是一个独立的共享jar文件,也不需要关心相同类的定义的冲突。对于特定项目的升级不会影响到相同环境下的其他项目。 注意最近的MyFaces项目的释放都是用了shared库,并在源代码的jar文件中包含了shared项目的源代码,所以不需要添加额外的源代码jar文件。 2. 如何从MyFaces获取格式良好的HTML输出? 中国网管联盟www_bitscn_com JTidy项目提供了一个ServletFilter,可以将响应的消息在输出前进行重新的格式化。Mozilla Firefox浏览器提供了一个扩展的View Formatted Source功能。 在一些版本的MyFaces中,可以通过设置org.apache.myfaces.PRETTY_HTML来在web.xml文件中启用pretty输出。但是,这个选项从来都没有被很好的支持,因为需要所有的renderer来支持,以便其工作。可能在以后的MyFaces发布中移除。 3. MyFaces Core和tomahawk发布包中的版本号表示什么? MyFaces Core使用三个部分来表示版本,例如1.1.1。但是这个值和普通的版本号计数是不同的。强两个数字表示了JSF规范的版本。因为二进制的JSF规范的API没有改变,前两位数字相同的发布包被认为是兼容的,所有使用JSF指定特征的既存代码会同样可以使用。 Tomahawk库也适用相同格式的版本号。但是因为JSF 1.2规范是向后兼容的,即兼容JSF 1.1规范,所有Tomahawk发布的版本中,1.1.x同样可以工作在JSF 1.2上。注意,tomahawk释放不保证二进制向后兼容。 4. 为什么DataModel不是可序列化的? 网管联盟www.bitsCN.com DataModel类(在UIData组件中使用)在显示和恢复视图阶段不需要保存任何任何状态。因此,不需要将它定义为可序列化的。 如果需要定义可序列化的managed bean,并且它包含一个DataModel类型的成员变量,那么将成员变量定义为transient。 5. 为什么时间显示不正确? JSF规范要求默认的date->String转换器使用标准的UTC时区,也叫做GMT时区。 MyFaces 1.1.0或者早期的发布并没有遵循JSF规范,它们默认的使用了服务器的时区。 可以通过显示试用转换器来进行时区的控制,例如: <f:convertDateTime timeZone="Antarctica/South_Pole" .../> 或者 <f:convertDateTime timeZone="#{bean.timeZone}" .../> #{bean.timeZone}返回字符串id或者TimeZone实例。 当然,也可以注册自定义的converter来覆盖标准的converter,使自定义代码适用于所有的时间到字符串的转换。 6. 如何在一个managed bean中访问另外一个managed bean? 54ne.com 有两种方法来实现访问同一个webapp中的其他managed bean: 使用依赖注入:在faces配置文件中定义managed beans,managed bean的属性可以被声明成到其他managed bean的引用: <managed-bean> <managed-bean-name>neededBean</managed-bean-name> <managed-bean-class>fqn.to.NeededBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> m
<managed-bean> 中国网管联盟www、bitsCN、com
</managed-property> </managed-bean> JSF规范要求managed properties根据它们声明的顺序进行初始化。所以setInitialized方法会在所有其他属性被调用后进行设置。 8. PhaseListener为什么会被调用两次?
JSF规范要求任何JSF实现框架在启动时自动加载/WEB-INF/faces-config.xml,所以没有必要添加如下的context参数: <context-param> <param-name>javax.faces.CONFIG_FILES</param-name> <param-value>/WEB-INF/faces-config.xml</param-value> </context-param> 如果在web.xml文件中配置了上述信息,会迫使JSF实现加载配置文件两次,所以注册了每个phase listener两次。 9. dataTables的Action listener和action命令没有被调用? 如果action源(h:commandLink,h:commandButton)没有被提供,那么Action Listeners和actions就不会被激活。当action源在dataTable上时,并且dataTable的value属性指向一个请求范围的数据源,那么action源在接下来的请求中就没有被提供,例如: <h:dataTable value="#{requestScopedBean.dataModel.wrappedData}" /> <h:column> <h:commandLink value="click here" action="#{backingBean.willNotFire}" /> </h:column> </h:dataTable> action源没有被rendered,是因为数据源在随后的请求中不存在了(在第一次响应完成后,被垃圾回收器进行了回收)。 为了解决这个问题,使用t:saveState标记或者将request范围的bean放在session范围内: <t:saveState value="#{myRequestScopedBean.dataModel.wrappedData}" /> 10. 日历、树等不能工作,并发生javascript脚本错误? 这是需要配置MyFacesExtensionFilter。 一些MyFaces组件不仅仅包含了HTML,可能需要额外的支持脚本、样式表、图片等等。这些资源包含在MyFaces的jar文件中,Extensions Filter添加所需的代码和URL来提供这些资源给生成的HTML。 网管联盟www.bitsCN.com
一些其他的组件,例如文件上传需要解析Multipart请求,这也是由Extensions Filter来完成的。 使用ExtensionFilter有如下的好处: 将MyFaces的组件和应用程序良好的隔离 不需要在页面或者webapp中添加MyFaces其他的组件相关的代码或者资源。 为MyFaces开发组提供了灵活的更新组件,保持透明及向后兼容的功能。 为页面开发人员减轻了压力。 只加载对使用组件的资源 处理MyFaces资源缓存 可以通过如下方式配置extension filter: 在web.xml文件中,将filter映射到JSF页面,例如*.jsp,以便使filter可以更新页面中的资源链接;同时映射filter到/faces/myFaces/ExtensionResources/*路径,这样可以处理页面独立的资源,例如图片,javascript脚本文件以及样式表等等。下面是一个配置的例子: <filter> <filter-name>MyFacesExtensionsFilter</filter-name> <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class> 54ne.com
<init-param> <param-name>maxFileSize</param-name> <param-value>20m</param-value> <description>Set the size limit for uploaded files. Format: 10 - 10 bytes 10k - 10 KB 10m - 10 MB 1g - 1 GB </description> feedom.net </init-param> </filter> 网管网bitsCN_com
<!-- extension mapping for adding <script/>, <link/>, and other resource tags to JSF-pages --> 网管联盟www.bitsCN.com
<!-- extension mapping for serving page-independent resources (javascript, stylesheets, images, etc.) --> 中国网管论坛bbs.bitsCN.com
如果只是使用标准的JSF组件,而不是用MyFaces扩展的组件,以t:打头的,那么就不需要使用这个filter,否则的话,就需要进行配置。 11. “ExtensionFilter not correctly configured.” Error? 如果发生如下的错误: "java.lang.IllegalStateException: ExtensionsFilter not correctly configured. JSF mapping missing. JSF pages not covered" 并且所有的配置已经正确的设置,那么检查以下内容: 确保配置正确,参考:http://myfaces./tomahawk/extensionsFilter.html 如果使用Servlet 2.4,那么不能使用jsp:forward或者request.getDispatcher().forward来跳转到某个页面,因为没有执行extensions filter,作为替代方法,要使用response.sendRedirect方法。 12. 使用tomahawk:popup标记时,发生NullPointerException,在HtmlPopupRenderer.encodedEnd facet的名字需要硬编码成”popup”。 库的依赖情况 MyFaces核心包和组件库。 当发布MyFaces类库的时候,也就是说不是Core框架,它会和当前的MyFaces Core,Sun Mojarra(即Sun JSF RI)的释放版本兼容。 54com.cn
同时支持一些其他的版本,但是不做任何保证。 Getting Started 开始Apache MyFaces的第一步应该是查看一下样例应用程序。可以在http://www./myfaces.jsf查看这些程序,或者可以通过自定义部署来完成。可以通过以下方式来部署: 下载Tomcat5.x/Tomcat6.x MyFaces例子。下载最新的webapp文件(tomahawk-X.X.X-examples.zip) 将MyFaces样例文件解压缩到指定目录。 将任何之前在Tomcat中发布过的MyFaces应用程序清除,同时清空Tomcat的work目录。并且保证类路径或者Tomcat的lib(common/lib or shared/lib)中不存在jsf-api.jar或者jsf-impl.jar(也就是说Sun API和实现)。将simple.war文件或者其他的例子拷贝到Tomcat安装目录的webapps目录。启动Tomcat。 也可以使用MyFaces 运行Sun JSF RI样例程序,具体介绍参考这里。 如何在自定义的web应用程序中使用MyFaces?建议步骤: 在MyFaces Wiki网站查看与开发环境中servlet容器的兼容性 然后可以使用样例程序来开始开发,例如blank.war。将blank.war解压缩后,就构成了需要工作的目录结构。 中国网管联盟www_bitscn_com 安装和配置 1. 在没有网络连接的情况下使用Tomahawk tomahawk.jar文件包含了META-INF/faces-config.xml,该文件使用了到web-facesconfig_1_1.dtd的PUBLIC引用。这使得在应用程序服务器启动的时候,访问java.sun.com来参考该DTD文件。这个问题的显示如下(从Tomcat日志中获取): “javax.faces.FacesException:Can’t parse configuration file:jar:file:/<web-context-path>/WEB-INF/lib/tomahawk.jar !/META-INF/faces-config.xml” 解决这个问题的办法如下: 在tomahawk.jar文件中添加META-INF/web-facesconfig_1_1.dtd 修改同目录的faces-config.xml的到DTD的引用: <!DOCTYPE faces-config SYSTEM "web-facesconfig_1_1.dtd"> 2. Apache Tomcat作为Servlet容器。 Apache Tomcat 5.5.x(不包括5.5.9) Apache Tomcat 5.5.x可以和MyFaces共同工作,所有需要的jar文件已经在MyFaces提供的war文件中包含。 54ne.com 如果在启动时看到空白页面,那么需要移除jsp-2.0.jar和commons.el。因为Tomcat5.5.x将这些文件放在容器外,造成了和MyFaces的war文件提供的jar文件冲突。 54ne.com
专题项目 网管网bitsCN.com
将输入组件设置为immediate并不影响模型的更新,任何的新的值仍然会在Update Model阶段被注入(也就是说,在任何immediate命令组件执行之后)。注意,也可以使用ValueChangeListener直接更新模型。 使用immediate属性使组件ActionListener或者action方法在apply-request-values阶段的最后被执行。也就是说,在任意非immediate值的验证和后台bean被更新之前执行。 如果是返回一个导航字符串的form的action方法,那么: 任意非null字符串会使得生命周期直接运行到render-response阶段,意味着任意非immediate组件的验证永远不会被执行。这就是为什么immediate命令组件会以自然的方式来实现cancel操作。它甚至在页面中输入域验证失败的情况。当然,也就没有什么update model阶段,也就是说用户输入的数据被丢弃。 null返回值会导致处理正常进行,也就是说,非immediate组件被验证,然后执行update model(如果不发生验证错误)。 如果想让actionListener方法返回void,必须调用 facesContext.renderResponse(); 在使用immediate输入组件时最重要的问题就是用户新输入的数值并不是总能在model中访问,因为update-model阶段还没有执行。 网管网bitsCN.com
对于页面中的非immediate输入组件,immediate命令组件的action方法访问用户输入数据的唯一方式就是通过使用组件绑定和通过名称查询来获取指定的UIComponent对象,然后调用getSubmittedValue方法来获取用户提供的原始字符串。这个值没有被转换成它的目标类型,也不会被验证。 对于immediate输入组件,进行了转换和验证的步骤,使用对应的UIComponnet组件,是可能获取转换后的值。如果组件在页面中位于UICommand组件的前面,并且触发了ValueChangeListener,这样就会执行ValueChangeListener。 警告:如果action方法更新模型,但是不进行导航,那么在输入组件的值通过验证并更新模型时都会覆盖后台bean的值。 任何immediate 组件的验证失败都不会停止immediate命令组件的执行,这和non-immediate输入组件和命令组件大不相同。 学习指南 当学习JSF的时候,不需要仔细查看具体的实现,下面建议了一个类/方法的列表,对学习JSF和MyFaces如何实际工作的很有帮助,同时提到了对典型方法的一个简短描述。 javax.faces.webapp.FacesServlet init方法用于启动基本的faces。它演示了如何使用FactoryFinder来创建LifeCycle和FacesContext工厂 网管网bitsCN.com service方法演示了LifeCycle对象控制整个JSF处理 javax.faces.component.UIViewRoot queueEvent方法在组件决定激活一个value-change事件(或者其他类型的事件)时被调用。事件队列的有意思的地方是: javax.faces.component.UIInput的验证方法 org.apache.myfaces.renderkit.html.HtmlButtonRendererBase的decode方法 javax.faces.component.UIComponentBase getRenderer方法演示了如何使用当前视图中的renderkit-id、组件声明的组件家族和组件声明的renderer-type名称来决定使用哪一个renderer。
javax.faces.webapp.UIComponentTag createComponentInstance演示了在JSP标记引用UIComponent组件并且组建不存在于view众时,如何实例化这个组件,通过调用Application.createComponent(String),注意该createComponentInstance方法调用自己的getComponentType()方法,这个方法典型的实现了JSF终端标记类,例如org.apache.myfaces.taglib.html.HtmlCommandButtonTag。 javax.faces.componnet.UIInput processDecodes、processValidators、processUpdates方法演示了表单数据是如何变成模型的数据,通过转换和验证的处理。对于多数的组件,实际的工作过程为Apply Values、Process Validators和Update Model阶段。注意,数据从表单中开始,加载到组件的提交值字段,转换成组件的本地值字段,然后拷贝到后台bean。很多条件影响到process的出口,例如rendered state、immediate state、conversion errors和validation errors。 结合DataTable和ActionListeners 网管网bitsCN_com 如果在dataTable的某一行包含command link或者command button,可以从javax.faces.event.ActionListener中轻松的获取bean: <h:dataTable value="#{ResultsBean.hitSet.hits}" var="hit"> <h:column> <h:commandLink> <f:actionListener type="net.java.OrderActionListener" /> <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 = anEvent.getComponent(); // 遍历command link或者command button的父组件,出口:父组件为UIData while (null != tmpComponent && !(tmpComponent instanceof UIData)) { tmpComponent = tmpComponent.getParent(); } // 如果事件组件不为空,并且父组件为UIData,那么取每行的数据,如果是Bean实例,那么强制转换 if (tmpComponent != null && (tmpComponent instanceof UIData)) { Object tmpRowData = ((UIData) tmpComponent).getRowData(); if (tmpRowData instanceof YourBeanClass) { 网管网bitsCN_com 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 href="/appContext/someAction.do?id=1234&userAction=prepareEdit">Edit</a> 可以使用JSTL来生成上面的内容: <c:url value="someAction.do" var="url"> feedom.net <c:param name="id" value="1234" /> <c:param name="userAction" value="prepareEdit" /> </c:url> <a href="${url}">Edit</a> 在JSF中,有很多方法来处理这种情况,下面列举了三种,关于第一种解决方式还存在争议,这里列举出来的目的是这可能是开发人员最先想到的。 1> 使用<f:factionListener ...>和UIData的getRowData() 可以参考上面的集成DataTable和ActionListeners来完成。 2> 使用f:param来传递参数 使用JSF完成的第一个思路可能就是模拟已经将参数传递给了链接。可以通过在commandButton或者commandLink中使用f:param标记: <t:dataTable var="emp" .... >
<h:commandLink id="editLink" action="#{employeeAction.prepareEdit}">
然后获取处理请求参数的句柄:
FacesContext context = FacesContext.getCurrentInstance(); |
|