Javaのフロントコントローラパターンの手引き

1概要

このチュートリアルでは、 Martin Fowlerの著書 「エンタープライズアプリケーションアーキテクチャのパターン」で定義されている Enterprise Patterns の一部である Front Controller Pattern について詳しく説明します。

Front Controller は、「Webサイトに対するすべての要求を処理するコントローラー」として定義されています。 Webアプリケーションの前に立ち、要求を後続のリソースに委任します。また、セキュリティ、国際化、特定のユーザーへの特定の意見の提示など、一般的な行動へのインターフェイスも提供します。

これにより、アプリケーションは実行時に動作を変更できます。

さらに、コードの重複を防ぐことでアプリケーションを読み、維持するのに役立ちます。

フロントコントローラは、単一のハンドラオブジェクトを介して要求をチャネリングすることによって、すべての要求処理を統合する。

2どのように動作しますか?

[フロントコントローラーパターン]は、主に2つの部分に分かれています。単一のディスパッチングコントローラとコマンドの階層次のUMLは、一般的なフロントコントローラ実装のクラス関係を表しています。

リンク:/uploads/front-controller-300x180.png%20300w[]

この単一のコントローラーは、要求に関連した動作をトリガーするために、要求をコマンドに振り分けます。

その実装を実証するために、コントローラを FrontControllerServlet に実装し、コマンドを抽象 FrontCommand から継承したクラスとして実装します。

3セットアップ

3.1. Mavenの依存関係

まず、 https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22javax.servlet%22%20AND%20a%3A%22javax.servletを使用して、新しい Maven WAR プロジェクトをセットアップします-api%22[javax.servlet-api] 含まれるもの:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.0-b01</version>
    <scope>provided</scope>
</dependency>
<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.0.M1</version>
    <configuration>
        <webApp>
            <contextPath>/front-controller</contextPath>
        </webApp>
    </configuration>
</plugin>

3.2. モデル

次に、 Model クラスとモデル Repository を定義します。モデルとして次の Book クラスを使用します。

public class Book {
    private String author;
    private String title;
    private Double price;

   //standard constructors, getters and setters
}

これがリポジトリになります。具体的な実装のためにソースコードを検索することも、独自に提供することもできます。

public interface Bookshelf {
    default void init() {
        add(new Book("Wilson, Robert Anton & Shea, Robert",
          "Illuminati", 9.99));
        add(new Book("Fowler, Martin",
          "Patterns of Enterprise Application Architecture", 27.88));
    }

    Bookshelf getInstance();

    <E extends Book> boolean add(E book);

    Book findByTitle(String title);
}

3.3. FrontControllerServlet

サーブレット自体の実装はかなり単純です。リクエストからコマンド名を抽出し、コマンドクラスの新しいインスタンスを動的に作成して実行します。

これにより、 Front Controller のコードベースを変更せずに新しいコマンドを追加できます。

もう1つの選択肢は、静的な条件付きロジックを使用してサーブレットを実装することです。これにはコンパイル時エラーチェックの利点があります。

public class FrontControllerServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request,
      HttpServletResponse response) {
        FrontCommand command = getCommand(request);
        command.init(getServletContext(), request, response);
        command.process();
    }

    private FrontCommand getCommand(HttpServletRequest request) {
        try {
            Class type = Class.forName(String.format(
              "com.baeldung.enterprise.patterns.front."
              + "controller.commands.%sCommand",
              request.getParameter("command")));
            return (FrontCommand) type
              .asSubclass(FrontCommand.class)
              .newInstance();
        } catch (Exception e) {
            return new UnknownCommand();
        }
    }
}

3.4. フロントコマンド

FrontCommand という抽象クラスを実装しましょう。これは、すべてのコマンドに共通の動作を保持しています。

このクラスは、 ServletContext とその要求および応答オブジェクトにアクセスできます。さらに、ビューの解像度を処理します。

public abstract class FrontCommand {
    protected ServletContext context;
    protected HttpServletRequest request;
    protected HttpServletResponse response;

    public void init(
      ServletContext servletContext,
      HttpServletRequest servletRequest,
      HttpServletResponse servletResponse) {
        this.context = servletContext;
        this.request = servletRequest;
        this.response = servletResponse;
    }

    public abstract void process() throws ServletException, IOException;

    protected void forward(String target) throws ServletException, IOException {
        target = String.format("/WEB-INF/jsp/%s.jsp", target);
        RequestDispatcher dispatcher = context.getRequestDispatcher(target);
        dispatcher.forward(request, response);
    }
}

この抽象的な FrontCommand の具体的な実装は SearchCommand になります。これには、本が見つかった場合や本が見つからない場合の条件付きロジックが含まれます。

public class SearchCommand extends FrontCommand {
    @Override
    public void process() throws ServletException, IOException {
        Book book = new BookshelfImpl().getInstance()
          .findByTitle(request.getParameter("title"));
        if (book != null) {
            request.setAttribute("book", book);
            forward("book-found");
        } else {
            forward("book-notfound");
        }
    }
}

アプリケーションが実行されている場合は、ブラウザでhttp://localhost:8080/front-controller/?command =を指定してこのコマンドを実行できます。

SearchCommand は2つのビューに解決され、2番目のビューは次のリクエストでテストできます。http://localhost:8080/front-controller/?command =検索

私たちのシナリオを締めくくるために、2番目のコマンドを実装します。これは、すべての場合においてフォールバックとして起動されます。コマンド要求はサーブレットにとって未知のものです。

public class UnknownCommand extends FrontCommand {
    @Override
    public void process() throws ServletException, IOException {
        forward("unknown");
    }
}

このビューは、http://localhost:8080/front-controller/?command = Order&title = any-title、または URL パラメータを完全に省略することで到達できます。

4展開

WAR ファイルプロジェクトを作成することにしたので、Webデプロイメント記述子が必要になります。この web.xml を使用すると、任意のサーブレットコンテナでWebアプリケーションを実行できます。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/web-app__3__1.xsd"
  version="3.1">
    <servlet>
        <servlet-name>front-controller</servlet-name>
        <servlet-class>
            com.baeldung.enterprise.patterns.front.controller.FrontControllerServlet
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>front-controller</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

最後のステップとして、「mvn install jetty:run」を実行し、ブラウザでビューを調べます。

5結論

これまで見てきたように、 Front Controller Pattern とそのサーブレットおよびコマンド階層としての実装に精通しているはずです。

いつもどおり、https://github.com/eugenp/tutorials/tree/master/patterns[on GitHub]にソースがあります。