1. 概要

このチュートリアルでは、 MartinFowlerで定義されているエンタープライズパターンの一部であるフロントコントローラーパターンについて詳しく説明します。 sbook「PatternsofEnterpriseApplicationArchitecture」。

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

これにより、アプリケーションは実行時に動作を変更できます。 さらに、コードの重複を防ぐことで、アプリケーションの読み取りと保守に役立ちます。

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

2. それはどのように機能しますか?

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

この単一のコントローラーは、要求に関連付けられた動作をトリガーするために、コマンドに要求をディスパッチします。

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

3. 設定

3.1. Mavenの依存関係

まず、javax.servlet-apiを含む新しいMavenWARプロジェクトをセットアップします。

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.0-b01</version>
    <scope>provided</scope>
</dependency>

jetty-maven-plugin と同様に:

<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

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

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

もう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

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 = Search&title = patterns

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

シナリオをまとめるために、すべての場合にフォールバックとして起動される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. 結論

これまで見てきたように、フロントコントローラーパターンと、サーブレットおよびコマンド階層としてのその実装に精通している必要があります。

いつものように、ソースはGitHubにあります。