Java前端控制器模式指南

A Guide to the Front Controller Pattern in Java

1.概述

在本教程中,我们将更深入地研究前端控制器模式,这是Martin Fowler的书"企业应用程序体系结构的模式"中定义的企业模式的一部分。

Front Controller定义为"处理网站请求的控制器"。它位于Web应用程序的前面,并将请求委派给后续资源。它还为常见行为(例如安全性,国际化)提供界面,并向某些用户提供特定视图。

这使应用程序可以在运行时更改其行为。此外,它还通过防止代码重复来帮助阅读和维护应用程序。

前控制器通过通过单个处理程序对象引导请求来合并所有请求处理。

2.它如何运作?

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

front-controller

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

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

3.设定

3.1。 Maven依赖

首先,我们将使用javax.servlet-api设置一个新的Maven WAR项目:

1
2
3
4
5
6
<dependency>
    <groupId>javax.servlet</groupId>
    javax.servlet-api</artifactId>
    <version>4.0.0-b01</version>
    <scope>provided</scope>
</dependency>

以及jetty-maven-plugin:

1
2
3
4
5
6
7
8
9
10
<plugin>
    <groupId>org.eclipse.jetty</groupId>
    jetty-maven-plugin</artifactId>
    <version>9.4.0.M1</version>
    <configuration>
        <webApp>
            <contextPath>/front-controller</contextPath>
        </webApp>
    </configuration>
</plugin>

3.2。模型

接下来,我们将定义一个Model类和一个模型存储库。我们将使用以下Book类作为模型:

1
2
3
4
5
6
7
public class Book {
    private String author;
    private String title;
    private Double price;

    // standard constructors, getters and setters
}

这将是存储库,您可以查找用于具体实现的源代码,也可以自己提供一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

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

这使我们可以添加新命令,而无需更改前端控制器的代码库。

另一个选择是使用静态条件逻辑来实现Servlet。这具有编译时错误检查的优点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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及其请求和响应对象。此外,它将处理视图分辨率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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。这将包括在发现书或书丢失的情况下的条件逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
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来访问此命令。

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

为了完善我们的方案,我们将实现第二个命令,该命令在所有情况下都将作为回退触发,Servlet未知命令请求:

1
2
3
4
5
6
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,我们可以在任何Servlet容器中运行Web应用程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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.结论

到目前为止,我们已经熟悉前端控制器模式及其作为Servlet和命令层次结构的实现。

和往常一样,您可以在GitHub上找到源。