MVCアーキテクチャの構成要素
JFinalが採用しているMVCパターンは、最小限のボイラープレートでWebアプリケーションの骨格を構築できるように設計されています。リクエストの流れを管理するコントローラ、横断的関心事を分離するインターセプタ、そしてURLと処理ロジックを紐付けるルーティングの3要素が連携することで、効率的なリクエストパイプラインが形成されます。
コントローラ:リクエストハンドリングの中核
すべてのコントローラは com.jfinal.core.Controller を継承して実装します。パブリックなメソッドは自動的にHTTPエンドポイント(アクション)として登録され、クエリパラメータやPOSTボディからデータを抽出する機能、クライアントへデータを返却する描画処理などが標準で提供されています。
典型的な実装例では、入力検証と状態遷移を含めることで堅牢性が高まります。バリデーションロジックは Validator インタフェースを実装したクラスとして独立させ、@Before 注釈によって対象アクションに関連付けます。
import com.jfinal.core.Controller;
import com.jfinal.validate.Validator;
public class AccountController extends Controller {
// バリデーションルールを定義
public static class LoginValidator extends Validator {
protected void validate(Controller c) {
if (!hasRequiredString("username")) {
setAttr("errorMsg", "ユーザーID必須");
}
if (!hasRequiredString("password")) {
setAttr("errorMsg", "パスワード必須");
}
}
protected void handleError(Controller c) {
c.render("/login.html"); // 検証失敗時は同じ画面へ戻す
}
}
@Before(LoginValidator.class)
public void authenticate() {
String token = getPara("token");
boolean success = AuthService.checkToken(token);
setAttr("status", success ? "authorized" : "denied");
renderJson(); // JSON形式で応答
}
public void dashboard() {
Long userId = getParaToLong("uid");
UserProfile user = UserRepository.find(userId);
setAttr("userData", user);
render("/pages/dashboard.html");
}
}
上記のように、getPara シリーズで文字列型や数値型への自動変換が可能です。描画方法はレスポンス形式に応じて renderHtml()、renderJson()、renderFreeMarker() などを使い分けます。
インターセプタ:AOPによる処理の分割
インターセプタは継続オブジェクトを通じてリクエスト前後のフックを実現します。 com.jfinal.aop.Interceptor を実装したクラスは、メソッド実行前に条件判定やログ記録を行い、 Invocation.invoke() で本来のアクション処理に制御を渡した後、終了時刻の計測やリソース解放などの後処理を実行できます。
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
public class DurationTrackingInterceptor implements Interceptor {
@Override
public void intercept(Invocation inv) {
String actionName = inv.getActionKey();
long initTime = System.currentTimeMillis();
try {
inv.invoke(); // 該当メソッドの実行
} finally {
long elapsed = System.currentTimeMillis() - initTime;
System.out.printf("[%s] 応答遅延: %dms%n", actionName, elapsed);
// 長時間応答の場合は警告レベルで記録
if (elapsed > 500) {
System.err.printf("パフォーマンス注意: %s の応答時間が閾値超過%n", actionName);
}
}
}
}
適用スコープは柔軟です。コントローラクラスの宣言部に注釈を付与すれば全アクションに適用され、特定メソッドのみを対象とすることも可能です。さらにプロジェクトレベルで既定のインターセプタチェーンを定義したり、組み込みの CORSInterceptor や Tx トランザクション管理機能を組み合わせたりできます。
ルーティング設定:URLマッピングの制御
Routes は HTTP リクエストのプレフィックスを受け取り、対応する Controller クラスへディスパッチする役割を担います。 JFinalConfig#configRoute(Routes) メソッド内で以下の構文を用いて定義します。
import com.jfinal.config.Routes;
public class AppConfig {
public void configRoute(Routes me) {
// プレフィックス /api/v1 に紐付け
me.add("/api/v1", UserManager.class);
// 第3引数で独自のビューフォルダパスを指定可能
me.add("/portal", PublicPage.class, "/static/views/public");
// パスパターンに基づく追加設定
me.add("/files", FileHandler.class);
}
}
特定のアクションに対してデフォルトのメソッド名ベースではないURL経路を割り当てたい場合は、@ActionKey 注釈を使用します。
public class PublicPage extends Controller {
@ActionKey("/main/feed")
public void fetchFeed() {
List<PostEntity> items = FeedRepository.getLatest(10);
setAttr("posts", items);
render("/feeds/index.html");
}
}
これにより、メソッド名とURL設計を分離でき、RESTfulな設計方針に従ったルーティング構築や既存システムのマイグレーション時の後方互換性維持が容易になります。
リクエストパイプラインの動作順序
- ルーティング判別: incomingリクエストのパスプレフィックスがRoutes設定と照合され、ターゲットControllerクラスが決定される。
- インターセプチャイン実行: グローバル設定→クラス指定→メソッド指定の順で登録されたインターセプタがスタック状に連鎖し、各ノードで前処理・実行・後処理を実行する。
- コントローラアクション実行: 選択された公開メソッドが呼び出され、リクエストパラメータの抽出、ドメイン層への委任、レスポンスデータの変換が行われる。
- 描画エンジン起動: render系メソッドが呼び出されると、テンプレートエンジンまたはシリアライズ処理が走査され、最終的なHTTP Bodyが構築されてストリームへ書き出される。
この順序保証により、各コンポーネントが独立してテスト可能になり、変更影響範囲を局所化できる設計思想が体现されています。