【问题标题】:JSF converter causes validator(s) to be ignoredJSF 转换器导致验证器被忽略
【发布时间】:2012-02-01 22:24:37
【问题描述】:

这是字段:

<h:inputText id="mobilePhoneNo"
             value="#{newPatientBean.phoneNo}"
             required="true"
             requiredMessage="Required"
             validator="#{mobilePhoneNumberValidator}"
             validatorMessage="Not valid (validator)"
             converter="#{mobilePhoneNumberConverter}"
             converterMessage="Not valid (converter)"
             styleClass="newPatientFormField"/>

验证器:

@Named
@ApplicationScoped
public class MobilePhoneNumberValidator implements Validator, Serializable
{
    @Override
    public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException
    {
        // This will appear in the log if/when this method is called.
        System.out.println("mobilePhoneNumberValidator.validate()");

        UIInput in = (UIInput) uic;
        String value = in.getSubmittedValue() != null ? in.getSubmittedValue().toString().replace("-", "").replace(" ", "") : "";

        if (!value.matches("04\\d{8}"))
        {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Please enter a valid mobile phone number.", null));
        }
    }
}

当我按下表单中的命令按钮时,我得到以下行为:

  • 当该字段为空白时,消息为“无效(转换器)”。
  • 当该字段具有有效条目时,消息为“无效(验证器)”。
  • 当字段中有无效条目时,消息为“无效(转换器)”。

在所有三种情况下,MobilePhoneNumberConverter.getAsObject() 都会被调用。 MobilePhoneNumberValidator.validate()从不被调用。当该字段为空时,它会忽略required="true" 属性并直接进行转换。

我原以为正确的行为是:

  • 当该字段为空白时,消息应为“必填”。
  • 当该字段有一个有效的条目时,应该没有任何消息。
  • 当该字段有无效条目时,消息应为“无效(验证器)”。
  • 如果有机会通过转换未通过验证,则消息应为“无效(转换器)”。

注意:支持 bean 是请求范围的,所以这里没有花哨的 AJAX 业务。

更新:

可能与将javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL 设置为true 有关吗?

【问题讨论】:

    标签: java jsf-2


    【解决方案1】:

    在阅读了 BalusC 的评论后,我再次更新了这篇文章。

    我创建了一个小型演示应用程序并查看各个阶段以及转换和验证发生的时间。

    查看:

    <h:form>
        <h:inputText value="#{demoBean.field}">
            <f:converter converterId="demoConverter"/>
            <f:validator validatorId="demoValidator"/>
        </h:inputText>
        <h:commandButton value="Submit" action="#{demoBean.demoAxn()}"/>
    </h:form>
    

    托管 bean:

    @ManagedBean
    @SessionScoped
    public class DemoBean implements Serializable {
        private String field;
    
        public DemoBean() {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
        }
    
        public String getField() {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
            return field;
        }
    
        public void setField(String field) {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
            this.field = field;
        }
    
        public String demoAxn() {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
            return null;
        }
    }
    

    转换器:

    @FacesConverter(value="demoConverter")
    public class DemoConverter implements Converter {
    
        @Override
        public Object getAsObject(FacesContext context, UIComponent component, String value) {
            System.out.println(Thread.currentThread().getStackTrace()[1]);            
            return value;
        }
    
        @Override
        public String getAsString(FacesContext context, UIComponent component, Object value) {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
            return (String) value;
        }    
    }
    

    验证器:

    @FacesValidator(value="demoValidator")
    public class DemoValidator implements Validator {
    
        @Override
        public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
        }
    
    }
    

    相位监听器:

    public class DemoPhaseListener implements PhaseListener {
        @Override
        public void afterPhase(PhaseEvent event) {
            System.out.println(Thread.currentThread().getStackTrace()[1]);
            System.out.println("PhaseId: " + event.getPhaseId() + "  ===============================\n\n");        
        }
    
        @Override
        public void beforePhase(PhaseEvent event) {
            System.out.println("\n\nPhaseId: " + event.getPhaseId() + "  ===============================");
            System.out.println(Thread.currentThread().getStackTrace()[1]);        
        }
    
        @Override
        public PhaseId getPhaseId() {
            return PhaseId.ANY_PHASE;
        }    
    }
    

    注册了阶段监听器:

    <lifecycle>
        <phase-listener>pkg.DemoPhaseListener</phase-listener>
    </lifecycle>
    

    当点击“提交”按钮时,使用该设置,输出为:

    信息:PhaseId:RESTORE_VIEW 1 =============================== 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:RESTORE_VIEW 1 =============================== 信息:PhaseId:APPLY_REQUEST_VALUES 2 =============================== 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:APPLY_REQUEST_VALUES 2 =============================== 信息:PhaseId:PROCESS_VALIDATIONS 3 ================================ 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoConverter.getAsObject(DemoConverter.java:13) 信息:pkg.DemoValidator.validate(DemoValidator.java:14) 信息:pkg.DemoBean.getField(DemoBean.java:17) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:PROCESS_VALIDATIONS 3 ================================ 信息:PhaseId:UPDATE_MODEL_VALUES 4 =============================== 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoBean.setField(DemoBean.java:22) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:UPDATE_MODEL_VALUES 4 =============================== 信息:PhaseId:INVOKE_APPLICATION 5 ================================ 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoBean.demoAxn(DemoBean.java:27) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:INVOKE_APPLICATION 5 ================================ 信息:PhaseId:RENDER_RESPONSE 6 ================================ 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoBean.getField(DemoBean.java:17) 信息:pkg.DemoConverter.getAsString(DemoConverter.java:20) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:RENDER_RESPONSE 6 ================================

    但是当做出如下改变以在转换器中抛出 NPE 时:

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println(Thread.currentThread().getStackTrace()[1]);            
        throw new NullPointerException();
    }
    

    输出是:

    信息:PhaseId:RESTORE_VIEW 1 =============================== 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:RESTORE_VIEW 1 =============================== 信息:PhaseId:APPLY_REQUEST_VALUES 2 =============================== 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:APPLY_REQUEST_VALUES 2 =============================== 信息:PhaseId:PROCESS_VALIDATIONS 3 ================================ 信息:pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17) 信息:pkg.DemoConverter.getAsObject(DemoConverter.java:13) 信息:pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10) 信息:PhaseId:PROCESS_VALIDATIONS 3 ================================ 信息:pkg.DemoBean.getField(DemoBean.java:17)

    但堆栈跟踪显示在结果视图中。

    【讨论】:

    • 我不知道这里发生了什么...我将转换器和验证器更改为使用 JSF 2.0 方式进行注释。不久前有一段时间,这完全没有区别。也许 GlassFish 3.1.1 正在惹恼我。无论如何,使用“正确”注释,required 属性仍然不起作用,而是直接进行转换。事实上,现在绝对没有发生验证的情况。我是否应该认为这意味着转换器会导致跳过验证?
    • “我应该认为这意味着转换器会导致验证被跳过吗?”不。他们做不同的不相关的事情,我们应该能够一起运行它们。但是,如果发生故障,jsf 会停止请求处理并跳过其他阶段并跳转到呈现响应。'
    • “不。他们做不同的不相关的事情,我们应该能够一起运行它们。”我亲眼所见——他们没有一起工作。 “但是,如果发生故障,jsf 会停止请求处理并跳过其他阶段并跳转到呈现响应。”但是,这有什么关系?验证和转换发生在同一阶段,jsf 将至少运行此阶段完成(验证所有提交的值,因此显示所有验证/转换错误)之前跳过剩余的阶段。
    • 转换发生在“应用请求值”阶段,而验证发生在“过程验证”阶段。
    • 您将 EL 强制转换与 JSF 转换混淆了。 EL 强制在应用请求值阶段运行,JSF 转换在验证阶段运行,就在 JSF 验证之前。
    【解决方案2】:

    转换发生在验证之前。当值为null 或为空时,也会调用转换器。如果您想将null 值委托给验证器,那么您需要设计转换器,当提供的值为null 或为空时,它只返回null

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.trim().isEmpty()) {
            return null;
        }
    
        // ...
    }
    

    与具体问题无关,您的验证器存在缺陷。您不应该从组件中提取提交的值。它与转换器返回的值相同。正确提交和转换的值已经作为第三个方法参数可用。

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        if (value == null) {
            return; // This should normally not be hit when required="true" is set.
        }
    
        String phoneNumber = (String) value; // You need to cast it to the same type as returned by Converter, if any.
    
        if (!phoneNumber.matches("04\\d{8}")) {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Please enter a valid mobile phone number.", null));
        }
    }
    

    【讨论】:

    • 感谢@BalusC。尽管很晚才出现并且基本上总结了 βнɛƨн Ǥʋяʋиɢ 的答案(我已经接受)的冗长对话,但我宁愿最有用的答案是为了其他遇到这个问题的人的利益而标记为已接受的答案。对不起 βнɛƨн Ǥʋяʋиɢ :(
    • 不客气。请注意,我在仔细查看了您的验证器实现后更新了答案,这本质上也是错误的。
    • @BalusC:感谢您纠正我。因为,完全误导我正在编辑我的答案。
    • 感谢@BalusC 的验证更正。现在一切都很清楚了。转换器进行转换,因此验证器不必进行转换。留下的是验证器来检查已经转换的值是否在某种可接受的范围内。我想我混淆了可转换性的概念(即字符串参数是 X 类型对象类型的有效表示)与验证(即对象(不是它的字符串表示,已经假定有效)是否具有特定用途的有效状态?
    • 转换器只是将提交的String值更改为不同的对象类型(或更改为不同表示形式的String)。如果转换器由于某种原因无法完成转换工作,那么它应该抛出一个ConverterException。验证器只是检查是否允许处理提交和转换的对象。例如。一定的长度、范围、值等。在电话号码的情况下,转换器最多只能用于修剪前导/尾随空格,而验证器可用于验证语法/格式。如果无效,则必须抛出 ValidatorException
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-23
    • 1970-01-01
    • 2013-05-20
    • 2018-05-15
    • 2013-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多