技术开发 频道

借助 Ajax 自动保存 JSF 表单: 第 2 部分

  选择数据存储库

  首先,为了保存表单数据,必须选择数据结构和存储库。这一点十分重要,因为为了存储临时数据,经常需要访问存储库。在示例应用程序中,数据每隔 10 秒为每个表单实例自动保存一次,但在真正的应用程序中,如果具有大量的并发用户,可能将这个间隔提高到 10 分钟更为合理一些。

  自动保存的表单数据自然要存储在内存中,原因是这些数据只会存储很短的一段时间,之后会被更新的数据所替代。在用户提交表单之前,每个表单实例都会定期保存数据。提交之后,任何与所提交表单相关的临时数据都会从内存清除。如果用户不能提交表单或没有单击提交按钮就放弃了此页面,那么最后保存的数据会尽可能久地被存储在内存。当用户再次返回此表单时,如果所保存的数据还在,他就可以选择恢复此表单。

  保存和恢复 JSF 组件的值

  每个表单实例的自动保存数据都保存在 Map 实例中。这类数据地图的每个条目和元素都包含 JSF 输入组件的值,其 ID 是数据地图的键。这个结构与 javax.servlet.ServletRequest 的参数地图类似,但却不完全相同,原因是请求参数地图包含字符串数组,而存储库数据地图则保存 JSF 视图的所有输入组件的转变和验证后的值。DataMapRepository 类的 saveValues() 方法是一种递归方法,可以遍历 JSF 组件树,用能实现 EditableValueHolder 的输入组件的值填充数据地图(参见 清单 4)。

  清单 4. 将 JSF 输入组件的值存储到数据地图

package autosave;
...
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
...
public class DataMapRepository ... {
    ...
    
public static void saveValues(UIComponent comp,
            Map
<String, Object> dataMap) {
        
if (comp == null)
            return;
        
if (comp instanceof EditableValueHolder) {
            
// Input component. Put its value into the data map
            EditableValueHolder evh
= (EditableValueHolder) comp;
            dataMap.put(comp.getId(), evh.getValue());
        }
        
// Iterate over the children of the current component
        Iterator children
= comp.getChildren().iterator();
        
while (children.hasNext()) {
            UIComponent child
= (UIComponent) children.next();
            
// Recursive call
            saveValues(child, dataMap);
        }
    }
    ...
}

  restoreValues() 方法(如 清单 5 所示)遍历此 JSF 组件树,恢复输入组件的值。此方法还清除了每个 EditableValueHolder 组件的 submittedValue 属性以便表单数据能恢复成地图数据,同时忽略所有的提交数据。saveValues() 方法在本文稍后的部分会用到,restoreValues() 会在本系列的第 3 部分用到。

  清单 5. 恢复 JSF 输入组件的值

package autosave;
...
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
...
public class DataMapRepository ... {
    ...
    
public static void restoreValues(UIComponent comp,
            Map
<String, Object> dataMap) {
        
if (comp == null || dataMap == null)
            return;
        
if (comp instanceof EditableValueHolder) {
            
// Input component. Get its value from the data map
            
// and clear any submitted value
            EditableValueHolder evh
= (EditableValueHolder) comp;
            evh.setValue(dataMap.get(comp.getId()));
            evh.setSubmittedValue(
null);
        }
        
// Iterate over the children of the current component
        Iterator children
= comp.getChildren().iterator();
        
while (children.hasNext()) {
            UIComponent child
= (UIComponent) children.next();
            
// Recursive call
            restoreValues(child, dataMap);
        }
    }

}

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

  如果用户登录,getDataMapId() 就会包含源自 Principal 对象的用户名。否则,用户就是匿名的,而且 getDataMapId() 使用的是浏览器 ID。JSF 视图 ID 是返回 ID 的一部分,对每个 JSF 页惟一。因此,此数据地图 ID 对每个用户和页面的组合而言也是惟一的。

  清单 6. 生成数据地图的惟一 ID

package autosave;
...
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
...
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.getUserPrincipal();
        
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 ID
        return userId
+ root.getViewId();
    }
    ...
}

  限制数据存储库的内存资源

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

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

  清单 7. 数据存储库类

package autosave;
...
import java.util.LinkedHashMap;
import java.util.Map;

public class DataMapRepository
        extends LinkedHashMap
<String, Map<String, Object>> {
    
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 maxDataMaps;
    }

    
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() 方法。

  清单 8. 恢复和检索数据地图所需的方法

package autosave;
...
public class DataMapRepository ... {
    ...
    
public Map<String, Object> getDataMap(FacesContext ctx) {
        
String id = getDataMapId(ctx);
        
if (id == null)
            return
null;
        return
get(id);
    }
    
    
public void setDataMap(FacesContext ctx, Map<String, Object> dataMap) {
        
String id = getDataMapId(ctx);
        
if (id == null)
            return;
        
if (dataMap != null)
            put(id, dataMap);
        
else
            remove(id);
    }
    ...
}
0
相关文章