Почему JSF применяет Bean Validation к теневому частному полю?

Я столкнулся с неожиданным поведением в 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>

И у меня есть этот компонент поддержки:

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 имеет то же имя, что и частная переменная в родительском классе. Это не имеет значения, потому что вы не можете затенять частное поле.

Обратите внимание на две вещи:

  1. Если вы запустите метод main в BackingBean, валидатор всегда будет возвращать ошибку проверки (сообщение «может быть не пустым»), потому что SomeSubClass.someField всегда имеет значение NULL. Мне такое поведение кажется правильным.
  2. If you submit the form with a blank value, you will receive a "may not be empty" error message; if you enter a value, it will pass validation, but you will see in the console that the value of SomeSubClass.someField is still null. This behaviour seems incorrect to me.
    • If you change the name of SomeSubClass.someField to someField2, you may submit the form with an empty value. This behaviour seems incorrect to me.

Похоже, что фаза проверки JSF и валидатор Hibernate не согласны с этим поведением. JSF применяет валидатор @NotEmpty частного поля в подклассе к полю с тем же именем в родительском классе, но Hibernate Validator не показывает это поведение при изолированном тестировании.

Кто-нибудь может объяснить такое поведение? Это ошибка или недоразумение в моих ожиданиях?

С использованием:

  • Сервер GlassFish с открытым исходным кодом, версия 3.1.2.2
  • Мохарра 2.1.6
  • Валидатор гибернации 4.3.0.Final

person DavidS    schedule 19.01.2015    source источник
comment
@BalusC, спасибо за редактирование. После вашего комментария я протестировал Hibernate Validator изолированно и обнаружил, что его поведение правильное, насколько мне известно. Я добавил сравнение фазы проверки JSF с валидатором Hibernate к моему вопросу, который демонстрирует проблему.   -  person DavidS    schedule 20.01.2015


Ответы (1)


JSF использует EL для получения / установки значений модели в соответствии с правилами Javabean. EL использует отражение для проверки и вызова общедоступных методов получения и установки. Другими словами, #{backingBean.someClass.someField} фактически использует getSomeField() и setSomeField() для получения / установки модели / отправленного значения. Таким образом, обрабатываемое поле фактически совпадает с полем в SomeClass.

Bean Validation использует отражение для проверки полей и игнорирует наличие общедоступных методов получения / установки. Другими словами, BV #{backingBean.someClass.someField} фактически имеет место SomeSubClass.

Это все объясняет. Но я согласен, что это сбивает с толку и кажется «неправильным». Он будет работать должным образом, если вы также переопределите геттер и сеттер в 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;
    }
}

Таким образом, EL увидит это и будет использовать в качестве модельного значения.

Однако это неудобно (@Override, который просто вызывает super, перевернет инструменты статического анализа стиля кода, такие как Sonar, PMD, Findbugs и т. Д.). Лучшая альтернатива - переместить @NotEmpty в переопределенный получатель:

public static class SomeSubClass extends SomeClass {

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

    @Override
    @NotEmpty
    public String getSomeField() {
        return super.getSomeField();
    }
}

BV также поддерживает эту конструкцию, поэтому она продолжит работать.

person BalusC    schedule 20.01.2015
comment
Дай мне посмотреть, понимаю ли я. Перед установкой значений модели JSF ищет валидатор, используя механизм поиска Bean Validation (который использует отражение), а затем JSF передает отправленное значение валидатору. Если он проходит проверку, устанавливаются значения модели. Это контрастирует с установкой значений в модели и последующим применением Bean Validation ко всей модели, и в этом случае @NotEmpty на SomeSubClass.someField всегда терпит неудачу. Это правильно? - person DavidS; 21.01.2015
comment
Действительно, EL всегда обращается к значениям модели через геттеры / сеттеры, а не через поля. Более того, поле не обязательно должно существовать для EL. - person BalusC; 21.01.2015