Java 通过postValidate进行JSF跨域验证,无需在支持bean中按名称查找组件

Java 通过postValidate进行JSF跨域验证,无需在支持bean中按名称查找组件,java,validation,jsf,jsf-2,composite-component,Java,Validation,Jsf,Jsf 2,Composite Component,我正在构建一个登录表单复合组件。使用它的页面将传递一个验证用户名和密码的事件处理程序。通常(不使用复合组件)当我们通过postValidate执行跨字段验证时,事件处理程序必须按名称查找字段的组件。验证器最好不要这样做,因为这些是应该抽象的组件的内部细节 知道如何在不知道复合组件内部细节的情况下,在postValidate处理程序中获取用户名和密码字段的转换值吗 更新: 这一点并不是为了避免按名称查找组件,而是为了能够跨字段验证复合组件的字段,而不需要使用页面和/或bean来了解组件的内部细节。

我正在构建一个登录表单复合组件。使用它的页面将传递一个验证用户名和密码的事件处理程序。通常(不使用复合组件)当我们通过
postValidate
执行跨字段验证时,事件处理程序必须按名称查找字段的组件。验证器最好不要这样做,因为这些是应该抽象的组件的内部细节

知道如何在不知道复合组件内部细节的情况下,在
postValidate
处理程序中获取用户名和密码字段的转换值吗

更新:
这一点并不是为了避免按名称查找组件,而是为了能够跨字段验证复合组件的字段,而不需要使用页面和/或bean来了解组件的内部细节。

f:event postValidate方法不会给您留下很多选项

我更喜欢的选项是对表单的最后一个组件进行验证,然后使用binding和f:attribute传入其他组件

比如说

<h:inputText id="field1" binding="#{field1}" ... />
<h:inputText id="field2" validator="#{...}">
  <f:attribute name="field1" value="#{field1}"/>
</h:inputText>

这是可以做到的。在以下代码中,请特别注意复合组件中的
postValidate
事件和支持组件中的
postValidate
方法。注意它如何解析
MethodExpression
属性并调用传入的方法

下面是复合组件:

支持组件:

@FacesComponent(“com.example.LoginForm”)
公共类LoginFormComponent扩展UIInput实现NamingContainer
{
@凌驾
受保护对象getConvertedValue(FacesContext上下文,对象newSubmittedValue)引发ConverterException
{
UIInput emailAddressComponent=(UIInput)findComponent(电子邮件地址ID);
UIInput passwordComponent=(UIInput)findComponent(密码\u ID);
字符串emailAddress=(字符串)emailAddressComponent.getValue();
字符串密码=(字符串)passwordComponent.getValue();
返回新的LoginFormValue(电子邮件地址、密码);
}
公共无效后验证(组件SystemEvent e){
FacesContext ctx=getFacesContext();
//如果用户名和/或密码字段无效,则不验证凭据。
如果(!ctx.getMessageList(EMAIL_ADDRESS_ID).isEmpty()||!ctx.getMessageList(PASSWORD_ID).isEmpty())
{
返回;
}
LoginFormValue=(LoginFormValue)getConvertedValue(null,null);
MethodExpression checkCredentials=(MethodExpression)getAttributes().get(检查凭据属性名称);
checkCredentials.invoke(ctx.getELContext(),新对象[]{getClientId(),value.getEmailAddress(),value.getPassword()});
}
@凌驾
公共字符串getFamily()
{
返回“javax.faces.NamingContainer”;
}
公共静态最终字符串检查\u凭证\u属性\u NAME=“checkCredentials”;
公共静态最终字符串EMAIL\u ADDRESS\u ID=“form:emailAddress”;
公共静态最终字符串密码\u ID=“form:PASSWORD”;
}
LoginFormValue
类的完整性:

公共类LoginFormValue
{
公共LoginFormValue(字符串电子邮件地址、字符串密码)
{
this.emailAddress=电子邮件地址;
this.password=密码;
}
公共字符串getEmailAddress()
{
返回电子邮件地址;
}
公共字符串getPassword()
{
返回密码;
}
私有字符串电子邮件地址;
私有字符串密码;
}
使用登录表单的页面:


登录
最后,页面的支持bean:

@Named
@RequestScoped
public class LoginBean implements Serializable
{
    public String getEmailAddress()
    {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress)
    {
        this.emailAddress = emailAddress;
    }

    public boolean isRememberMe()
    {
        return rememberMe;
    }

    public void setRememberMe(boolean rememberMe)
    {
        this.rememberMe = rememberMe;
    }

    /** Action listener for login-form. Called after validation passes. */
    public void submit()
    {
        User user = userDao.findByEmailAddress(emailAddress);
        userRequestBean.login(user.getUserId());

        // Remember me
        if (!rememberMe)
        {
            return;
        }

        // Handle rememberMe here (create a cookie, etc.)
    }

    /** Called by the backing component's postValidate event handler */
    public void checkCredentials(String clientId, String emailAddress, String password)
    {
        if (!securityEjb.checkCredentials(emailAddress, password))
        {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Incorrect email address/password", null);
            FacesContext ctx = FacesContext.getCurrentInstance();
            ctx.addMessage(clientId, message);
            ctx.renderResponse();
        }
    }

    private String emailAddress = "";

    private boolean rememberMe = true;

    @Inject
    private UserRequestBean userRequestBean;

    @EJB
    private SecurityEjb securityEjb;

    @EJB
    private UserDao userDao;

    @EJB
    private LoginCookieDao loginCookieDao;
}
我曾经用过一个类似的问题,我认为它以一种更干净的方式解决了你的问题

与JSF验证器不同,WarningValidator是在模型更新后的呈现响应之前执行的,应用程序已经被调用,因此您可以简单地访问应用程序以获得验证结果

@Named
public class BarWarningValidator implements WarningValidator{

    @Inject
    private MyValidationBean validationBean;

    @Override
    public void process(FacesContext context, UIInput component, ValidationResult validationResult) {
        if(!validationBean.isBarValid()) {
            validationResult.setFacesMessage(new FacesMessage(FacesMessage.SEVERITY_WARN, "FooBar", "This is a warning."));
        }
    }
}
并将验证程序添加到目标字段:

<h:outputLabel for="bar" value="Default warning:" />
<h:inputText id="bar">
    <jw:warning validator="#{barWarningValidator}" />
    <f:ajax event="change" render="..." />
</h:inputText>
<h:message for="bar" />


Hmmm。。。这也打破了抽象。我想知道是否可以在复合组件上定义一个双参数(user/pass)方法属性,并从组件的支持类中的事件调用它。组件ID仍然在
postValidate
处理程序方法中硬编码。那么,你是说问题不在于硬编码的ID,而在于它们在哪个类中被引用?
postValidate
处理程序方法位于支持组件内部,而不是使用它的页面的支持bean。所以它被很好地藏起来,看不见。我可以打包
<h:outputLabel for="bar" value="Default warning:" />
<h:inputText id="bar">
    <jw:warning validator="#{barWarningValidator}" />
    <f:ajax event="change" render="..." />
</h:inputText>
<h:message for="bar" />