基于web的应用程序设计模式

我正在设计一个简单的基于web的应用程序。我是这个网络领域的新手。我需要你在设计模式方面的建议,比如职责应该如何在Servlet之间分配,制作新Servlet的标准等等。

实际上,我的主页上有几个实体,对应于每个实体,我们有很少的选项,如添加、编辑和删除。早些时候,我每个选项使用一个Servlet,比如Servlet1用于添加entity1, Servlet2用于编辑entity1等等,这样我们就有了大量的Servlet。

现在我们正在改变我们的设计。我的问题是如何选择servlet的职责。我们是否每个实体都有一个Servlet来处理所有选项并将请求转发给服务层?或者我们应该为整个页面使用一个servlet来处理整个页面请求,然后将其转发到相应的服务层?另外,是否将请求对象转发到服务层。

114182 次浏览

恕我直言,如果你从责任分配的角度来看,在web应用程序中没有太大的区别。但是,保持图层的清晰度。在表示层中保留任何纯粹用于表示的内容,比如特定于web控件的控件和代码。把实体放在业务层,把所有功能(如添加、编辑、删除)放在业务层。但是,将它们呈现到浏览器上,以便在表示层中处理。对于。net, ASP。NET MVC模式在保持层分离方面做得很好。研究一下MVC模式。

在破旧的MVC模式中,Servlet是“C”-控制器。

它的主要工作是进行初始请求评估,然后根据初始评估将处理分派给特定的worker。工作人员的职责之一可能是设置一些表示层bean,并将请求转发到JSP页面以呈现HTML。因此,仅出于这个原因,您需要将请求对象传递给服务层。

不过,我不会开始编写原始的Servlet类。他们所做的工作是非常可预测和样板化的,这是框架做得非常好的事情。幸运的是,有很多可用的、经过时间考验的候选名称(按字母顺序排列):Apache WicketJava服务器面春天等等。

一个像样的web应用程序包含了多种设计模式。我只提最重要的几个。


模型视图控制器模式

你想要使用的核心(架构)设计模式是模型-视图-控制器模式控制器将由Servlet表示,Servlet根据请求直接创建/使用特定的模型视图模型将由Javabean类表示。这通常可以进一步分为商业模式(包含操作(行为))和数据模型(包含数据(信息))。视图将由可以通过EL(表达式语言)直接访问(数据) 模型的JSP文件表示。

然后,根据操作和事件的处理方式有一些变化。比较流行的有:

  • 基于请求(动作)的MVC:这是最简单的实现。(业务) 模型直接与HttpServletRequestHttpServletResponse对象一起工作。您必须自己收集、转换和验证请求参数。视图可以用普通的HTML/CSS/JS表示,它不跨请求维护状态。这就是Spring MVCStruts条纹的工作原理。

  • 基于组件的MVC:这个更难实现。但你最终会得到一个更简单的模型和视图,其中所有的“raw”;Servlet API是完全抽象的。您不应该需要自己收集、转换和验证请求参数。控制器执行此任务,并在模型中设置收集、转换和验证的请求参数。您所需要做的就是定义直接与模型属性一起工作的动作方法。视图由"components"JSP标记库或XML元素,这些标记库或XML元素反过来生成HTML/CSS/JS。后续请求的视图状态在会话中维护。这对于服务器端转换、验证和值更改事件特别有帮助。这就是JSFWicket玩!的工作原理。

顺便说一句,对自己开发的MVC框架感兴趣是一种很好的学习练习,只要你出于个人/私人目的使用它,我强烈建议你使用它。但是一旦你变得专业,那么强烈建议你选择一个现有的框架,而不是重新发明你自己的框架。从长远来看,学习一个现有的、开发良好的框架所花费的时间比自己开发和维护一个健壮的框架要少。

在下面的详细解释中,我将限制自己使用基于请求的MVC,因为这更容易实现。


控制器模式 (调停者模式)

首先,控制器部分应该实现控制器模式(这是一种特殊的调停者模式)。它应该只包含一个servlet,该servlet提供所有请求的集中入口点。它应该根据请求提供的信息来创建模型,比如路径信息或servletpath,方法和/或特定的参数。在下面的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/registerhttp://example.com/pages/login这样的URL,并为/WEB-INF/register.jsp/WEB-INF/login.jsp提供适当的GET和POST操作。零件registerlogin等然后可用request.getPathInfo()如上例。

当你使用后缀模式,如*.do*.html等,然后你可以调用URL,如http://example.com/register.dohttp://example.com/login.do等,你应该改变这个答案中的代码示例(也是ActionFactory),以通过request.getServletPath()提取registerlogin部分。


策略模式

Action应该跟在策略模式之后。它需要被定义为一个抽象/接口类型,它应该基于抽象方法的传入参数来完成工作(这是与命令模式的区别,其中抽象/接口类型应该基于在实现的创建期间传入的参数来完成工作)。

public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

你可能想通过自定义异常(如ActionException)使Exception更具体。这只是一个基本的开始示例,其余的都取决于您。

下面是一个LoginAction的例子,它(顾名思义)登录用户。User本身又是一个数据模型视图知道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应该跟在工厂方法模式后面。基本上,它应该提供一个创建方法,返回抽象/接口类型的具体实现。在这种情况下,它应该根据请求提供的信息返回Action接口的实现。例如,方法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配置文件进行配置:(pseudo)

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)


其他模式

这些是目前为止最重要的模式。

为了更进一步,你可以使用门面模式来创建一个Context类,它反过来包装请求和响应对象,并提供几个方便的委托给请求和响应对象的方法,并将其作为参数传递给Action#execute()方法。这增加了一个额外的抽象层来隐藏原始Servlet API。然后在每个Action实现中基本上都应该以 import javax.servlet.*声明结束。在JSF术语中,这就是FacesContextExternalContext类正在做的事情。你可以在Action#execute()0中找到一个具体的例子。

然后是状态模式,用于您想要添加一个额外的抽象层来分割收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务。在JSF术语中,这就是LifeCycle正在做的事情。

然后是复合模式,如果你想创建一个基于组件的视图,它可以附加在模型上,其行为取决于基于请求的生命周期的状态。在JSF术语中,这就是UIComponent所表示的。

通过这种方式,您可以一点一点地向基于组件的框架发展。


参见:

我使用过struts框架,发现它相当容易学习。当使用struts框架时,站点的每个页面都将具有以下项目。

1)每次刷新HTML页面时都会调用所使用的动作。该操作应该在页面第一次加载时填充表单中的数据,并处理web UI和业务层之间的交互。如果您使用jsp页面来修改可变java对象,那么java对象的副本应该存储在表单中,而不是原始的表单中,这样原始数据就不会被修改,除非用户保存了页面。

2)用于在动作和jsp页面之间传输数据的表单。这个对象应该由一组getter和setter组成,这些getter和setter用于jsp文件需要访问的属性。表单还有一个方法,用于在持久化数据之前验证数据。

3) jsp页面,用于呈现页面的最终HTML。jsp页面混合了HTML和特殊的struts标记,用于访问和操作表单中的数据。尽管struts允许用户将Java代码插入到jsp文件中,但您应该非常谨慎地这样做,因为这会使您的代码更难阅读。jsp文件中的Java代码很难调试,不能进行单元测试。如果您发现自己在jsp文件中编写的java代码超过4-5行,那么应该将代码移到操作中。

BalusC优秀的回答涵盖了大多数web应用程序的模式。

一些应用程序可能需要Chain-of-responsibility_pattern

在面向对象设计中,责任链模式是一种由命令对象源和一系列处理对象组成的设计模式。每个处理对象都包含定义它可以处理的命令对象类型的逻辑;其余的被传递给链中的下一个处理对象。

使用这个模式的用例:

当处理请求(命令)的处理器未知时,该请求可以发送给多个对象。通常你设置的继任者为object。如果当前对象不能处理请求或部分处理请求并将相同的请求转发给的继任者对象。

有用的SE问题/文章:

为什么我要使用一个责任链而不是一个装饰师?< / >

责任链的通用用法?< / >

chain-of-responsibility-pattern from oodesign

chain_of_responsibility from sourcemaking