当前位置: 首页 > 知识库问答 >
问题:

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

纪俊良
2023-03-14

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

    }
}
  • 如果在backingbean中运行main方法,则验证器将始终返回验证错误(消息“可能不是空的”),因为someSubClass.SomeField始终为空。这种行为在我看来是正确的。
  • 如果以空白值提交表单,则会收到“可能不为空”的错误消息;如果您输入了一个值,它将通过验证,但您将在控制台中看到SOMESubClass.SomeField的值仍然为NULL。这种行为在我看来是不正确的。
    • 如果将SomeSubClass.SomeField的名称更改为SomeField2,则可以以空值提交表单。这种行为在我看来是不对的。

    JSF的验证阶段和Hibernate验证器似乎对此行为不一致。JSF将子类中私有字段的@notempty验证器应用于父类中同名字段,但在隔离测试时,Hibernate验证器不会显示这种行为。

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

    使用:

    • GlassFish服务器开源版3.1.2.2
    • Mojarra 2.1.6
    • Hibernate验证器4.3.0.final

共有1个答案

韩祯
2023-03-14

JSF使用EL获取/设置符合Javabean规则的模型值。EL使用反射来检查和调用公共getter和setter。换句话说,#{BackingBean.SomeClass.SomeField}实际上使用GetSomeField()SetSomeField()来获取/设置模型/提交的值。被操作的字段实际上是someclass中的字段。

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

这说明了一切。但我同意,这是令人困惑的,似乎是“不正确的”。当您还重写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就会看到它并使用它作为模型值。

但这很尴尬(@override只调用super就会翻转静态代码风格分析工具,如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也支持这个构造,所以它会继续工作。

 类似资料:
  • 我被这个概念困住了。 这是我在一个站点上看到的解释的一部分: 隐藏实现 我是这样想象的: 库客户机是否知道这个实现有什么区别?

  • 如果调用,将得到一个空列表。 我尝试在Java中,在Kotlin中使用kotlin-reflect库,但没有成功。 我还尝试了https://github.com/ronmamo/reflections库进行反射,我得到了同样的结果。 你知道如何访问那个私有字段或者如何使用反射扩展一个隐藏的抽象类吗?

  • 约书亚·布洛赫在高效的Java中写道: 请注意,非零长度数组总是可变的,因此类具有公共静态最终数组字段或返回此类字段的访问器是错误的。如果类具有这样的字段或访问器,客户端将能够修改数组的内容。这是安全漏洞的常见来源: 请注意,许多IDE生成的访问器会返回对私有数组字段的引用,这恰恰导致了这个问题。有两种方法可以解决这个问题。您可以将公共数组设为私有,并添加公共不可变列表: 或者,可以将数组设为私有

  • 我一直在阅读PHP表单处理教程,他们建议在表单中使用隐藏字段,这样PHP就可以使用来检测表单是提交的还是只显示的。 但是,在使用记事本使用并逐步浏览代码并观察变量之后,我不清楚为什么我们不能直接使用并完全删除隐藏字段。 毕竟,似乎没有一次在不设置其他表单字段的情况下设置隐藏字段(即使它们是空的)。第一次通过时,当表单显示时,在提交之前,变量已经存在,但它是空的。我想我们也可以使用查看表单是否已提交

  • 问题内容: 在以下情况下: 学生“隐藏人的ID字段。 如果我们想在内存中表示以下内容: 约翰对象会为storint Person.ID及其自己拥有两个单独的存储位置吗? 问题答案: 正确。示例中的每个类都有其自己的int IDid字段。 您可以通过以下方式从子类中读取或分配值: 或在外部(当它们是公开的时):

  • 问题内容: 在大学里学习时,我不得不做一些难看的Java基础知识,例如不使用封装就可以工作,同一类中的主要方法等。(我不想在Java样式指南上展开讨论,我只是想澄清一下,我不会在大学以外写这样的东西) 我偶然发现了一种我无法向自己解释的行为: 为什么这段代码可以编译并正确运行?我怎么可能访问私有字段?由于主类位于同一类中,因此行为异常? 问题答案: 由于静态方法是类的成员,因此可以访问中的任何私有