在多线程环境中工作
之前介绍过的 DataMapRepository 类及其基类 LinkedHashMap 都不是线程安全的。解决这个问题的第一种方法是使用 java.util.Collections 的 synchronizedMap() 方法,它返回线程安全的包装器地图。对表单数据存储库的情况而言,除了确保它在多线程的服务器环境中被安全使用之外,最好是构建一个定制的包装器类,由它控制如何访问存储库。
为数据存储库使用线程安全的包装器
清单 9 给出了这个包装器类,在示例应用程序中称为 RepositoryWrapper。它具有一个名为 repository 的字段,类型为 DataMapRepository。getRepository() 方法返回专用 repository 的副本,而 setRepository() 也会创建一个新副本。这些副本都包含与原始存储库相同的数据地图对象,这一点没错,因为数据地图在用表单数据创建和填充之后不会被修改。
清单 9. 数据存储库的包装器类
...
public class RepositoryWrapper implements java.io.Serializable {
private DataMapRepository repository;
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 DataMapRepository(repository);
else
this.repository.clear();
}
...
}
RepositoryWrapper 类包含线程安全的方法以便访问数据地图和包装了的存储库的 maxDataMaps 属性(参见 清单 10)。getDataMap() 和 setDataMap() 方法可以为给定的 FacesContext 检索和存储数据地图。若针对 ctx 参数的数据地图已经存在,hasDataMap() 方法返回 true,而且 clearDataMap() 方法会从存储库删除数据地图。
清单 10. 用于访问数据存储库的线程安全的方法
...
import javax.faces.context.FacesContext;
...
import java.util.Map;
public class RepositoryWrapper implements java.io.Serializable {
...
public synchronized Map<String, Object> getDataMap(
FacesContext ctx) {
return repository.getDataMap(ctx);
}
public synchronized void setDataMap(FacesContext ctx,
Map<String, Object> dataMap) {
repository.setDataMap(ctx, dataMap);
}
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 maxDataMaps) {
repository.setMaxDataMaps(maxDataMaps);
}
...
}
将包装器配置成 JSF 受管 bean
所有包含表单数据的地图对象都保存在存储库实例中,该实例的包装器被配置成 faces-config.xml 文件中的一个 JSF 受管 bean(参见 清单 11)。指定的 bean 的名称为 repositoryWrapper,作用域为 application。JSF 配置文件也可用于为数据存储库的 maxDataMaps 属性提供值。
清单 11. 将存储库包装器配置成 JSF 受管 bean
...
<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>maxDataMaps</property-name>
<value>100</value>
</managed-property>
</managed-bean>
...
</faces-config>
RepositoryWrapper 类有两个静态方法,可以返回受管 bean 实例。这些方法(如 清单 12 所示)可以使用 FacesContext 或 ServletContext 从 application 作用域检索包装器 bean。
清单 12. 获得受管 bean 实例
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.servlet.ServletContext;
...
public class RepositoryWrapper implements java.io.Serializable {
...
public static RepositoryWrapper getManagedBean(FacesContext ctx) {
Application app = ctx.getApplication();
ValueBinding vb = app.createValueBinding("#{repositoryWrapper}");
return (RepositoryWrapper) vb.getValue(ctx);
}
public static RepositoryWrapper getManagedBean(ServletContext ctx) {
return (RepositoryWrapper) ctx.getAttribute("repositoryWrapper");
}
}