为什么 JSF 应用阴影私有字段的 Bean 验证?

Why is JSF applying the Bean Validation of a shadowed private field?

我在 Hibernate Validator 和 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>

我有这个支持 bean:

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);
        }

    }
}

请注意 SomeSubClass.someField 与父 class 中的私有变量同名。这应该无关紧要,因为您不能隐藏私有字段。

注意两点:

  1. 如果您 运行 BackingBean 中的 main 方法,验证器将始终 return 验证错误(消息 "may not be empty"),因为 SomeSubClass.someField 始终为空。这种行为对我来说似乎是正确的。
  2. 如果您提交带有空白值的表单,您将收到 "may not be empty" 错误消息;如果你输入一个值,它将通过验证,但你会在控制台中看到 SomeSubClass.someField 的值仍然为空。这种行为对我来说似乎是不正确的。
    • 如果您将 SomeSubClass.someField 的名称更改为 someField2,您可以提交带有空值的表单。这种行为对我来说似乎是不正确的。

看来 JSF 的验证阶段和 Hibernate 验证器不同意这种行为。 JSF 将子 class 中私有字段的 @NotEmpty 验证器应用于父 class 中的同名字段,但 Hibernate Validator 在单独测试时不会显示此行为.

任何人都可以解释这种行为吗?是bug,还是自己的预期有误?

使用:

JSF 使用 EL get/set 模型值符合 Javabean 规则。 EL 使用反射来检查和调用 public getter 和 setter。换句话说,#{backingBean.someClass.someField} 实际上使用 getSomeField()setSomeField() 来 get/set model/submitted 值。被操作的字段实际上是 SomeClass.

中的字段

Bean Validation 使用反射来检查字段并忽略 public getters/setters 的存在。也就是说,#{backingBean.someClass.someField}的BV实际上发生在SomeSubClass

这说明了一切。但我同意这令人困惑并出现 "incorrect"。当您还覆盖 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 也支持这种结构,所以它会继续工作。