收藏 分享(赏)

借助 ajax 自动保存 jsf 表单.doc

上传人:kuailexingkong 文档编号:1257806 上传时间:2018-06-20 格式:DOC 页数:17 大小:171KB
下载 相关 举报
借助 ajax 自动保存 jsf 表单.doc_第1页
第1页 / 共17页
借助 ajax 自动保存 jsf 表单.doc_第2页
第2页 / 共17页
借助 ajax 自动保存 jsf 表单.doc_第3页
第3页 / 共17页
借助 ajax 自动保存 jsf 表单.doc_第4页
第4页 / 共17页
借助 ajax 自动保存 jsf 表单.doc_第5页
第5页 / 共17页
点击查看更多>>
资源描述

1、第 1 章 借助 Ajax 自动保存 JSF 表单: 第 2 部分在服务器端管理表单数据级别: 中级Andrei Cioroianu, 高级 Java 开发人员和顾问, Devsphere2007 年 10 月 08 日在 “用 Ajax 自动保存 JSF 表单” 系列的第 1 部分中,作者兼 Java 开发人员 Andrei Cioroianu 为您展示了如何借助 Asynchronous JavaScript + XML(Ajax)和 JavaServer Faces(JSF)技术构建能自动保存 Web 表单的 Java 应用程序,介绍了如何用 JavaScript 和 XMLHttpRe

2、quest 获得、编码和提交表单数据,如何调整 JSF 请求生命周期来处理 Ajax 请求以及如何在服务器端从 JSF 组件树获取所提交的数据 。在这个包含三部分的系列文章的第 2 部分中,您将了解如何识别跨浏览器会话的匿名用户、如何为多个用户和页面管理自动保存的表单数据、如何选择数据存储库以及如何处理线程安全性问题。简介本系列的第 1 部分 中描述了这样一个场景:其中,应用程序在服务器上自动保存表单数据,在用户关闭或重新打开浏览器后,表单可以恢复。这个解决方案即使在用户浏览器失效或用户没有在 Web 表单上单击 Submit 就离开应用程序之后也可以很好地工作。本文所附的示例应用程序(请参见

3、 下载)包含了一个名为 SupportForm.jsp 的典型 JSF 表单,此表单的数据通过 AutoSaveScript.js 文件的 JavaScript 函数定期提交给服务器。SupportForm.jsp 和 AutoSaveScript.js 均在本系列 第 1 部分 做过介绍,该部分还展示了如何使用 JSF 阶段侦听器在不干扰应用程序逻辑的前提下处理 Ajax 请求。在本文中,您将学习如何构建线程安全的数据存储库来自动保存表单数据。您将看到如何选择数据结构、如何用用 JSF 组件树提取出的表单数据填充这些结构、如何恢复 JSF 组件的状态、如何限制数据存储库的内存资源以及如何实现

4、其持久性。您还会了解到几个 Web 技巧,例如如何使用过滤器和浏览器 ID cookies。跨会话识别用户要在用户关闭和重新打开浏览器之后恢复 Web 表单,应用程序要能跨会话识别用户,如果用户做过身份认证,这一点并不难实现。如果应用程序使用标准方法进行用户认证,就可以调用 getUserPrincipal() 方法,此方法在 HttpServletRequest 接口中定义,然后再用 java.security.Principal 的 getName() 方法获取用户名。如何应用程序支持匿名用户的表单保存和表单恢复特性,您也可以设置浏览器 ID,这个 ID 与会话 ID 十分类似,只不过后者

5、是在单一的会话中跟踪用户。实际上,当用户首次访问应用程序时,可以取得会话 ID cookie 的值并设置另一个名为 BROWSERID 的 cookie。与会话开始生成、会话结束失效的会话 ID 不同,BROWSERID cookie 只设置一次,且可以在很长一段时间(比如几年)后才失效。使用 servlet 过滤器servlet 过滤器非常适合设置 BROWSERID cookie,因为当用户首次访问应用程序时,此过滤器可以截取每个 HTTP 请求,并将此 cookie 添加到 HTTP 响应。一旦浏览器通过第一个响应收到此 cookie,那么所有后续响应都将包含 BROWSERID coo

6、kie,这样应用程序能够通过其浏览器 ID 来识别匿名用户。本文所附带的示例代码包含一个名为 BrowserIdFilter 的类,它实现了 javax.servlet.Filter。此类还有一个名为 getBrowserId() 的方法(参见 清单 1),它迭代请求对象的 cookie,返回 BROWSERID cookie 的值或 null(如果这样的 cookie 不存在)。清单 1. 获得浏览器 ID cookie文档选项打印本页将此页作为电子邮件发送未显示需要 JavaScript 的文档选项讨论样例代码developerWorks Ajax 资源中心请访问 Ajax 资源中心,这里

7、几乎囊括了关于 Ajax 编程模型的所有信息,包括各种文章和教程、论坛、博客、wikis、活动和新闻。用户会感谢您本文接下来将会介绍在 Ajax 应用程序中构建自动保存功能如何能让用户的 Web 体验更加方便和高效。与此同时,您还可以获得几个高级的 Web 技巧,比如使用过滤器和用户浏览器 ID cookie。package autosave;.import javax.servlet.http.Cookie;.public class BrowserIdFilter implements Filter public static String BROWSERID = “BROWSERID“;

8、 / cookie namepublic static String getBrowserId(HttpServletRequest httpRequest) String browserId = null;Cookie cookies = httpRequest.getCookies();if (cookies != null)for (int i = 0; i .BrowserIdFilterautosave.BrowserIdFilterBrowserIdFilter/*.浏览器 ID 的解决方案只能用在表单 不 包含任何敏感信息的情况下,原因是所有通过这个共享的计算机使用此应用程序的用

9、户都会被视为同一个用户。 跨会话识别用户的惟一一种最安全的方案是使用基于用户名和密码的标准身份认证方法。但基于密码的身份认证要求用户必须登录到应用程序,这很不方便。在很多时候,安全性最为重要,但有时,与其注册到站点,用户更愿意保持匿名。浏览器 ID 提供了识别匿名用户的一种简便方式。本系列所给出的表单自动保存特性适用于认证的和匿名的这两类用户。在接下来的章节中,您将看到如何存储和检索自动保存的表单数据以及如何在多线程的环境中使用表单数据。设置浏览器 ID浏览器 ID 也可以用于通过身份认证的用户以便他们无需每次访问应用程序时都要登录。您可能也见过这样的站点,其中的登录屏幕上有标着 “Remem

10、ber me on this computer” 的复选框。这样的复选框就是让此站点能够设置浏览器 ID 以便用户不必在返回该站点时还要再次登录。选择数据存储库首先,为了保存表单数据,必须选择数据结构和存储库。这一点十分重要,因为为了存储临时数据,经常需要访问存储库。在示例应用程序中,数据每隔 10 秒为每个表单实例自动保存一次,但在真正的应用程序中,如果具有大量的并发用户,可能将这个间隔提高到 10 分钟更为合理一些。自动保存的表单数据自然要存储在内存中,原因是这些数据只会存储很短的一段时间,之后会被更新的数据所替代。在用户提交表单之前,每个表单实例都会定期保存数据。提交之后,任何与所提交表

11、单相关的临时数据都会从内存清除。如果用户不能提交表单或没有单击提交按钮就放弃了此页面,那么最后保存的数据会尽可能久地被存储在内存。当用户再次返回此表单时,如果所保存的数据还在,他就可以选择恢复此表单。 保存和恢复 JSF 组件的值每个表单实例的自动保存数据都保存在 Map 实例中。这类数据地图的每个条目和元素都包含 JSF 输入组件的值,其 ID 是数据地图的键。这个结构与 javax.servlet.ServletRequest 的参数地图类似,但却不完全相同,原因是请求参数地图包含字符串数组,而存储库数据地图则保存 JSF 视图的所有输入组件的转变和验证后的值。DataMapReposit

12、ory 类的 saveValues() 方法是一种递归方法,可以遍历 JSF 组件树,用能实现 EditableValueHolder 的输入组件的值填充数据地图(参见 清单 4)。清单 4. 将 JSF 输入组件的值存储到数据地图package autosave;.import ponent.EditableValueHolder;import ponent.UIComponent;.public class DataMapRepository . .public static void saveValues(UIComponent comp,Map dataMap) if (comp =

13、null)return;if (comp instanceof EditableValueHolder) / Input component. Put its value into the data mapEditableValueHolder evh = (EditableValueHolder) comp;dataMap.put(comp.getId(), evh.getValue();/ Iterate over the children of the current componentIterator children = comp.getChildren().iterator();w

14、hile (children.hasNext() UIComponent child = (UIComponent) children.next();/ Recursive callsaveValues(child, dataMap);回页首.restoreValues() 方法(如 清单 5 所示)遍历此 JSF 组件树,恢复输入组件的值。此方法还清除了每个 EditableValueHolder 组件的 submittedValue 属性以便表单数据能恢复成地图数据,同时忽略所有的提交数据。 saveValues() 方法在本文稍后的部分会用到,restoreValues() 会在本系列的

15、第 3 部分用到。清单 5. 恢复 JSF 输入组件的值package autosave;.import ponent.EditableValueHolder;import ponent.UIComponent;.public class DataMapRepository . .public static void restoreValues(UIComponent comp,Map dataMap) if (comp = null | dataMap = null)return;if (comp instanceof EditableValueHolder) / Input compone

16、nt. Get its value from the data map/ and clear any submitted valueEditableValueHolder evh = (EditableValueHolder) comp;evh.setValue(dataMap.get(comp.getId();evh.setSubmittedValue(null);/ Iterate over the children of the current componentIterator children = comp.getChildren().iterator();while (childr

17、en.hasNext() UIComponent child = (UIComponent) children.next();/ Recursive callrestoreValues(child, dataMap);每个地图实例都有一个惟一的 ID,此 ID 由用户 ID 和 JSF 视图 ID 组成。因此,很自然地会将所有这些数据地图都放到一个存储库地图内。DataMapRepository 类会扩展 java.util.LinkedHashMap,而且它有一个方法,名为 getDataMapId()(参见 清单 6),该方法利用给定的 faces 上下文(其方法返回包含所需用户和视图信息

18、的对象)生成数据地图的 ID。如果用户登录,getDataMapId() 就会包含源自 Principal 对象的用户名。否则,用户就是匿名的,而且 getDataMapId() 使用的是浏览器 ID。JSF 视图 ID 是返回 ID 的一部分,对每个 JSF 页惟一。因此,此数据地图 ID 对每个用户和页面的组合而言也是惟一的。清单 6. 生成数据地图的惟一 IDpackage autosave;.import ponent.UIViewRoot;import javax.faces.context.ExternalContext;import javax.faces.context.Fac

19、esContext;.import java.security.Principal;.public class DataMapRepository . .public static String getDataMapId(FacesContext ctx) UIViewRoot root = ctx.getViewRoot();if (root = null)return null;ExternalContext ectx = ctx.getExternalContext();String userId = null;Principal principal = ectx.getUserPrin

20、cipal();if (principal != null) / Use the name of the authenticated user.userId = principal.getName(); else / Use the browser ID of the anonymous user.userId = BrowserIdFilter.getBrowserId(HttpServletRequest) ectx.getRequest();if (userId = null)return null;/ Concatenate the user ID and the JSF view I

21、Dreturn userId + root.getViewId();.限制数据存储库的内存资源由于 Java 堆有限且数据存储库也不应该消耗太多内存,因此有必要进行一些限制。在示例应用程序中,存储库所能存储的数据地图的实例是有限制的。当达到这个限值时,最旧的那个数据地图会从存储库中删除,以便进行垃圾收集。此机制已经构建到 java.util.LinkedHashMap 类中 只需重写 removeEldestEntry() 方法以便在存储库具有超出所允许数量的条目时能够返回 true。清单 7 给出了 DataMapRepository 类,它扩展了 LinkedHashMap,添加了 max

22、DataMaps 属性并重写了 removeEldestEntry() 方法,正如之前所解释的。此外,DataMapRepository 还包含了一个构造函数以便创建存储库实例的副本,如果想要在运行应用程序时获取存储库快照,这种方法会十分有用。原始的存储库和其副本均包含同样的数据地图对象,这是因为数据地图是不能修改的,这一点在本文稍后的部分介绍。清单 7. 数据存储库类package autosave;.import java.util.LinkedHashMap;import java.util.Map;public class DataMapRepositoryextends Linked

23、HashMap private static final int DEFAULT_MAX_DATA_MAPS = 1000;private int maxDataMaps;public DataMapRepository() maxDataMaps = DEFAULT_MAX_DATA_MAPS;public DataMapRepository(DataMapRepository repository) maxDataMaps = repository.maxDataMaps;putAll(repository);public int getMaxDataMaps() return maxDa

24、taMaps;public void setMaxDataMaps(int maxDataMaps) this.maxDataMaps = maxDataMaps;protected boolean removeEldestEntry(Map.Entry eldest) return size() maxDataMaps;.getDataMap() 和 setDataMap() 方法(参见 清单 8)可用于访问存储库的数据地图。这两个方法都使用 getDataMapId() 为给定的上下文生成 ID,然后再调用继承自 LinkedHashMap 类的 get()、put() 和 remove(

25、) 方法。清单 8. 恢复和检索数据地图所需的方法package autosave;.public class DataMapRepository . .public Map getDataMap(FacesContext ctx) String id = getDataMapId(ctx);if (id = null)return null;return get(id);public void setDataMap(FacesContext ctx, Map dataMap) String id = getDataMapId(ctx);if (id = null)return;if (dat

26、aMap != null)put(id, dataMap);elseremove(id);.在多线程环境中工作之前介绍过的 DataMapRepository 类及其基类 LinkedHashMap 都不是线程安全的。解决这个问题的第一种方法是使用 java.util.Collections 的 synchronizedMap() 方法,它返回线程安全的包装器地图。对表单数据存储库的情况而言,除了确保它在多线程的服务器环境中被安全使用之外,最好是构建一个定制的包装器类,由它控制如何访问存储库。为数据存储库使用线程安全的包装器清单 9 给出了这个包装器类,在示例应用程序中称为 Repositor

27、yWrapper。它具有一个名为 repository 的字段,类型为 DataMapRepository。getRepository() 方法返回专用 repository 的副本,而 setRepository() 也会创建一个新副本。这些副本都包含与原始存储库相同的数据地图对象,这一点没错,因为数据地图在用表单数据创建和填充之后不会被修改。清单 9. 数据存储库的包装器类package autosave;.public class RepositoryWrapper implements java.io.Serializable private DataMapRepository rep

28、ository;回页首public RepositoryWrapper() repository = new DataMapRepository();public synchronized DataMapRepository getRepository() return new DataMapRepository(repository);public synchronized void setRepository(DataMapRepository repository) if (repository != null)this.repository = new DataMapRepositor

29、y(repository);elsethis.repository.clear();.RepositoryWrapper 类包含线程安全的方法以便访问数据地图和包装了的存储库的 maxDataMaps 属性(参见 清单 10)。getDataMap() 和 setDataMap() 方法可以为给定的 FacesContext 检索和存储数据地图。若针对 ctx 参数的数据地图已经存在,hasDataMap() 方法返回 true,而且 clearDataMap() 方法会从存储库删除数据地图。清单 10. 用于访问数据存储库的线程安全的方法package autosave;.import ja

30、vax.faces.context.FacesContext;.import java.util.Map;public class RepositoryWrapper implements java.io.Serializable .public synchronized Map getDataMap(FacesContext ctx) return repository.getDataMap(ctx);public synchronized void setDataMap(FacesContext ctx,Map dataMap) repository.setDataMap(ctx, dat

31、aMap);public synchronized boolean hasDataMap(FacesContext ctx) return getDataMap(ctx) != null;public synchronized void clearDataMap(FacesContext ctx) setDataMap(ctx, null);public synchronized int getMaxDataMaps() return repository.getMaxDataMaps();public synchronized void setMaxDataMaps(int maxDataM

32、aps) repository.setMaxDataMaps(maxDataMaps);.将包装器配置成 JSF 受管 bean所有包含表单数据的地图对象都保存在存储库实例中,该实例的包装器被配置成 faces-config.xml 文件中的一个 JSF 受管 bean(参见 清单 11)。指定的 bean 的名称为 repositoryWrapper,作用域为 application。JSF 配置文件也可用于为数据存储库的 maxDataMaps 属性提供值。清单 11. 将存储库包装器配置成 JSF 受管 bean.repositoryWrapperautosave.RepositoryWrapperapplication.maxDataMaps100.RepositoryWrapper 类有两个静态方法,可以返回受管 bean 实例。这些方法(如 清单 12 所示)可以使用 FacesContext 或 ServletContext 从 application 作用域检索包装器 bean。清单 12. 获得受管 bean 实例

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 经营企划

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报