A Guide to the Front Controller Pattern in Java – Java中的前端控制器模式指南

最后修改: 2016年 10月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial we’ll be digging deeper into the Front Controller Pattern, part of the Enterprise Patterns as defined in Martin Fowler‘s book “Patterns of Enterprise Application Architecture”.

在本教程中,我们将深入探讨Front Controller Pattern,这是Martin Fowler《企业应用架构模式》一书中定义的Enterprise Patterns 的一部分。

Front Controller is defined as “a controller that handles all requests for a Web site”. It stands in front of a web-application and delegates requests to subsequent resources. It also provides an interface to common behavior such as security, internationalization and presenting particular views to certain users.

前台控制器被定义为 “一个处理网站所有请求的控制器”。它站在网络应用程序的前面,将请求委托给后续资源。它还提供了一个常见行为的接口,如安全、国际化和向某些用户展示特定的视图。

This enables an application to change its behavior at runtime. Furthermore it helps to read and maintain an application by preventing code duplication.

这使得一个应用程序能够在运行时改变其行为。此外,它还有助于通过防止代码重复来阅读和维护一个应用程序。

The Front Controller consolidates all request handling by channeling requests through a single handler object.

前台控制器通过一个处理对象来引导请求,从而整合所有的请求处理。

2. How Does It Work?

2.它是如何工作的?

The Front Controller Pattern is mainly divided into two parts. A single dispatching controller and a hierarchy of commands. The following UML depicts class relations of a generic Front Controller implementation:

前置控制器模式主要分为两部分。一个单一的调度控制器和一个命令的层次结构。下面的UML描述了一个通用Front Controller实现的类关系。

front-controller

This single controller dispatches requests to commands in order to trigger behavior associated with a request.

这个单一的控制器将请求分派给命令,以触发与请求相关的行为。

To demonstrate its implementation, we’ll implement the controller in a FrontControllerServlet and commands as classes inherited from an abstract FrontCommand.

为了演示其实现,我们将在FrontControllerServlet中实现控制器,并将命令作为从抽象的FrontCommand继承的类。

3. Setup

3.设置

3.1. Maven Dependencies

3.1.Maven的依赖性

First, we’ll setup a new Maven WAR project with javax.servlet-api included:

首先,我们将建立一个新的Maven WAR项目,并将javax.servlet-api纳入其中。

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

as well as jetty-maven-plugin:

以及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

3.2.模型

Next, we’ll define a Model class and a model Repository. We’ll use the following Book class as our model:

接下来,我们将定义一个Model类和一个模型Repository。我们将使用下面的类作为我们的模型。

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

    // standard constructors, getters and setters
}

This will be the repository, you can lookup the source code for concrete implementation or provide one on your own:

这将是资源库,你可以查询具体实现的源代码,或者自己提供一个。

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

3.3.FrontControllerServlet

The implementation of the Servlet itself is fairly simple. We’re extracting the command name from a request, creating dynamically a new instance of a command class and executing it.

Servlet本身的实现是相当简单的。我们从一个请求中提取命令名称,动态地创建一个新的命令类实例并执行它。

This allows us to add new commands without changing a code base of our Front Controller.

这使我们能够在不改变我们的Front Controller的代码基础上添加新的命令。

Another option is to implement the Servlet using static, conditional logic. This has the advantage of compile-time error checking:

另一个选择是使用静态、条件逻辑来实现Servlet。这样做的好处是可以进行编译时的错误检查。

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

3.4.FrontCommand

Let’s implement an abstract class called FrontCommand, which is holding the behavior common to all commands.

让我们实现一个名为FrontCommand的抽象类,它持有所有命令的共同行为。

This class has access to the ServletContext and its request and response objects. Furthermore, it’ll handle view resolution:

这个类可以访问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);
    }
}

A concrete implementation of this abstract FrontCommand would be a SearchCommand. This will include conditional logic for cases where a book was found or when book is missing:

这个抽象的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");
        }
    }
}

If the application is running, we can reach this command by pointing our browser to http://localhost:8080/front-controller/?command=Search&title=patterns.

如果应用程序正在运行,我们可以通过将浏览器指向http://localhost:8080/front-controller/?command=Search&title=patterns来达到这个命令。

The SearchCommand resolves to two views, the second view can be tested with the following request http://localhost:8080/front-controller/?command=Search&title=any-title.

SearchCommand 解析为两个视图,第二个视图可以用以下请求进行测试 http://localhost:8080/front-controller/?command=Search&title=any-title

To round up our scenario we’ll implement a second command, which is fired as fallback in all cases, a command request is unknown to the Servlet:

为了完善我们的方案,我们将实现第二条命令,在所有情况下,命令请求对Servlet来说是未知的,这条命令将作为后备命令被触发。

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

This view will be reachable at http://localhost:8080/front-controller/?command=Order&title=any-title or by completely leaving out the URL parameters.

该视图将可在http://localhost:8080/front-controller/?command=Order&title=any-title或通过完全不使用URL参数来达到。

4. Deployment

4.部署

Because we decided to create a WAR file project, we’ll need a web deployment descriptor. With this web.xml we’re able to run our web-application in any Servlet container:

因为我们决定创建一个WAR文件项目,我们需要一个web部署描述符。有了这个web.xml,我们就能在任何Servlet容器中运行我们的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>

As the last step we’ll run ‘mvn install jetty:run’ and inspect our views in a browser.

作为最后一步,我们将运行‘mvn install jetty:run’并在浏览器中检查我们的视图。

5. Conclusion

5.结论

As we’ve seen so far, we now should be familiar with the Front Controller Pattern and its implementation as Servlet and command hierarchy.

正如我们到目前为止所看到的,我们现在应该熟悉Front Controller Pattern以及它作为Servlet和命令层次的实现。

As usual, you’ll find the sources on GitHub.

像往常一样,你可以在GitHub上找到源代码