読者です 読者をやめる 読者になる 読者になる

Java EE 事始め!

主にJava EEについて、つらつらとマイペースに書いていきます。「Java EEを勉強するときに、一番最初に読んでもらえるブログ」を目指して頑張ります!

JSR 371(MVC 1.0) Early Draft version 1.0 の日本語訳(中編)

Java EE 8 MVC 1.0

この記事について

Java EE 8の新機能であるMVC 1.0のJSR 371 Early Draft Release 1 を、英語の勉強がてら日本語訳してみました。
今回は中編として、第3章~第4章を訳します。第5章~第6章は、後編とします。
前編(第1章~第2章)はコチラ↓
JSR 371(MVC 1.0) Early Draft version 1.0 の日本語訳(前編) - Java EE 事始め!
後編(第5章~第6章)はコチラ↓
JSR 371(MVC 1.0) Early Draft version 1.0 の日本語訳(後編) - Java EE 事始め!

Chapter 3 例外ハンドリング

この章では、MVC APIにおける例外ハンドリングについて述べます。例外ハンドリングは、JAX-RSにより提供されている仕組みに基づくものです。しかし、HTMLフォームポストで一般的なバリデーション例外を追加サポートしています。

3.1 例外マッパー

MVCコントローラーにおける通常の例外ハンドリングの仕組みは、JAX-RS仕様のリソースメソッドのために定義されたものと全く同じです。一言で言うと、アプリケーションは例外をレスポンスに変換する例外マッピングプロバイダーを実装できます。もし、特定の例外型の例外マッパーが見つからなかった場合、デフォルトのルールが適用されます。すなわち、例外がチェックか非チェックか、レスポンスを含むWebApplicationExceptionの特殊なケースの追加ルールが利用されるかです。詳しくはJAX-RSの仕様を参照してください。
Bean Validationで失敗した際の結果としてスローされるConstraintViolationExceptionのケースを考えます。

@Controller
@Path("form")
public class FormController {

  @POST
  public Response formPost(@Valid @BeanParam FormDataBean form) {
    return Response.status(OK).entity("data.jsp").build();
  }
}

formPostメソッドはFormDataBeanを引数にインジェクトしています。例と同じように、FormDataBeanは@Min(18)、@Size(min=1)などの制約を含むと仮定します。@Validが付加されていることにより、全HTMLフォームポストのバリデーションが起動します。もしバリデーションが失敗した場合、ConstraintViolationException(ValidationExceptionのサブクラス)がスローされます。
アプリケーションは、下記の例外マッパーを含めることで、例外をハンドリングできます。

public class FormViolationMapper
    implements ExceptionMapper<ConstraintViolationException> {

  @Inject
  private ErrorDataBean error;

  @Override
  public Response toResponse(ConstraintViolationException e) {
    final Set<ConstraintViolation<?>> set = e.getConstraintViolations();
    if (!set.isEmpty()) {
      // fill out ErrorDataBean ...
    }
    return Response.status(Response.Status.BAD_REQUEST)
      .entity("error.jsp").build();
  }
}

この例外マッパーはErrorDataBeanのインスタンスを変更し、例外のヒューマンフレンドリーな説明を供給することへの意図する(メソッドシグネチャで要求されているレスポンスでラップされた)error.jspビューを返します。
一般に、例外マッパーを使うことは例外をハンドリングする便利な方法ですが、よりよい方法が必要な場合もあります。上記のマッパーは、アプリケーションでスローされたConstraintViolationException全インスタンスに実行されます。アプリケーションは複数のフォームポストコントローラーを含むことができるので、1つのメソッドによってすべての例外をハンドリングすることは、コントローラー個別のカスタマイズをすることを難しくします。更に、例外マッパーは(一部の妥当な)バインドデータ、または上記の例のFormDataBeanにアクセスできません。

3.2 バリデーション例外

MVCは、3.1で説明したものに代わる例外ハンドリングの仕組みを提供します。例外ハンドリングをバインドされたデータにアクセスできない1つの場所に集中させるよりも、コントローラーメソッドが例外ハンドラとして振舞えるようにします。言い換えれば、コントローラーメソッド自身がエラーハンドリングできるように宣言されていれば、パラメータバリデーションが失敗しても、コントローラーメソッドが呼び出されます。
フィールドまたはプロパティとしてjavax.mvc.validation.ValidationResultが(直接、または継承を通して間接的に)定義されているコントローラークラスは、パラメータの検証中にバリデーションエラーが発生しても、コントローラーメソッドが呼び出されます。実装は、正しい意味論を判断するために、この型(訳注:ValidationResult)のフィールドまたはプロパティをじこしんだんできなければなりません。(訳注:ここの訳はかなり自信無し・・・)この型のフィールドおよびプロパティsetterは、適切なビーン初期化を保証するために、@Injectで注釈されていなければなりません。
3.1の例に戻り、今度はコントローラーメソッドを例外ハンドラとしてみます。

@Controller
@Path("form")
public class FormController {

  @Inject
  private ValidationResult vr;

  @Inject
  private ErrorDataBean error;

  @POST
  @ValidateOnExecution(type = ExecutableType.NONE)
  public Response formPost(@Valid @BeanParam FormDataBean form) {
    if (vr.isFailed()) {
      // fill out ErrorDataBean ...
      return Response.status(BAD_REQUEST).entity("error.jsp").build();
    }
    return Response.status(OK).entity("data.jsp").build();
  }
}

フィールドvrがインジェクション対象であることにより、このクラス内のコントローラーメソッドがバリデーションエラーをハンドリングできることを、実装に知らせます。結果として、パラメータが検証されたこのクラス内のメソッドは、バリデーションエラーが発見されたかどうかを確認するために、vr.isFailed()を呼び出すべきです(※1)。
ValidationResultクラスは(訳注:実際はインターフェイスです)、バリデーション中に見つかったすべての違反に関する詳細な情報を取得するメソッドを提供します。このクラスのインスタンスは、すべてリクエストスコープです。詳細はJavadocを確認してください。
前述したように、ValidationResult型のプロパティも併せてサポートされています。ここに、前述の例を変更し、変わりにプロパティを使った例を示します。

@Controller
@Path("form")
public class FormController {

  private ValidationResult vr;

  public ValidationResult getVr() {
    return vr;
  }

  @Inject
  public void setVr(ValidationResult vr) {
    this.vr = vr;
  }
  ...
}

@Injectアノテーションがフィールドからsetterに移動したことに注目してください。これにより、ビーン作られた際に、確かにCDIによって適切に初期化されます。実装は、同一クラス内にフィールドとプロパティ(getterおよびsetterから呼ばれる)が存在する場合、プロパティを優先しなければなりません。

※1

ValidateOnExecutionアノテーションは、違反の検出によってCDIとBean Validationの実行を止めないことを保証します。それゆえ、正しい意味論を保証するために、バリデーションはメソッド呼び出し前に、JAX-RS実装によって実行されなければなりません。

Editors Note 3.1

javax.mvc.validation.ValidationResultをメソッドの引数としてインジェクト可能にすることは、CDI 2.0の新機能で再考されるかもしれません。

Chapter 4 イベント

この章では、リクエスト処理中に発生した重要なイベントがMVCアプリケーションに通知される仕組みを紹介します。この仕組みは、実装によって発火可能およびアプリケーションによって観察可能なCDIイベントに基づいています。

4.1 オブザーバー

javax.mvc.eventパッケージは、リクエストの処理中に実装によって発火されなければならないイベント型を多く定義しています。実装は、このセットを拡張すること、およびこの仕様で定義されたすべてのイベントの追加情報を提供することを許可されています。イベントのサポートに関する詳しい情報は、実装のドキュメントを参照してください。
イベントの観察は、アプリケーションに以下の事項を容易にします。リクエストのライフサイクルの学習、アプリケーションレベルのロギングの提供、パフォーマンス監視、などです。第6章では、実装が特定のビューエンジン処理を選択するアルゴリズムを解説しています。この情報は、ViewEngineSelectedイベントを観察する全てのアプリケーション(またはフレームワーク)によって可能になっています。例えば、

@ApplicationScoped
public class EventObserver {

  public void onViewEngineSelected(@Observes ViewEngineSelected event) {
    ...
  }
}

CDIのオブザーバーメソッドは、引数の位置に@Observesアノテーションを使うことで定義されます。EventObserverクラスは、実装によってViewEngineSelectedイベントが発火されるごとに呼ばれるonViewEngineSelectedメソッドを持つ、アプリケーションスコープのCDIビーンです。
この例を完成させるため、選択されたビューエンジンに関する情報が、クライアントに伝えられる必要があると仮定しましょう。この情報を、クライアントに返されるビューで利用可能にすることを保証するために、EventObserverクラスは、ビューにアクセスされる同じリクエストスコープのビーンをインジェクト・変更することができます。

@ApplicationScoped
public class EventObserver {

  @Inject
  private EventBean eventBean;

  public void onViewEngineSelected(@Observes ViewEngineSelected event) {
    eventBean.setView(event.getView());
    eventBean.setEngine(event.getEngine());
  }
}

ビューとモデルの連携については、2.2を参照してください。
実装により発火されるイベントは同期的であるため、アプリケーションは、オブザーバーメソッド内ではシンプルなタスクのみを実行することが推奨されます。長い演算と同様に、ブロッキング呼び出しは避けるべきです。

Editors Note 4.1

CDI 2.0の新機能によって、同期vs非同期イベント処理についてレビューされるべきです。