访问静态和动态资源
作者:Mark Basler
状态:在 Early Access 版本阶段
问题描述
在设计组件时,开发者会发现需要为大多数开发项目重复执行一些常见任务。一个需要经常使用的功能是访问静态和/或动态资源。静态资源通常是 Javascript 文件、层叠样式表 (CSS) 文件、图像和表示片段等。动态资源通常与随输入而发生变化的功能相关,如 AJAX 调用、表单提交以及链接操作。本文阐述了用于实现静态和动态资源请求的各种方法。
解决方案
根据组件所使用的分发模式,可以使用不同的方法来访问资源。如果分发是 Web 归档 (WAR) 或企业归档 (EAR),则可以选用很多不同的方法来访问资源(无论是静态还是动态资源)。您甚至可以将资源打包为自己的归档文件,以便从 Web 页中直接访问它们。
大多数组件分发是指将所有相关资源打包为 Java 归档 (JAR),这些资源将随使用它们的应用程序一起提供。由于组件开发者希望最大限度地减少组件用户需要进行配置的工作量,因此,这种类型的打包可以减少用于访问资源的各种选项。同样的情况,使用直接访问方法装入资源时,将要求组件用户进行一些非必要的配置,如将资源文件复制到其应用程序分发中的特定位置,或者在应用程序的部署描述符中注册工件等。
我们在下面讨论了可用于访问静态和动态资源的各种方法。
直接访问
直接访问方法涉及将资源与归档的分发打包在一起。这有点类似于传统 Web 设计,其中将图像、Javascript 文件和 CSS 文件打包在 Web 模块下面,可通过 URL 进行访问,并由 Web 或应用服务器直接进行处理。在某些情况下,可通过 Servlet 来访问组件资源,这要求组件用户在 Web 应用程序标准部署描述符中定义 Servlet/Servlet 映射。
这是一种用于访问 Web 应用程序资源的常用惯例。但它要求开发者进行一些额外的配置才能使用组件。而且存在资源名称在组件之间不唯一的风险,即可能会导致无法在同一页中组合使用这些组件。此方法可能只适用于以下情况:其他应用程序不使用该组件,或者使用该组件的开发组的规模很小。
呈现器
可以使用组件的呈现器来提供通过 FacesServlet 访问的资源。在“应用请求值”阶段,呈现器具有控制权,它可以执行提供所需静态资源的任务,或者在动态调用中委托/执行相应的功能。在呈现器完成其任务后,将在 FacesContext 上调用 responseComplete 方法以跳过 JSF 生命周期的其余部分。
这种方法会造成性能下降和产生副作用等一些严重后果。在“应用请求值”阶段之前,“恢复视图”阶段必须重新构建组件树。这可能需要很长时间并对性能造成影响,尤其是在客户端上保存状态时。使用 responseComplete 方法时,此阶段必须完成执行,这可能会导致组件树出现不希望产生的副作用。如果组件将其 "immediate" 属性设置为 true,将导致在“应用请求值”阶段结束之前执行组件的逻辑(验证、转换和事件),此时也可能会出现副作用。考虑到这些限制,这并不是一种最佳方法。
PhaseListener
可以注册一个 PhaseListener ,以便在“恢复视图”阶段中由 PhaseListener 来处理请求,可能会通过 JSF 1.2 委托的方法将任务委托给受管 Bean(替代 JSF 1.1 方法绑定方法)。对于静态请求,PhaseListener 使用某种标识机制来查找资源(如通过传入的 phaseEvent.getFacesContext().getViewRoot().getViewId() 获得部分请求 URL),或者从 HttpServletRequest 参数映射中提取值(可通过 PhaseEvent 参数以及 phasesEvent.getFacesContext().getExternalContext().getRequestParameterMap() 调用来查找这些参数)。
根据每个组件的特定需要对 PhaseListener 进行编码的主要问题是,在与应用程序捆绑在一起的所有 faces-config.xml 文件中注册的所有 JAR 中的 PhaseListener 是依次激发的,并不保证原来的先后顺序。实际上遇到的问题是,对资源请求处理了多次,这会导致使用多个资源文件副本(端到端放置)填充响应。当多个开发者以不同方式对组件进行编码时,经常会出现这种问题。您会发现甚至在最严格的开发环境中,也可能会发生冲突。另外,存在多个 PhaseListener 会增加性能负担,因为将对每个请求执行注册的所有 PhaseListener。
第三方库
由于很难禁止开发者在现有 PhaseListener 中添加功能,因此,访问静态和动态资源的最佳方法是根本不使用定制的 PhaseListener。因此,我们使用 Shale-Remoting 库来满足所有静态/动态请求。Shale-Remoting 使用单一 PhaseListerner,它不要求开发者进行配置以实现静态请求,并将动态请求委托给受管 Bean。如果库中的组件使用此方法,则无需开发定制代码来传播请求,即可满足所有静态/动态请求。另外,Shale-Remoting 方法使用在 Web 上下文根目录中启动的 URL,因此,页面设计者不必跟踪页面在 Web 应用程序中的位置以确保它通过 FacesServlet。
静态示例:
此静态示例代码片段取自 com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer 类,并稍稍进行了修改以使其更加简单明了。在规划组件打包时,我们决定将资源存储在 JAR 的 /META-INF 目录中的组件名称下面。例如,文件上载组件的 Javascript 文件存储在 "/META-INF/fileupload/fileupload.js" 中。下面的代码片段说明了如何使用 Shale-Remoting API 访问 Javascript 文件 ("fileupload.js") 和 CSS 文件 ("fileupload.css") 静态资源。
import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;
/**
* <p>Stateless helper bean to manufacture resource linkages.</p>
*/
private static XhtmlHelper helper = new XhtmlHelper();
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
if ((context == null) || (component == null)) {
throw new NullPointerException();
}
ResponseWriter writer = context.getResponseWriter();
....
//shale remoting resource retrieval
helper.linkJavascript(context, component, writer,
Mechanism.CLASS_RESOURCE, "/META-INF/fileupload/fileupload.js");
helper.linkStylesheet(context, component, writer,
Mechanism.CLASS_RESOURCE, "/META-INF/fileupload/fileupload.css");
...
}
动态示例:
此动态示例代码片段取自 com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer 类,并稍稍进行了修改以使其更加简单明了。下面的示例说明了如何使用 Shale-Remoting 来动态地访问在应用程序的 faces-config.xml 文件中注册的受管 Bean ("bpui_fileupload_handler") 的 "handleFileUpload" 方法。创建动态调用字符串后,将用作 "onsubmit" Javascript 事件处理程序函数(该函数用于填充 Dojo bind 函数实现的 XMLHttpRequest URL)中的参数。通过 AJAX 执行动态 Shale-Remoting 调用 (XMLHttpRequest) 时,控制权将被委托给受管 Bean 的方法以执行相应的功能。然后,使用 org.apache.shale.remoting.faces.ResponseFactory 来创建 javax.faces.context.ResponseWriter,以便使用 startElement/EndElement 简便方法编写元素,从而简化返回相应响应的过程。
FileUploadRenderer 代码片段:
import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;
private static XhtmlHelper helper=new XhtmlHelper();
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
if ((context == null) || (component == null)) {
throw new NullPointerException();
}
ResponseWriter writer = context.getResponseWriter();
....
// shale remoting callback for status
String fileUploadCallback = helper.mapResourceId(context, Mechanism.DYNAMIC_RESOURCE,
"/bpui_fileupload_handler/handleFileUpload");
outComp.getAttributes().put("onsubmit", "return bpui.fileupload.submitForm(this, '" + retMimeType + "', '" + retFunction + "','" +
progressBarDivId + "', '" + fileUploadCallback + "')");
...
}
注意:使用 Shale-Remoting 动态调用时,响应头标被设置为允许浏览器在请求 URL 未发生变化时缓存响应数据。这可能会导致组件(如文件上载、进度栏)出现问题,即使用相同的 URL 来轮询状态更新(尤其是在 Internet Explorer 中)。Shale-Remoting 在将来的发行版本中会解决这一问题,但在解决此问题之前,可通过设置响应头标来通知浏览器不要缓存响应;下面取自 com.sun.javaee.blueprints.components.ui.fileupload.FileUploadHandler 类的 "handleFileStatus" 方法的代码片段描述了这一过程。较新的浏览器可能不需要所有这些头标,但 Struts 使用了此方法并计划将其用于 Shale-Remoting 中。
FacesContext context=FacesContext.getCurrentInstance();
HttpServletResponse response=(HttpServletResponse)context.getExternalContext().getResponse();
response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);
faces-config.xml 代码片段:
下面配置的受管 Bean 用于通过 Shale-Remoting 将请求链接到受管 Bean 上。还要注意,预填充了 "fileUploadStatus" 受管 Bean 以在 bpui_fileuploag_handler 受管 Bean 中使用。
<managed-bean>
<managed-bean-name>bpui_fileupload_handler</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.fileupload.FileUploadHandler</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>fileUploadStatus</property-name>
<value>#{fileUploadStatus}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>fileUploadStatus</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.fileupload.FileUploadStatus</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
如果要使用来自 HttpServerRequest 的参数来填充受管 Bean,名为 formInputElement 的表单元素的 managed-property 声明应类似于以下内容:
<managed-property>
<property-name>formInputElementValue</property-name>
<value>#{param.
formInputElement
}</value>
</managed-property>
有关 JSF 隐式对象的详细信息,请参见 Java EE 5 教程。也可以使用 HttpServletRequest 参数(可通过 FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap() 调用来查找这些参数)映射在受管 Bean 中检索 HttpServerRequest 参数值。
FileUploadHandler 代码片段:
在 Shale-Remoting 库将请求链接到受管 Bean 后,将使用 ResponseFactory 来创建 ResponseWriter 以简化请求的响应过程
import org.apache.shale.remoting.faces.ResponseFactory;
import javax.faces.context.ResponseWriter;
private static ResponseFactory factory=new ResponseFactory();
public void handleFileUpload() {
FacesContext context=FacesContext.getCurrentInstance();
...
try {
ResponseWriter writer = factory.getResponseWriter(context, "text/xml");
writer.startElement("response", null);
writer.startElement("message", null);
writer.write(status.getMessage());
writer.endElement("message");
...
writer.endElement("response");
writer.flush();
} catch (IOException iox) {
getLogger().log(Level.SEVERE, "response.exeception", iox);
}
}
参考资料
© Sun Microsystems 2006。Java BluePrints Solutions Catalog 中的所有内容受版权保护,未经 Sun Microsystems 的明确书面许可,不得在其他产品中发布。