关于java:Design Patterns基于Web的应用程序

Design Patterns web based applications

我正在设计一个简单的基于Web的应用程序。我是这个基于Web的领域的新手,我需要你关于设计模式的建议,比如责任应该如何分配给servlet,创建新servlet的标准等等。

实际上,我的主页上有几个实体,与它们对应,我们有几个选项,如添加、编辑和删除。早些时候,我为每个选项使用一个servlet,比如servlet1用于添加entity1,servlet2用于编辑entity1等等,这样我们就得到了大量的servlet。

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


一个有点体面的Web应用程序由混合的设计模式组成。我只会提到最重要的那些。好的。模型视图控制器模式

您要使用的核心(体系结构)设计模式是模型-视图-控制器模式。控制器由servlet表示,servlet(in)根据请求直接创建/使用特定的模型和视图。模型将由JavaBean类表示。在包含操作(行为)的业务模型和包含数据(信息)的数据模型中,这通常可以进一步划分。视图由JSP文件表示,JSP文件通过EL(表达式语言)直接访问(数据)模型。好的。

然后,根据操作和事件的处理方式,会有一些变化。最受欢迎的是:好的。

  • 基于请求(操作)的MVC:这是最简单的实现。(业务)模型直接与HttpServletRequestHttpServletResponse对象一起工作。您必须自己收集、转换和验证请求参数(大部分)。该视图可以用普通的HTML/CSS/JS表示,并且不在请求之间维护状态。这就是SpringMVC、Struts和Stripes的工作原理。好的。

  • 基于组件的MVC:这很难实现。但是您最终得到了一个更简单的模型和视图,其中所有"原始"servlet API都被完全抽象掉了。您不需要自己收集、转换和验证请求参数。控制器执行此任务,并在模型中设置收集、转换和验证的请求参数。您需要做的只是定义直接与模型属性一起工作的操作方法。视图由JSP标记库或XML元素风格的"组件"表示,后者反过来生成HTML/CSS/JS。在会话中维护后续请求的视图状态。这对于服务器端转换、验证和值更改事件特别有用。这就是JSF、Wicket和Play的方式!作品。好的。

另一点需要注意的是,使用国产的MVC框架是一个非常好的学习练习,我建议您只要将其用于个人/私人目的。但是一旦你变得专业了,强烈建议你选择一个现有的框架,而不是重新设计你自己的框架。与自己开发和维护一个健壮的框架相比,学习一个现有的、开发良好的框架需要的时间要短得多。好的。

在下面的详细解释中,我将限制自己使用基于请求的MVC,因为这更容易实现。好的。型前控制器模式(中介模式)

首先,控制器部分应该实现前端控制器模式(这是一种专门的中介模式)。它应该只包含一个servlet,它提供所有请求的集中入口点。它应该根据请求提供的信息创建模型,例如pathinfo或servletpath、方法和/或特定参数。在下面的HttpServlet示例中,该业务模型称为Action。好的。型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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/*,您可以调用url,如http://example.com/pages/register、http://example.com/pages/login等,并向/WEB-INF/register.jsp/WEB-INF/login.jsp提供适当的get和post操作。registerlogin等零件随后由request.getPathInfo()提供,如上述示例所示。好的。型

当您使用诸如*.do*.html等后缀模式时,然后可以调用url,如http://example.com/register.do、http://example.com/login.do等,您应该更改此答案中的代码示例(也就是ActionFactory)来提取registerlogin部分,而不是request.getServletPath()。好的。型战略模式

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

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

您可能想让Exception更具体一些,比如ActionException这样的自定义异常。这只是一个基本的开球例子,剩下的都由你决定。好的。型

下面是一个LoginAction的例子,它(正如其名称所说)登录用户。User本身又是一个数据模型。这种观点意识到了User的存在。好的。型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(pathinfo是请求URL中上下文和servlet路径之后的部分,不包括查询字符串)。好的。型

1
2
3
public static Action getAction(HttpServletRequest request) {
    return actions.get(request.getMethod() + request.getPathInfo());
}

反过来,actions应该是一些静态的/应用范围的Map,它包含所有已知的动作。如何填这张地图取决于你。硬编码:好的。型

1
2
3
4
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...

或者根据类路径中的属性/xml配置文件进行配置:(伪)好的。

1
2
3
for (Entry entry : configuration) {
    actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}

或者动态地基于类路径中实现特定接口和/或注释的类的扫描:(伪)好的。

1
2
3
4
5
for (ClassFile classFile : classpath) {
    if (classFile.isInstanceOf(Action.class)) {
       actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
    }
}

记住要为没有映射的情况创建一个"不做任何事情"Action。例如,让它直接返回request.getPathInfo().substring(1)。好的。其他模式

到目前为止,这些都是重要的模式。好的。

为了更进一步,您可以使用facade模式创建一个Context类,该类依次包装请求和响应对象,并提供一些委托给请求和响应对象的方便方法,并将其作为参数传递给Action#execute()方法。这会添加一个额外的抽象层来隐藏原始servlet API。然后,您应该在每个Action实现中基本上得到零个import javax.servlet.*声明。在JSF术语中,这就是FacesContextExternalContext类所做的。你可以在这个答案中找到一个具体的例子。好的。

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

然后是您想要创建一个基于组件的视图的情况的复合模式,该视图可以附加到模型中,其行为取决于基于请求的生命周期的状态。在JSF术语中,这就是UIComponent所代表的。好的。

这样,您就可以一点一点地向基于组件的框架发展。好的。参见:

  • Java核心库中GOF设计模式的实例
  • 请求MVC和组件MVC之间的差异
  • 使用MVC和DAO模式在JSP页面的HTML中显示JDBC结果集
  • 什么组件是JSF MVC框架中的MVC?
  • JSF控制器、服务和DAO

好啊。


在被敲打的MVC模式中,servlet是"C"控制器。

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

不过,我不会开始编写原始的Servlet类。他们所做的工作是非常可预测的和样板式的,框架做得非常好。幸运的是,有许多可用的、经过时间考验的候选(按字母顺序排列):Apache WiKET、Java Server Asple、Spring等等。


imho,如果从责任分配的角度来看,Web应用程序的情况没有太大的区别。但是,保持层中的清晰度。在表示层中保留任何纯粹用于表示目的的内容,例如特定于Web控件的控件和代码。只需将实体保留在业务层中,并将所有功能(如添加、编辑、删除)等保留在业务层中。但是,将它们呈现到浏览器上,以便在表示层中处理。对于.NET,ASP.NET MVC模式在保持层分离方面非常好。研究MVC模式。


Balusc优秀的答案涵盖了Web应用程序的大多数模式。

某些应用程序可能需要责任链模式

In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.

使用案例使用此模式:

当处理请求(命令)的处理程序未知并且此请求可以发送到多个对象时。一般情况下,您将"后续任务"设置为"对象"。如果当前对象无法处理请求或部分处理请求,则将同一请求转发给后续对象。

有用的SE问题/文章:

为什么我要用责任链来代替装饰师?

责任链的常见用法?

面向对象设计的责任链模式

来源责任链


我已经使用了Struts框架,并且发现它相当容易学习。使用Struts框架时,站点的每个页面都将包含以下项。

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

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

3)用于呈现页面最终HTML的JSP页面。JSP页面是HTML和用于访问和操作表单中数据的特殊struts标记的混合体。虽然Struts允许用户将Java代码插入到JSP文件中,但这样做会非常谨慎,因为这会使代码变得更难阅读。JSP文件内部的Java代码很难调试,不能进行单元测试。如果发现自己在JSP文件中编写了超过4-5行Java代码,则代码可能会被移动到操作中。