«1. Обзор

В этом руководстве мы углубимся в шаблон переднего контроллера, часть корпоративных шаблонов, определенных в книге Мартина Фаулера «Шаблоны архитектуры корпоративных приложений».

Front Controller определяется как «контроллер, который обрабатывает все запросы к веб-сайту». Он стоит перед веб-приложением и делегирует запросы последующим ресурсам. Он также предоставляет интерфейс для общего поведения, такого как безопасность, интернационализация и представление конкретных представлений определенным пользователям.

Это позволяет приложению изменять свое поведение во время выполнения. Кроме того, это помогает читать и поддерживать приложение, предотвращая дублирование кода.

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

2. Как это работает?

Шаблон переднего контроллера в основном разделен на две части. Единый диспетчерский контроллер и иерархия команд. Следующий UML изображает отношения классов универсальной реализации Front Controller:

front-controller

Этот единственный контроллер отправляет запросы командам, чтобы инициировать поведение, связанное с запросом.

Чтобы продемонстрировать его реализацию, мы реализуем контроллер в FrontControllerServlet и команды как классы, унаследованные от абстрактного FrontCommand.

3. Настройка

3.1. Зависимости Maven

Сначала мы настроим новый проект 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>

, а также плагином jetty-maven:

<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. Модель

Далее мы определим класс модели и репозиторий модели. В качестве модели мы будем использовать следующий класс 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.

Другим вариантом является реализация сервлета с использованием статической условной логики. Преимущество этого заключается в проверке ошибок во время компиляции:

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\u0026title=patterns.

SearchCommand разрешается в два представления, второе представление можно протестировать с помощью следующего запроса http://localhost:8080/front-controller/?command=Search\u0026title=any-title.

В завершение нашего сценария мы реализуем вторую команду, которая запускается как резервная во всех случаях, запрос команды неизвестен сервлету:

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

Это представление будет доступно по адресу http:// localhost:8080/front-controller/?command=Order\u0026title=any-title или полностью исключив параметры URL.

4. Развертывание

Поскольку мы решили создать проект с файлом WAR, нам понадобится дескриптор веб-развертывания. С помощью этого файла web.xml мы можем запустить наше веб-приложение в любом контейнере сервлетов:

<?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 и его реализацией в виде сервлета и иерархии команд.

Как обычно, вы найдете исходники на GitHub.