システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

JSFとCDIとBeanValidationで想定外だったこと

そもそも「いや、それJSFだと亜流ですので」って終わりそうな話だと思いますが、誰かの何かに役立つかもしれないので。

環境

  • Payara5

  • Java EE7

  • Java8

事象


Controller(もしくは ManagedBeanのAction相当)

@Named
@RequestScoped
public class UserRegistrationAction {

    private UserRegistrationPage registrationForm;

    private UserService userService;

    private ViewMessage viewMessage;

    public UserRegistrationAction() {
    }

    @Inject
    public UserRegistrationAction(UserRegistrationPage registrationForm, UserService userService, ViewMessage viewMessage) {
        this.registrationForm = registrationForm;
        this.userService = userService;
        this.viewMessage = viewMessage;
    }

    public String fwPersist() {
        this.registrationForm.init();
        return "persistedit.xhtml?faces-redirect=true";
    }

    public String confirm() {

        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

        //validateしても、対象のクラスフィールドに @Validと記述しても無視される
        //理由は、コンテナで生成したインスタンスのフィールドが nullだから。
        //ただし、アクセッサを経由したら値は取得できることは デバッグモードで確認済み
        Set<ConstraintViolation<Object>> results = validator.validate(registrationForm, ValidationPriority.class);
        this.viewMessage.appendMessage(results);
        if (results.isEmpty() == false) {
            return "persistedit.xhtml?faces-redirect=true";
        }

        return "persistconfirm.xhtml?faces-redirect=true";
    }

    public String register() {
        User requestUser = this.registrationForm.toUser();
        this.userService.register(requestUser);

        Optional<User> responseUser = this.userService.findByKey(requestUser);

        //とりあえず、登録要素が無いという有り得ない状況だったら 自画面にそのまま遷移させる(ありえないケース)
        if (responseUser.isPresent() == false) {
            return null;
        }

        this.registrationForm.update(responseUser.get());
        return "persistcomplete.xhtml?faces-redirect=true";
    }
}

Form(もしくはManagedBeanのActionFrom相当)

@Named
@SessionScoped
public class UserRegistrationPage implements Serializable {

    private static final long serialVersionUID = 1L;

    private UserId userId;


    private EmailForm userEmail;

    @Valid
    private NameForm name;

    @Valid
    private DateOfBirthForm dateOfBirth;

    @Valid
    private PhoneNumberForm phoneNumber;

    private GenderForm gender;

    public UserRegistrationPage() {
    }

(以下略)
public class NameForm implements DefaultForm<UserName>, Serializable {

    private static final long serialVersionUID = 1L;

    @NotBlank(groups = ValidationGroups.Form.class)
    private String value = "";

    public NameForm() {
    }

    public NameForm(String userName) {
        this.value = userName;
    }

    /**
     * @inheritDoc
     */
    @Override
    public String display() {
        return this.getValue().getValue();
    }

    /**
     * @inheritDoc
     */
    @Override
    public UserName getValue() {
        return new UserName(this.value);
    }
}

フィールドに@Validアノテーションしていても、対象クラス配下の@NotBlankが 有効にならず、次の画面に遷移してしまいます。

コード全量
Bitbucket

対応後


Controller(もしくは ManagedBeanのAction相当)

@Named
@RequestScoped
public class UserRegistrationAction {

    private UserRegistrationPage registrationForm;

    private UserService userService;

    private ViewMessage viewMessage;

    public UserRegistrationAction() {
    }

    @Inject
    public UserRegistrationAction(UserRegistrationPage registrationForm, UserService userService, ViewMessage viewMessage) {
        this.registrationForm = registrationForm;
        this.userService = userService;
        this.viewMessage = viewMessage;
    }

    public String fwPersist() {
        this.registrationForm.init();
        return "persistedit.xhtml?faces-redirect=true";
    }

    public String confirm() {

        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

        //CDI管理外のインスタンスを引数として検証を行えば @Valid が有効(普通のBeanValidationの挙動)
        Set<ConstraintViolation<Object>> results = validator.validate(registrationForm.getValidationPersistUser(), ValidationPriority.class);
        this.viewMessage.appendMessage(results);
        if (results.isEmpty() == false) {
            return "persistedit.xhtml?faces-redirect=true";
        }

        return "persistconfirm.xhtml?faces-redirect=true";
    }

    public String register() {
        User requestUser = this.registrationForm.toUser();
        this.userService.register(requestUser);

        Optional<User> responseUser = this.userService.findByKey(requestUser);

        //とりあえず、登録要素が無いという有り得ない状況だったら 自画面にそのまま遷移させる(ありえないケース)
        if (responseUser.isPresent() == false) {
            return null;
        }

        this.registrationForm.update(responseUser.get());
        return "persistcomplete.xhtml?faces-redirect=true";
    }
}

Form(もしくはManagedBeanのActionFrom相当)

@Named
@SessionScoped
public class UserRegistrationPage implements Serializable {

    //@Valid を外す(意味がないので)

    private static final long serialVersionUID = 1L;

    private UserId userId;

    private EmailForm userEmail;

    private NameForm name;

    private DateOfBirthForm dateOfBirth;

    private PhoneNumberForm phoneNumber;

    private GenderForm gender;

    public UserRegistrationPage() {
    }

    //(省略)


    public Object getValidationPersistUser() {
        ValidationPersistUser obj = new ValidationPersistUser();

        //private インナークラスなので、外部から更新されることは気にする必要は無いと考え
        //コンストラクタ実装など手間はかけずにフィールドに直接設定
        obj.userEmail = userEmail;
        obj.name = name;
        obj.dateOfBirth = dateOfBirth;
        obj.phoneNumber = phoneNumber;
        return obj;
    }

    // 追加Actionで必須検証が必要なフィールドだけを抜粋して検証を行う.
    // 本メソッドを見れば、検証対象が分かるので groups による指定よりも見通しは良い(と思う)
    private static class ValidationPersistUser {

        @Valid
        private EmailForm userEmail;

        @Valid
        private NameForm name;

        @Valid
        private DateOfBirthForm dateOfBirth;

        @Valid
        private PhoneNumberForm phoneNumber;

    }

(省略)

コード全量
Bitbucket

動機

vermeer.hatenablog.jp

追記メモ

改めて コメント、本当に ありがとうございます。

javax.inject.Providerは初見だったので、関連しそうなところの勉強用のリンク

toydi/README.md at master · tokuhirom/toydi · GitHub

Google Guice 使い方メモ

[dependency-injection] CDI注入ループ [circular-dependency] | CODE Q&A 問題解決 [日本語]

倖せの迷う森

さいごに

vermeer.hatenablog.jp

でも挙げた通り、これは私の実験場みたいなものなので、これがJSFの通常実装のパターンとは思わないでいただければと思います。