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

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

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


当前回答

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


模型-视图-控制器模式

The core (architectural) design pattern you'd like to use is the Model-View-Controller pattern. The Controller is to be represented by a Servlet which (in)directly creates/uses a specific Model and View based on the request. The Model is to be represented by Javabean classes. This is often further dividable in Business Model which contains the actions (behaviour) and Data Model which contains the data (information). The View is to be represented by JSP files which have direct access to the (Data) Model by EL (Expression Language).

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

Request (action) based MVC: this is the simplest to implement. The (Business) Model works directly with HttpServletRequest and HttpServletResponse objects. You have to gather, convert and validate the request parameters (mostly) yourself. The View can be represented by plain vanilla HTML/CSS/JS and it does not maintain state across requests. This is how among others Spring MVC, Struts and Stripes works. Component based MVC: this is harder to implement. But you end up with a simpler model and view wherein all the "raw" Servlet API is abstracted completely away. You shouldn't have the need to gather, convert and validate the request parameters yourself. The Controller does this task and sets the gathered, converted and validated request parameters in the Model. All you need to do is to define action methods which works directly with the model properties. The View is represented by "components" in flavor of JSP taglibs or XML elements which in turn generates HTML/CSS/JS. The state of the View for the subsequent requests is maintained in the session. This is particularly helpful for server-side conversion, validation and value change events. This is how among others JSF, Wicket and Play! works.

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

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


前端控制器模式(中介模式)

首先,控制器部分应该实现Front Controller模式(这是一种专门的中介模式)。它应该只包含一个servlet,该servlet提供所有请求的集中入口点。它应该基于请求提供的信息来创建Model,比如路径信息或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模式上,例如/pages/*, *。做,甚至只是*.html。

对于前缀模式,例如/pages/*,您可以调用URL,例如http://example.com/pages/register, http://example.com/pages/login等,并提供/WEB-INF/register.jsp, /WEB-INF/login.jsp与适当的GET和POST操作。零件注册,登录等,然后可以通过request.getPathInfo(),如上例所示。

当你使用后缀模式,如*。do, *.html等,然后你可以调用URL像http://example.com/register.do, http://example.com/login.do等,你应该改变在这个答案(也是ActionFactory)的代码示例提取注册和登录部分由request.getServletPath()代替。


策略模式

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

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

您可能想要使用自定义异常(如ActionException)使异常更加特定。这只是一个基本的开始示例,其余的都取决于您。

这里有一个LoginAction的例子,它(顾名思义)登录用户。用户本身又是一个数据模型。视图知道用户的存在。

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方法模式。基本上,它应该提供一个创建方法,返回抽象/接口类型的具体实现。在这种情况下,它应该根据请求提供的信息返回Action接口的实现。例如,方法和pathinfo (pathinfo是请求URL中上下文和servlet路径之后的部分,不包括查询字符串)。

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

动作应该是一些静态的/应用范围的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());
    }
}

请记住,在没有映射的情况下创建“不做任何事”操作。例如,让它直接返回request.getPathInfo().substring(1)。


其他模式

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

To get a step further, you could use the Facade pattern to create a Context class which in turn wraps the request and response objects and offers several convenience methods delegating to the request and response objects and pass that as argument into the Action#execute() method instead. This adds an extra abstract layer to hide the raw Servlet API away. You should then basically end up with zero import javax.servlet.* declarations in every Action implementation. In JSF terms, this is what the FacesContext and ExternalContext classes are doing. You can find a concrete example in this answer.

然后是State模式,您希望添加一个额外的抽象层来拆分收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务。在JSF术语中,这就是生命周期所做的事情。

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

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


参见:

Java核心库中的GoF设计模式示例 请求MVC和组件MVC的区别 在JSP页面使用MVC和DAO模式在HTML中显示JDBC ResultSet JSF MVC框架中的MVC组件是什么? JSF控制器、服务和DAO

其他回答

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

一些应用程序可能需要chain -of- responbility_pattern

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

使用这个模式的用例:

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

有用的SE问题/文章:

我为什么要用责任链而不是装饰师?

责任链的一般用法?

来自oodesign的责任链模式

从源代码制作开始的责任链

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

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

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

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


模型-视图-控制器模式

The core (architectural) design pattern you'd like to use is the Model-View-Controller pattern. The Controller is to be represented by a Servlet which (in)directly creates/uses a specific Model and View based on the request. The Model is to be represented by Javabean classes. This is often further dividable in Business Model which contains the actions (behaviour) and Data Model which contains the data (information). The View is to be represented by JSP files which have direct access to the (Data) Model by EL (Expression Language).

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

Request (action) based MVC: this is the simplest to implement. The (Business) Model works directly with HttpServletRequest and HttpServletResponse objects. You have to gather, convert and validate the request parameters (mostly) yourself. The View can be represented by plain vanilla HTML/CSS/JS and it does not maintain state across requests. This is how among others Spring MVC, Struts and Stripes works. Component based MVC: this is harder to implement. But you end up with a simpler model and view wherein all the "raw" Servlet API is abstracted completely away. You shouldn't have the need to gather, convert and validate the request parameters yourself. The Controller does this task and sets the gathered, converted and validated request parameters in the Model. All you need to do is to define action methods which works directly with the model properties. The View is represented by "components" in flavor of JSP taglibs or XML elements which in turn generates HTML/CSS/JS. The state of the View for the subsequent requests is maintained in the session. This is particularly helpful for server-side conversion, validation and value change events. This is how among others JSF, Wicket and Play! works.

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

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


前端控制器模式(中介模式)

首先,控制器部分应该实现Front Controller模式(这是一种专门的中介模式)。它应该只包含一个servlet,该servlet提供所有请求的集中入口点。它应该基于请求提供的信息来创建Model,比如路径信息或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模式上,例如/pages/*, *。做,甚至只是*.html。

对于前缀模式,例如/pages/*,您可以调用URL,例如http://example.com/pages/register, http://example.com/pages/login等,并提供/WEB-INF/register.jsp, /WEB-INF/login.jsp与适当的GET和POST操作。零件注册,登录等,然后可以通过request.getPathInfo(),如上例所示。

当你使用后缀模式,如*。do, *.html等,然后你可以调用URL像http://example.com/register.do, http://example.com/login.do等,你应该改变在这个答案(也是ActionFactory)的代码示例提取注册和登录部分由request.getServletPath()代替。


策略模式

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

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

您可能想要使用自定义异常(如ActionException)使异常更加特定。这只是一个基本的开始示例,其余的都取决于您。

这里有一个LoginAction的例子,它(顾名思义)登录用户。用户本身又是一个数据模型。视图知道用户的存在。

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方法模式。基本上,它应该提供一个创建方法,返回抽象/接口类型的具体实现。在这种情况下,它应该根据请求提供的信息返回Action接口的实现。例如,方法和pathinfo (pathinfo是请求URL中上下文和servlet路径之后的部分,不包括查询字符串)。

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

动作应该是一些静态的/应用范围的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());
    }
}

请记住,在没有映射的情况下创建“不做任何事”操作。例如,让它直接返回request.getPathInfo().substring(1)。


其他模式

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

To get a step further, you could use the Facade pattern to create a Context class which in turn wraps the request and response objects and offers several convenience methods delegating to the request and response objects and pass that as argument into the Action#execute() method instead. This adds an extra abstract layer to hide the raw Servlet API away. You should then basically end up with zero import javax.servlet.* declarations in every Action implementation. In JSF terms, this is what the FacesContext and ExternalContext classes are doing. You can find a concrete example in this answer.

然后是State模式,您希望添加一个额外的抽象层来拆分收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务。在JSF术语中,这就是生命周期所做的事情。

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

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


参见:

Java核心库中的GoF设计模式示例 请求MVC和组件MVC的区别 在JSP页面使用MVC和DAO模式在HTML中显示JDBC ResultSet JSF MVC框架中的MVC组件是什么? JSF控制器、服务和DAO

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

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

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

3) A jsp page which is used to render the final HTML of the page. The jsp page is a hybrid of HTML and special struts tags used to access and manipulate data in the form. Although struts allows users to insert Java code into jsp files you should be very cautious about doing that because it makes your code more difficult to read. Java code inside jsp files is difficult to debug and can not be unit tested. If you find yourself writing more than 4-5 lines of java code inside a jsp file the code should probably be moved to the action.

IMHO, there is not much difference in case of web application if you look at it from the angle of responsibility assignment. However, keep the clarity in the layer. Keep anything purely for the presentation purpose in the presentation layer, like the control and code specific to the web controls. Just keep your entities in the business layer and all features (like add, edit, delete) etc in the business layer. However rendering them onto the browser to be handled in the presentation layer. For .Net, the ASP.NET MVC pattern is very good in terms of keeping the layers separated. Look into the MVC pattern.