Java 基于 Web 的应用程序设计模式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3541077/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 01:41:53  来源:igfitidea点击:

Design Patterns web based applications

javadesign-patternsjspservlets

提问by mawia

I am designing a simple web-based application. I am new to this web-based domain.I needed your advice regarding the design patterns like how responsibility should be distributed among Servlets, criteria to make new Servlet, etc.

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

Actually, I have few entities on my home page and corresponding to each one of them we have few options like add, edit and delete. Earlier I was using one Servlet per options like Servlet1 for add entity1, Servlet2 for edit entity1 and so on and in this way we ended up having a large number of servlets.

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

Now we are changing our design. My question is how you exactly choose how you choose the responsibility of a servlet. Should we have one Servlet per entity which will process all it's options and forward request to the service layer. Or should we have one servlet for the whole page which will process the whole page request and then forward it to the corresponding service layer? Also, should the request object forwarded to service layer or not.

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

采纳答案by BalusC

A bit decent web application consists of a mix of design patterns. I'll mention only the most important ones.

一个有点像样的 Web 应用程序由多种设计模式组成。我只会提到最重要的。



Model View Controller pattern

模型视图控制器模式

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

您想要使用的核心(架构)设计模式是Model-View-Controller 模式。该控制器是由一个Servlet其中(在)直接创造来表示/使用特定的模型视图基于该请求。该模型是由JavaBean类来表示。这通常在包含操作(行为)的业务模型和包含数据(信息)的数据模型中进一步划分。该视图是由具有对(直接访问JSP文件来表示数据模型由EL(表达式语言)。

Then, there are variations based on how actions and events are handled. The popular ones are:

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

  • Request (action) based MVC: this is the simplest to implement. The (Business) Modelworks directly with HttpServletRequestand HttpServletResponseobjects. You have to gather, convert and validate the request parameters (mostly) yourself. The Viewcan be represented by plain vanilla HTML/CSS/JS and it does not maintain state across requests. This is how among others Spring MVC, Strutsand Stripesworks.

  • 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 Controllerdoes 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 Viewis represented by "components" in flavor of JSP taglibs or XML elements which in turn generates HTML/CSS/JS. The state of the Viewfor 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, Wicketand Play!works.

  • 基于请求(动作)的 MVC:这是最简单的实现。的(业务模式直接与HttpServletRequestHttpServletResponse对象。您必须自己收集、转换和验证请求参数(主要是)。该视图可以通过在请求普通的香草HTML / CSS / JS,它不保持状态来表示。这就是Spring MVCStrutsStripes 的工作方式。

  • 基于组件的 MVC:这更难实现。但是您最终会得到一个更简单的模型和视图,其中所有“原始”Servlet API 都被完全抽象掉了。您不应该需要自己收集、转换和验证请求参数。所述控制器执行此任务,并设置在所收集的,转换后的和验证请求参数模型。您需要做的就是定义直接与模型属性一起使用的操作方法。该视图是通过“组件”在JSP标记库或依次产生HTML / CSS / JS的XML元素的风味表示。视图的状态为后续请求保留在会话中。这对于服务器端转换、验证和值更改事件特别有用。这就是JSFWicketPlay 等的方式!作品。

As a side note, hobbying around with a homegrown MVC framework is a very nice learning exercise, and I do recommend it as long as you keep it for personal/private purposes. But once you go professional, then it's strongly recommended to pick an existing framework rather than reinventing your own. Learning an existing and well-developed framework takes in long term less time than developing and maintaining a robust framework yourself.

附带说明一下,对自己开发的 MVC 框架感兴趣是一个非常好的学习练习,只要您出于个人/私人目的保留它,我确实推荐它。但是一旦你变得专业,那么强烈建议选择一个现有的框架而不是重新发明你自己的框架。与自己开发和维护一个强大的框架相比,学习一个现有的和完善的框架所花费的时间要少得多。

In the below detailed explanation I'll restrict myself to request based MVC since that's easier to implement.

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



Front Controller pattern(Mediator pattern)

前端控制器模式Mediator 模式

First, the Controllerpart should implement the Front Controller pattern(which is a specialized kind of Mediator pattern). It should consist of only a single servlet which provides a centralized entry point of all requests. It should create the Modelbased on information available by the request, such as the pathinfo or servletpath, the method and/or specific parameters. The Business Modelis called Actionin the below HttpServletexample.

首先,Controller部分应该实现Front Controller 模式(这是一种特殊的Mediator 模式)。它应该只包含一个 servlet,它提供所有请求的集中入口点。它应该根据请求可用的信息创建模型,例如路径信息或 servletpath、方法和/或特定参数。该商业模式被称为Action在下面的HttpServlet例子。

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);
    }
}

Executing the action should return some identifier to locate the view. Simplest would be to use it as filename of the JSP. Map this servlet on a specific url-patternin web.xml, e.g. /pages/*, *.door even just *.html.

执行操作应该返回一些标识符来定位视图。最简单的方法是将其用作 JSP 的文件名。将此 servlet 映射到特定的url-patternin web.xml,例如/pages/**.do甚至只是*.html.

In case of prefix-patterns as for example /pages/*you could then invoke URL's like http://example.com/pages/register, http://example.com/pages/login, etc and provide /WEB-INF/register.jsp, /WEB-INF/login.jspwith the appropriate GET and POST actions. The parts register, login, etc are then available by request.getPathInfo()as in above example.

在前缀模式,例如情况下,/pages/*你可以然后调用URL就像http://example.com/pages/registerhttp://example.com/pages/login等,提供/WEB-INF/register.jsp/WEB-INF/login.jsp使用适当的GET和POST行为. 部件registerlogin等随后可通过request.getPathInfo()如上例所示使用。

When you're using suffix-patterns like *.do, *.html, etc, then you could then invoke URL's like http://example.com/register.do, http://example.com/login.do, etc and you should change the code examples in this answer (also the ActionFactory) to extract the registerand loginparts by request.getServletPath()instead.

当您使用, 等后缀模式时*.do*.html您可以调用 URL,如http://example.com/register.dohttp://example.com/login.do等,您应该更改此答案中的代码示例(也是ActionFactory)来提取registerlogin部分request.getServletPath()



Strategy pattern

策略模式

The Actionshould follow the Strategy pattern. It needs to be defined as an abstract/interface type which should do the work based on the passed-inarguments of the abstract method (this is the difference with the Command pattern, wherein the abstract/interface type should do the work based on the arguments which are been passed-in during the creationof the implementation).

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

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

You may want to make the Exceptionmore specific with a custom exception like ActionException. It's just a basic kickoff example, the rest is all up to you.

您可能希望Exception使用自定义异常更具体,例如ActionException. 这只是一个基本的启动示例,其余的都取决于您。

Here's an example of a LoginActionwhich (as its name says) logs in the user. The Useritself is in turn a Data Model. The Viewis aware of the presence of the User.

这是一个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.
        }
    }

}


Factory method pattern

工厂方法模式

The ActionFactoryshould follow the Factory method pattern. Basically, it should provide a creational method which returns a concrete implementation of an abstract/interface type. In this case, it should return an implementation of the Actioninterface based on the information provided by the request. For example, the methodand pathinfo(the pathinfo is the part after the context and servlet path in the request URL, excluding the query string).

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

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

The actionsin turn should be some static/applicationwide Map<String, Action>which holds all known actions. It's up to you how to fill this map. Hardcoding:

actions反过来应该是一些静态/应用程序范围Map<String, Action>它包含所有已知的行动。如何填充这张地图取决于你。硬编码:

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

Or configurable based on a properties/XML configuration file in the classpath: (pseudo)

或者基于类路径中的属性/XML 配置文件进行配置:(伪)

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

Or dynamically based on a scan in the classpath for classes implementing a certain interface and/or annotation: (pseudo)

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

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

Keep in mind to create a "do nothing" Actionfor the case there's no mapping. Let it for example return directly the request.getPathInfo().substring(1)then.

请记住,Action为没有映射的情况创建“什么都不做” 。例如让它直接返回request.getPathInfo().substring(1)then 。



Other patterns

其他图案

Those were the important patterns so far.

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

To get a step further, you could use the Facade patternto create a Contextclass 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 zeroimport javax.servlet.*declarations in every Actionimplementation. In JSF terms, this is what the FacesContextand ExternalContextclasses are doing. You can find a concrete example in this answer.

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

Then there's the State patternfor the case that you'd like to add an extra abstraction layer to split the tasks of gathering the request parameters, converting them, validating them, updating the model values and execute the actions. In JSF terms, this is what the LifeCycleis doing.

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

Then there's the Composite patternfor the case that you'd like to create a component based view which can be attached with the model and whose behaviour depends on the state of the request based lifecycle. In JSF terms, this is what the UIComponentrepresent.

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

This way you can evolve bit by bit towards a component based framework.

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



See also:

也可以看看:

回答by Kangkan

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.

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

回答by Alexander Pogrebnyak

In the beaten-up MVC pattern, the Servlet is "C" - controller.

在老一套的 MVC 模式中,Servlet 是“C”——控制器。

Its main job is to do initial request evaluation and then dispatch the processing based on the initial evaluation to the specific worker. One of the worker's responsibilities may be to setup some presentation layer beans and forward the request to the JSP page to render HTML. So, for this reason alone, you need to pass the request object to the service layer.

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

I would not, though, start writing raw Servletclasses. The work they do is very predictable and boilerplate, something that framework does very well. Fortunately, there are many available, time-tested candidates ( in the alphabetical order ): Apache Wicket, Java Server Faces, Springto name a few.

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

回答by EsotericNonsense

I have used the strutsframework and find it fairly easy to learn. When using the struts framework each page of your site will have the following items.

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

1) An action which is used is called every time the HTML page is refreshed. The action should populate the data in the form when the page is first loaded and handles interactions between the web UI and the business layer. If you are using the jsp page to modify a mutable java object a copy of the java object should be stored in the form rather than the original so that the original data doesn't get modified unless the user saves the page.

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

2) The form which is used to transfer data between the action and the jsp page. This object should consist of a set of getter and setters for attributes that need to be accessible to the jsp file. The form also has a method to validate data before it gets persisted.

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

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.

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

回答by Ravindra babu

BalusCexcellent answer covers most of the patterns for web applications.

BalusC优秀的答案涵盖了 Web 应用程序的大部分模式。

Some application may require Chain-of-responsibility_pattern

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

In object-oriented design, the chain-of-responsibilitypattern 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.

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

Use case to use this pattern:

使用此模式的用例:

When handler to process a request(command) is unknown and this request can be sent to multiple objects. Generally you set successorto object. If current object can't handle the request or process the request partially and forward the same request to successorobject.

当处理请求(命令)的处理程序未知并且此请求可以发送到多个对象时。通常,您将后继对象设置为对象。如果当前对象无法处理请求或部分处理请求并将相同的请求转发给后继对象。

Useful SE questions/articles:

有用的 SE 问题/文章:

Why would I ever use a Chain of Responsibility over a Decorator?

为什么我会在装饰器上使用责任链?

Common usages for chain of responsibility?

责任链的常见用法?

chain-of-responsibility-patternfrom oodesign

oodesign的责任链模式

chain_of_responsibilityfrom sourcemaking

来源制作的责任链