当我使用UUID#randomUUID()
(使用SecureRandom)或RandomStringUtils#randomAlphabetic(int)
(使用Random)生成HtmlInputText的组件ID时,验证将停止工作。相反,如果我使用任意硬编码字符串(例如" C5d682a6f")设置组件ID,则验证按预期工作。这是代码:
import org.apache.commons.lang3.RandomStringUtils;
import java.util.UUID;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlMessage;
import javax.faces.component.html.HtmlPanelGrid;
@Model
public class LoginBean
{
private HtmlPanelGrid panelGrid;
private String email;
@PostConstruct void initialize()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
String componentId;
componentId = "C" + UUID.randomUUID().toString().substring(0, 8); // Yields something like "C5d682a6f", which should be fine, yet breaks validation.
//componentId = RandomStringUtils.randomAlphabetic(8); // Yields something like "zxYBcUYM", which should be fine, yet breaks validation.
//componentId = "C5d682a6f"; // Hard-coding the same exact kind of string generated by UUID#randomUUID() works fine.
//componentId = "zxYBcUYM"; // Hard-coding the same exact kind of string generated by RandomStringUtils#randomAlphabetic(int)
HtmlInputText emailFieldComponent = (HtmlInputText)facesContext.getApplication().createComponent(
facesContext,
HtmlInputText.COMPONENT_TYPE,
"javax.faces.Text"
);
emailFieldComponent.setId(componentId);
emailFieldComponent.setValueExpression(
"value",
facesContext.getApplication().getExpressionFactory().createValueExpression(
facesContext.getELContext(),
"#{loginBean.email}",
String.class
)
);
// The following validators stop working if UUID#randomUUID() or
// RandomStringUtils#randomAlphabetic(int) are used to generate componentId.
emailFieldComponent.setRequired(true);
emailFieldComponent.addValidator(new EmailValidator());
HtmlMessage message = (HtmlMessage)facesContext.getApplication().createComponent(
facesContext,
HtmlMessage.COMPONENT_TYPE,
"javax.faces.Message"
);
message.setFor(componentId);
panelGrid = (HtmlPanelGrid)facesContext.getApplication().createComponent(
facesContext,
HtmlPanelGrid.COMPONENT_TYPE,
"javax.faces.Grid"
);
panelGrid.setColumns(2);
panelGrid.getChildren().add(emailFieldComponent);
panelGrid.getChildren().add(message);
}
}
有关为何如此的任何想法?我只需要将componentId作为在运行时生成并符合以下约定的任意String(来自UIComponent#setId(String)
JavaDoc):
组件标识符必须遵守以下语法限制:
Must not be a zero-length String. First character must be a letter or an underscore ('_'). Subsequent characters must be a letter, a digit, an underscore ('_'), or a dash ('-').
组件标识符还必须遵守以下语义限制(请注意,setId()实现不强制执行此限制):
The specified identifier must be unique among all the components (including facets) that are descendents of the nearest ancestor UIComponent that is a NamingContainer, or within the scope of the entire component tree if there is no such ancestor that is a NamingContainer.
我的开发环境是Wildfly 8.1.0.Final上的Mojarra 2.2.6-jbossorg-4。
修改
因此,似乎任何在运行时创建组件ID的尝试都会导致验证失败。
componentId = "C" + Long.toHexString(Double.doubleToLongBits(Math.random()));
componentId = "C" + Long.toHexString(System.currentTimeMillis());
componentId = "C" + Long.toHexString(new Date().getTime());
componentId = "C" + new Date().hashCode();
如果在编译时知道组件ID,则验证发生就好了。
componentId = "C" + Long.toHexString(Double.doubleToLongBits(Double.MAX_VALUE));
我真的很想明白为什么会这样。
编辑#2:
以下工作正常(谢谢你,BalusC),在运行时生成的componentId,这正是我需要的:
setId(facesContext.getViewRoot().createUniqueId());
我遵循了BalusC的建议,看了UIViewRoot#createUniqueId()
,看起来就像这样:
public String createUniqueId() {
return createUniqueId(getFacesContext(), null);
}
public String createUniqueId(FacesContext context, String seed) {
if (seed != null) {
return UIViewRoot.UNIQUE_ID_PREFIX + seed;
} else {
Integer i = (Integer) getStateHelper().get(PropertyKeys.lastId);
int lastId = ((i != null) ? i : 0);
getStateHelper().put(PropertyKeys.lastId, ++lastId);
return UIViewRoot.UNIQUE_ID_PREFIX + lastId;
}
}
但我感到困惑,因为上述方法似乎没有将新客户端ID存储在JSF视图状态中。它只增加lastId并在视图状态中更新lastId。
答案 0 :(得分:1)
组件ID未存储在JSF视图状态中。它们就像组件本身,因此基本上是请求作用域。只有存储在JSF视图状态的东西基本上是视图范围的。即通过getStateHelper()
方法放置/获取组件的东西。 getId()
/ setId()
方法无法做到这一点。
当JSF需要处理回发请求时,它将在恢复视图阶段重建视图(即所有组件实例将重新创建,如new UIComponent()
等),因此组件将按照您的方式获得不同的客户端ID。此后,JSF将使用JSF视图状态的数据恢复组件树。
然后,当JSF需要处理应用请求值阶段时,它将使用客户端ID作为参数名称从HTTP请求参数映射中提取请求参数。但是,由于此客户端ID已更改,JSF无法找到最初提交的值。
发生在这里的事情。如何解决它是第二个。一个很好的起点是UINamingContainer#createUniqueId()
。