关闭。这个问题是基于意见的。它目前不接受答案。想改进这个问题?更新问题,以便可以通过编辑这篇文章用事实和引用来回答它。 4年前关闭。改进这个问题
我正在设计一个简单的基于 Web 的应用程序。我是这个基于 Web 的领域的新手。我需要您对设计模式的建议,例如如何在 Servlet 之间分配责任、制作新 Servlet 的标准等。
实际上,我的主页上几乎没有实体,并且对应于每个实体,我们几乎没有添加、编辑和删除等选项。早些时候,我为每个选项使用一个 Servlet,例如 Servlet1 用于添加实体 1,Servlet2 用于编辑实体 1 等等,这样我们最终拥有了大量的 Servlet。
现在我们正在改变我们的设计。我的问题是您如何准确地选择如何选择 servlet 的职责。我们是否应该每个实体有一个 Servlet,它将处理它的所有选项并将请求转发到服务层。或者我们应该为整个页面设置一个 servlet 来处理整个页面请求,然后将其转发到相应的服务层?另外,请求对象是否应该转发到服务层。
有点像样的 Web 应用程序由多种设计模式组成。我只会提到最重要的那些。
模型视图控制器模式
您要使用的核心(架构)设计模式是 Model-View-Controller pattern。 Controller 将由 Servlet 表示,该 Servlet (in) 根据请求直接创建/使用特定的 Model 和 View。 Model 将由 Javabean 类表示。这通常可以在包含操作(行为)的业务模型和包含数据(信息)的数据模型中进一步划分。 View 将由 JSP 文件表示,这些文件可以通过 EL(表达式语言)直接访问(Data)Model。
然后,根据操作和事件的处理方式会有所不同。流行的是:
基于请求(动作)的 MVC:这是最容易实现的。 (业务)模型直接与 HttpServletRequest 和 HttpServletResponse 对象一起工作。您必须(主要)自己收集、转换和验证请求参数。视图可以用普通的 HTML/CSS/JS 表示,它不会跨请求维护状态。这就是 Spring MVC、Struts 和 Stripes 的工作方式。
基于组件的 MVC:这更难实现。但是您最终会得到一个更简单的模型和视图,其中所有“原始”Servlet API 都被完全抽象掉了。您不需要自己收集、转换和验证请求参数。 Controller 执行此任务并在模型中设置收集、转换和验证的请求参数。您需要做的就是定义直接与模型属性一起使用的操作方法。视图由 JSP 标签库或 XML 元素风格的“组件”表示,这些元素又生成 HTML/CSS/JS。后续请求的视图状态在会话中维护。这对于服务器端转换、验证和值更改事件特别有用。这就是 JSF、Wicket 和 Play 中的其他内容!作品。
附带说明一下,喜欢使用自制的 MVC 框架是一个非常好的学习练习,只要您出于个人/私人目的保留它,我就推荐它。但是一旦你变得专业,那么强烈建议选择一个现有的框架,而不是重新发明你自己的。从长远来看,学习一个现有的和完善的框架比自己开发和维护一个健壮的框架所花费的时间更少。
在下面的详细解释中,我将限制自己使用基于请求的 MVC,因为这更容易实现。
前端控制器模式(中介者模式)
首先,Controller 部分应该实现 Front Controller pattern(它是一种特殊的 Mediator pattern)。它应该只包含一个 servlet,它提供所有请求的集中入口点。它应该根据请求可用的信息(例如 pathinfo 或 servletpath、方法和/或特定参数)创建 Model。 商业模式 在下面的 HttpServlet
示例中称为 Action
。
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Action action = ActionFactory.getAction(request);
String view = action.execute(request, response);
if (view.equals(request.getPathInfo().substring(1)) {
request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
}
else {
response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
}
}
catch (Exception e) {
throw new ServletException("Executing action failed.", e);
}
}
执行动作应该返回一些标识符来定位视图。最简单的方法是将它用作 JSP 的文件名。将此 servlet 映射到 web.xml
中的特定 url-pattern
,例如 /pages/*
、*.do
甚至只是 *.html
。
对于例如 /pages/*
的前缀模式,您可以调用诸如 http://example.com/pages/register、http://example.com/pages/login 等的 URL,并为 /WEB-INF/register.jsp
、/WEB-INF/login.jsp
提供适当的 GET 和 POST 操作。 request.getPathInfo()
可以使用 register
、login
等部分,如上例所示。
当您使用 *.do
、*.html
等后缀模式时,您可以调用 http://example.com/register.do、http://example.com/login.do 等 URL,您应该更改此答案中的代码示例(也是 {6 }) 改为通过 request.getServletPath()
提取 register
和 login
部分。
策略模式
Action
应在 Strategy pattern 之后。它需要被定义为一个抽象/接口类型,它应该根据抽象方法的传入参数来完成工作(这是与 Command pattern 的区别,其中抽象/接口type 应该根据在实现的创建期间传入的参数来完成工作)。
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
您可能希望使用 ActionException
之类的自定义异常使 Exception
更加具体。这只是一个基本的启动示例,其余的完全取决于您。
下面是一个 LoginAction
示例,它(如其名称所示)登录用户。 User
本身又是一个数据模型。 View 知道 User
的存在。
public class LoginAction implements Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userDAO.find(username, password);
if (user != null) {
request.getSession().setAttribute("user", user); // Login user.
return "home"; // Redirect to home page.
}
else {
request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
return "login"; // Go back to redisplay login form with error.
}
}
}
工厂方法模式
ActionFactory
应在 Factory method pattern 之后。基本上,它应该提供一个创建方法,该方法返回一个抽象/接口类型的具体实现。在这种情况下,它应该根据请求提供的信息返回 Action
接口的实现。例如,method 和 pathinfo(pathinfo 是请求 URL 中上下文和 servlet 路径之后的部分,不包括查询字符串)。
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
反过来,actions
应该是一些静态/应用程序范围的 Map<String, Action>
,它包含所有已知操作。如何填写此地图由您决定。硬编码:
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
或者基于类路径中的属性/XML 配置文件进行配置:(伪)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
或者动态地基于类路径中的扫描实现某个接口和/或注释的类:(伪)
for (ClassFile classFile : classpath) {
if (classFile.isInstanceOf(Action.class)) {
actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
}
}
请记住为没有映射的情况创建“无操作”Action
。让它例如直接返回 request.getPathInfo().substring(1)
然后。
其他图案
到目前为止,这些是重要的模式。
为了更进一步,您可以使用 Facade pattern 创建一个 Context
类,该类依次包装请求和响应对象,并提供几种方便的方法来委派请求和响应对象,并将其作为参数传递给 {6 } 方法。这增加了一个额外的抽象层来隐藏原始的 Servlet API。然后,您应该基本上在每个 Action
实现中都有 zero import javax.servlet.*
声明。在 JSF 术语中,这就是 FacesContext
和 ExternalContext
类正在做的事情。您可以在 this answer 中找到具体示例。
然后是 State pattern,您想添加一个额外的抽象层来拆分收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务。在 JSF 术语中,这就是 LifeCycle
正在做的事情。
然后是 Composite pattern,您希望创建一个基于组件的视图,该视图可以附加到模型中,并且其行为取决于基于请求的生命周期的状态。在 JSF 术语中,这就是 UIComponent
所代表的内容。
通过这种方式,您可以一点一点地向基于组件的框架发展。
也可以看看:
Java核心库中的GoF设计模式示例
请求 MVC 和组件 MVC 的区别
使用 MVC 和 DAO 模式在 JSP 页面的 HTML 中显示 JDBC ResultSet
JSF MVC 框架中的 MVC 是哪些组件?
JSF 控制器、服务和 DAO
在老旧的 MVC 模式中,Servlet 是“C”——控制器。
它的主要工作是进行初始请求评估,然后根据初始评估将处理分派给特定的工作人员。工作人员的职责之一可能是设置一些表示层 bean 并将请求转发到 JSP 页面以呈现 HTML。因此,仅出于这个原因,您就需要将请求对象传递给服务层。
不过,我不会开始编写原始的 Servlet
类。他们所做的工作是非常可预测的和样板的,框架做得很好。幸运的是,有许多可用的、经过时间考验的候选者(按字母顺序排列):Apache Wicket、Java Server Faces、Spring 等等。
恕我直言,如果从职责分配的角度来看,Web 应用程序的情况并没有太大区别。但是,请保持图层的清晰度。在表示层中保留纯粹用于表示目的的任何内容,例如特定于 Web 控件的控件和代码。只需将您的实体保留在业务层中,并将所有功能(如添加、编辑、删除)等保留在业务层中。但是将它们渲染到浏览器上以在表示层中处理。对于 .Net,ASP.NET MVC 模式在保持层分离方面非常好。查看 MVC 模式。
我使用了 struts 框架,发现它相当容易学习。使用 struts 框架时,您网站的每个页面都会有以下项目。
1) 每次刷新 HTML 页面时都会调用一个动作。该操作应在首次加载页面时填充表单中的数据并处理 Web UI 和业务层之间的交互。如果您使用 jsp 页面修改可变 java 对象,则应将 java 对象的副本存储在表单中而不是原始数据中,以便除非用户保存页面,否则原始数据不会被修改。
2)用于在动作和jsp页面之间传输数据的表单。此对象应包含一组 getter 和 setter,用于 jsp 文件需要访问的属性。该表单还具有在数据被持久化之前验证数据的方法。
3)一个jsp页面,用于呈现页面的最终HTML。 jsp 页面是 HTML 和特殊 struts 标记的混合体,用于访问和操作表单中的数据。尽管 struts 允许用户将 Java 代码插入到 jsp 文件中,但您应该非常谨慎地这样做,因为这会使您的代码更难阅读。 jsp 文件中的 Java 代码难以调试,无法进行单元测试。如果您发现自己在 jsp 文件中编写了超过 4-5 行的 java 代码,则可能应该将代码移至 action。
BalusC 的优秀答案涵盖了 Web 应用程序的大部分模式。
某些应用程序可能需要 Chain-of-responsibility_pattern
在面向对象的设计中,责任链模式是由一个命令对象源和一系列处理对象组成的设计模式。每个处理对象都包含定义它可以处理的命令对象类型的逻辑;其余的被传递给链中的下一个处理对象。
使用此模式的用例:
当处理请求(命令)的处理程序未知并且此请求可以发送到多个对象时。通常,您将后继者设置为对象。如果当前对象无法处理请求或部分处理请求并将相同的请求转发给后继对象。
有用的 SE 问题/文章:
Why would I ever use a Chain of Responsibility over a Decorator?
Common usages for chain of responsibility?
chain-of-responsibility-pattern 来自 oodesign
不定期副业成功案例分享
web.xml
中检索它们,那么您可以使用ServletContextListener
。让工厂实现它(并在web.xml
中注册为<listener>
)并在contextInitialized()
方法期间完成填充工作。Action
实现中以与普通 servlet 相同的方式正确执行此操作(另请参阅 servlets wiki 以获取基本示例,您可以自由地将其进一步重构为某些Validator
接口)。但是您也可以在调用操作之前执行此操作,但这更复杂,因为它需要在每个视图的基础上了解验证规则。 JSF 通过在 XHTML 标记中提供required="true"
、validator="customValidatorName"
等来解决这个问题。