实现存储库的持久性
在本节,我们会增强示例应用程序以在关机之前将存储库保存到服务器上并在服务器再启动时加以恢复。对象序列化是实现存储库持久性的一种最简单的方式,原因是 DataMapRepository 类、所包含的数据地图以及它们的元素都是可序列化的。注意:数据地图包含 JSF 组件的值,而根据 JSP 规范,它们必须是可序列化的。
对象序列化有很多明显的缺陷,但在这种情况下,却是一个合理的持久性解决方案,原因是保存在存储库中的表单数据实例的数量是有限的。而且,即使存储库的临时数据在服务器由于某些与应用程序不相干的问题而当机的情况下丢失了,也不会造成任何严重后果。一种更为可靠的、基于关系型或对象数据库的解决方案将需要更多的 CPU 资源,其分配对于每 10 秒就更新一次的部分用户输入的存储而言很难做到。而且,存储库状态还应该能够不受服务器或应用程序重启的影响。
使用 ServletContextListener
示例应用程序的 DataMapPersistence 类实现了 javax.servlet.ServletContextListener 接口以便它能在应用程序开始和结束时获得通知。这些侦听器方法使用由 getDataFile() 方法返回的 File 对数据存储库进行序列化和反序列化处理(如 清单 15 所示)。数据文件放在示例应用程序的 WEB-INF 目录。
清单 15. 获得数据存储库的文件
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
...
import java.io.File;
public class DataMapPersistence implements ServletContextListener {
private File getDataFile(ServletContext sctx) {
String path = sctx.getRealPath("/WEB-INF/repository.ser");
if (path == null)
return null;
return new File(path);
}
...
}
加载数据存储库
servlet/JSP 容器会在应用程序初始化期间调用 contextInitialized() 方法。清单 16 给出了此方法,它对存储库对象进行反序列化并设置名为 loadedRepository 的上下文属性,此属性可使用 JSP/JSF EL 日后访问。servlet 上下文属性与保存在 application 作用域内的 JSP/JSF 变量类似。所加载的存储库随后可用于设置 RepositoryWrapper bean 的 repository 属性。
清单 16. 在应用程序初始化期间加载数据存储库
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
...
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DataMapPersistence implements ServletContextListener {
...
public void contextInitialized(ServletContextEvent e) {
ServletContext sctx = e.getServletContext();
File dataFile = getDataFile(sctx);
if (dataFile == null || !dataFile.exists())
return;
try {
ObjectInputStream in = new ObjectInputStream(
new BufferedInputStream(
new FileInputStream(dataFile)));
try {
// Read the data repository from the file.
Object repository = in.readObject();
// Store the loaded repository into the application scope.
sctx.setAttribute("loadedRepository", repository);
} finally {
in.close();
}
} catch (Exception x) {
sctx.log("Loading Error", x);
}
}
...
}
在 contextInitialized() 调用之前,不会调用任何 servlet 或 JSP 页,这意味着 JSF 框架可能还尚未初始化。因而,contextInitialized() 不能设置 RepositoryWrapper bean 的 repository 属性,此 bean 受管于 JSF 框架。这种设定在 faces-config.xml 文件完成,在此文件中,application 作用域的 loadedRepository 变量可完全被用于设置此受管 bean 的 repository 属性(参见 清单 17)。
清单 17. 将所加载的数据存储到存储库包装器
...
<managed-bean>
<managed-bean-name>repositoryWrapper</managed-bean-name>
<managed-bean-class>autosave.RepositoryWrapper</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<managed-property>
<property-name>repository</property-name>
<value>#{loadedRepository}</value>
</managed-property>
...
</managed-bean>
...
</faces-config>
保存数据存储库
在应用程序关闭后,servlet/JSP 容器将调用 contextDestroyed() 方法。在获得 RepositoryWrapper 实例后,由 JSF 框架进行管理,contextDestroyed() 方法从包装器 bean 检索数据存储库的一个副本并将此存储库副本序列化到数据文件内(参见 清单 18)。
清单 18. 应用程序关闭前将文件中的存储库数据保存到文件中
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
...
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class DataMapPersistence implements ServletContextListener {
...
public void contextDestroyed(ServletContextEvent e) {
ServletContext sctx = e.getServletContext();
File dataFile = getDataFile(sctx);
if (dataFile == null)
return;
try {
ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(
new FileOutputStream(dataFile)));
try {
// Get a copy of the data repository from the wrapper bean.
RepositoryWrapper wrapper
= RepositoryWrapper.getManagedBean(sctx);
Object repository = wrapper.getRepository();
// Serialize the data repository into the file.
out.writeObject(repository);
} finally {
out.close();
}
} catch (Exception x) {
sctx.log("Saving Error", x);
}
}
}
清单 19 显示了 DataMapPersistence 类是如何作为 web.xml 文件中的 servlet 上下文侦听器进行配置的。
清单 19. 配置 servlet 上下文侦听器
...
<listener>
<listener-class>autosave.DataMapPersistence</listener-class>
</listener>
...
</web-app>