1. 概述
在这个教程中,我们将深入探讨前端控制器模式,这是企业模式中马丁·福勒(Martin Fowler)在《企业应用架构模式》一书中定义的一部分。
前端控制器被定义为“处理网站所有请求的控制器”。它位于Web应用程序的前端,将请求分发给后续资源,并提供诸如安全性、国际化以及向特定用户展示特定视图等通用行为的接口。
这使得应用程序能够在运行时改变其行为,并有助于阅读和维护代码,防止代码重复。
前端控制器通过单个处理器对象集中处理所有请求。
2. 它的工作原理是什么?
前端控制器模式主要分为两个部分:一个单一的调度控制器和一个命令层次结构。以下UML图展示了通用前端控制器实现的类关系:
这个单一的控制器将请求分发给命令,以触发与请求关联的行为。
为了演示其实现,我们将在这个教程中实现一个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
:
<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
Servlet本身的实现相当简单。我们从请求中提取命令名称,动态创建一个命令类的新实例并执行它。
这允许我们在不更改前端控制器代码库的情况下添加新命令。
另一种选择是使用静态条件逻辑来实现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
让我们实现一个名为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
来达到这个命令。
SearchCommand
将解析为两个视图,第二个视图可以通过以下请求进行测试:http://localhost:8080/front-controller/?command=Search&title=any-title
。
为了完成我们的场景,我们将实现另一个命令,当Servlet未知的命令请求时,它将作为默认处理。这个视图可以通过http://localhost:8080/front-controller/?command=Order&title=any-title
访问,或者完全省略URL参数。
4. 部署
由于我们决定创建一个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>
最后一步是运行'mvn install jetty:run'
并在浏览器中检查我们的视图。
5. 总结
至此,我们已经熟悉了前端控制器模式及其作为Servlet和命令层次结构实现的基本概念。
如往常一样,你可以在GitHub上找到完整的代码。