设计模式 基于 Web 的应用程序 [已关闭]

2022-08-31 04:46:32

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

实际上,我的主页上几乎没有实体,并且对应于每个实体,我们几乎没有添加,编辑和删除等选项。早些时候,我为每个选项使用一个Servlet,例如Servlet1用于添加实体1,Servlet2用于编辑实体1等,这样我们最终拥有了大量的servlet。

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


答案 1

一个有点体面的Web应用程序由混合设计模式组成。我只提到最重要的那些。


模型视图控制器模式

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

然后,根据操作和事件的处理方式存在变化。流行的有:

  • 基于请求(操作)的 MVC:这是最容易实现的。(业务模型直接与 对象一起工作。您必须自己收集,转换和验证请求参数(主要是)。视图可以由普通的 HTML/CSS/JS 表示,并且它不跨请求维护状态。这就是Spring MVCStrutsStripes等人的工作方式。HttpServletRequestHttpServletResponse

  • 基于组件的MVC:这更难实现。但是你最终会得到一个更简单的模型和视图,其中所有“原始”Servlet API都被完全抽象出来。您不需要自己收集、转换和验证请求参数。控制器执行此任务,并在模型中设置收集、转换和验证的请求参数。您需要做的就是定义直接与模型属性一起使用的操作方法。视图由 JSP taglibs 或 XML 元素的“组件”表示,而 XML 元素又生成 HTML/CSS/JS。后续请求的视图状态在会话中维护。这对于服务器端转换、验证和值更改事件特别有用。这就是JSFWicketPlay!的工作原理。

顺便说一句,使用本土MVC框架进行业余爱好是一个非常好的学习练习,只要您出于个人/私人目的保留它,我确实建议您这样做。但是,一旦你变得专业,那么强烈建议你选择一个现有的框架,而不是重新发明你自己的框架。学习一个现有且开发良好的框架比自己开发和维护一个健壮的框架需要更少的时间。

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


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

首先,控制器部分应实现前端控制器模式(这是一种专门的中介模式)。它应该只包含一个 servlet,它提供所有请求的集中入口点。它应该根据请求提供的信息(如 pathinfo 或 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 映射到特定的 中,例如 ,甚至只是 。url-patternweb.xml/pages/**.do*.html

例如,在前缀模式的情况下,您可以调用URL,如 http://example.com/pages/registerhttp://example.com/pages/login 等,并提供适当的GET和POST操作。然后,部分 、等可通过 request.getPathInfo() 获得,如上例所示。/pages/*/WEB-INF/register.jsp/WEB-INF/login.jspregisterlogin

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


策略模式

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

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

您可能希望使用自定义异常(如 ) 使 更具体。这只是一个基本的开球示例,其余的都取决于你。ExceptionActionException

下面是一个示例(顾名思义)登录到用户中。它本身又是一个数据模型视图知道 存在 .LoginActionUserUser

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.
        }
    }

}

工厂方法模式

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

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

反过来,应该是一些静态/应用程序范围的,它包含所有已知的操作。这取决于你如何填写这张地图。硬编码:actionsMap<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());
    }
}

请记住,要为没有映射的情况创建“不执行任何操作”。例如,让它直接返回然后。Actionrequest.getPathInfo().substring(1)


其他模式

这些是迄今为止的重要模式。

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

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

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

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


另请参阅:


答案 2

在被打败的MVC模式中,Servlet是“C” - 控制器。

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

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


推荐