为什么JSF要对隐藏的私有字段应用Bean验证?

为什么JSF要对隐藏的私有字段应用Bean验证?,jsf,jakarta-ee,bean-validation,hibernate-validator,Jsf,Jakarta Ee,Bean Validation,Hibernate Validator,我在Hibernate验证器和JSF中遇到了一些令人惊讶的行为。我想知道这种行为是一种错误,还是我自己期望的一种误解 我有这个Facelets页面: <?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> &

我在Hibernate验证器和JSF中遇到了一些令人惊讶的行为。我想知道这种行为是一种错误,还是我自己期望的一种误解

我有这个Facelets页面:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:body>
        <h:form>
            <h:inputText value="#{backingBean.someClass.someField}"/>
            <h:commandButton value="submit" action="#{backingBean.submit1()}"/>
        </h:form>
    </h:body>
</html>

我有一个后盾:

import java.util.Set;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.hibernate.validator.constraints.NotEmpty;

@ManagedBean
@RequestScoped
public class BackingBean {

    private SomeClass someClass = new SomeSubClass();

    public SomeClass getSomeClass() {
        return someClass;
    }

    public void setSomeClass(SomeClass someClass) {
        this.someClass = someClass;
    }

    public void submit1() {
        System.out.println("BackingBean: " + someClass.getSomeField());
        ((SomeSubClass) someClass).submit2();
    }

    public static class SomeClass {

        private String someField;

        public String getSomeField() {
            return someField;
        }

        public void setSomeField(String someField) {
            this.someField = someField;
        }
    }

    public static class SomeSubClass extends SomeClass {

        @NotEmpty
        private String someField;

        private void submit2() {
            System.out.println("SomeSubClass: " + someField);
        }
    }

    public static void main(String[] args) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        SomeClass someClass = new SomeSubClass();
        someClass.setSomeField("ham");
        Set<ConstraintViolation<SomeClass>> errors = validator.validate(someClass);
        for (ConstraintViolation<SomeClass> error : errors) {
            System.out.println(error);
        }

    }
}
import java.util.Set;
导入javax.faces.bean.ManagedBean;
导入javax.faces.bean.RequestScope;
导入javax.validation.ConstraintViolation;
导入javax.validation.validation;
导入javax.validation.Validator;
导入javax.validation.ValidatorFactory;
导入org.hibernate.validator.constraints.NotEmpty;
@ManagedBean
@请求范围
公共类BackingBean{
private SomeClass SomeClass=new SomeSubClass();
公共SomeClass getSomeClass(){
返回某个类;
}
公共无效设置SomeClass(SomeClass SomeClass){
this.someClass=someClass;
}
公开无效提交1(){
System.out.println(“BackingBean:+someClass.getSomeField());
((SomeSubClass)someClass.submit2();
}
公共静态类{
私有字符串字段;
公共字符串getSomeField(){
返回someField;
}
public void setSomeField(字符串someField){
this.someField=someField;
}
}
公共静态类SomeSubClass扩展了SomeClass{
@空空如也
私有字符串字段;
私人无效提交2(){
System.out.println(“SomeSubClass:+someField”);
}
}
公共静态void main(字符串[]args){
ValidatorFactory=Validation.buildDefaultValidatorFactory();
Validator Validator=factory.getValidator();
SomeClass SomeClass=新的SomeSubClass();
someClass.setSomeField(“ham”);
Set errors=validator.validate(someClass);
for(ConstraintViolation错误:错误){
系统输出打印项次(错误);
}
}
}
请注意,
SomeSubClass.someField
与父类中的私有变量具有相同的名称。这不重要,因为您不能对私有字段进行阴影处理

有两点需要注意:

  • 如果在
    BackingBean
    中运行
    main
    方法,验证程序将始终返回验证错误(消息“可能不为空”),因为
    SomeSubClass.someField
    始终为空。我认为这种行为是正确的
  • 如果您提交的表单值为空,您将收到“不得为空”错误消息;如果您输入一个值,它将通过验证,但您将在控制台中看到
    SomeSubClass.someField
    的值仍然为空。我觉得这种行为不正确。
    • 如果将
      SomeSubClass.someField
      的名称更改为
      someField2
      ,则可以提交带有空值的表单。我觉得这种行为不正确
  • JSF的验证阶段和Hibernate验证器似乎不同意这种行为。JSF正在将子类中私有字段的
    @NotEmpty
    验证器应用于父类中同名字段,但Hibernate验证器在单独测试时不会显示此行为

    有人能解释这种行为吗?这是一个错误,还是我自己的期望中的误解

    使用:

    • GlassFish服务器开源版本3.1.2.2
    • 莫哈拉2.1.6
    • Hibernate验证程序4.3.0.Final

      • JSF使用EL获取/设置符合Javabean规则的模型值。EL使用反射来检查和调用公共getter和setter。换句话说,
        {backingBean.someClass.someField}
        实际上使用
        getSomeField()
        setSomeField()
        来获取/设置模型/提交的值。被操纵的字段实际上就是
        SomeClass
        中的字段

        Bean验证使用反射来检查字段,并忽略公共getter/setter的存在。换句话说,
        {backingBean.someClass.someField}
        的BV实际上发生在
        SomeSubClass

        public static class SomeSubClass extends SomeClass {
        
            @NotEmpty
            private String someField;
        
            private void submit2() {
                System.out.println("SomeSubClass: " + someField);
            }
        
            @Override
            public String getSomeField() {
                return someField;
            }
        
            @Override
            public void setSomeField(String someField) {
                this.someField = someField;
            }
        }
        
        这解释了一切。但我同意这是令人困惑的,似乎“不正确”。当您在
        SomeSubClass
        中重写getter和setter时,它将按预期工作

        public static class SomeSubClass extends SomeClass {
        
            @NotEmpty
            private String someField;
        
            private void submit2() {
                System.out.println("SomeSubClass: " + someField);
            }
        
            @Override
            public String getSomeField() {
                return someField;
            }
        
            @Override
            public void setSomeField(String someField) {
                this.someField = someField;
            }
        }
        
        这样,EL将看到它并将其用作模型值

        然而,这很尴尬(一个只调用super的
        @Override
        将翻转静态代码风格的分析工具,如Sonar、PMD、Findbugs等)。更好的替代方法是将
        @NotEmpty
        移动到被重写的getter:

        public static class SomeSubClass extends SomeClass {
        
            private void submit2() {
                System.out.println("SomeSubClass: " + getSomeField());
            }
        
            @Override
            @NotEmpty
            public String getSomeField() {
                return super.getSomeField();
            }
        }
        

        BV也支持此构造,因此它将继续工作。

        @BalusC,感谢您的编辑。在您的评论之后,我单独测试了Hibernate验证器,发现就我而言,它的行为是正确的。我已经在我的问题中添加了一个JSF验证阶段与Hibernate Validator的比较,它演示了这个问题。让我看看我是否理解。在设置模型值之前,JSF使用Bean Validation的查找机制(使用反射)查找验证器,然后JSF将提交的值传递给验证器。如果通过验证,则设置模型值。这与在模型上设置值,然后对整个模型应用Bean验证形成对比,在这种情况下,
        SomeSubClass.someField
        上的
        @NotEmpty
        总是失败。确实,EL总是通过getter/setter访问模型值,而不是通过字段。此外,对于EL来说,字段不一定存在。