设计模式基于 Web 的应用程序
我正在设计一个简单的基于 Web 的应用程序.我是这个基于 Web 的领域的新手.我需要您对设计模式的建议,例如如何在 Servlet 之间分配责任、制作新 Servlet 的标准等.
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.
实际上,我的主页上几乎没有实体,对应于每个实体,我们几乎没有添加、编辑和删除等选项.早些时候,我为每个选项使用一个 Servlet,例如 Servlet1 用于添加实体 1,Servlet2 用于编辑实体 1 等等,这样我们最终拥有了大量的 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 的职责.我们是否应该每个实体有一个 Servlet,它将处理它的所有选项并将请求转发到服务层.或者我们应该为整个页面设置一个 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.
推荐答案
有点像样的 Web 应用程序由多种设计模式组成.我只会提到最重要的.
A bit decent web application consists of a mix of design patterns. I'll mention only the most important ones.
您想使用的核心(架构)设计模式是 模型-视图-控制器模式.Controller 将由 Servlet 表示,该 Servlet (in) 根据请求直接创建/使用特定的 Model 和 View.Model 将由 Javabean 类表示.这通常可以在包含操作(行为)的业务模型和包含数据(信息)的数据模型中进一步划分.View 将由 JSP 文件表示,这些文件可以通过 EL(表达式语言)直接访问(Data)Model.
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).
然后,根据操作和事件的处理方式会有所不同.流行的是:
Then, there are variations based on how actions and events are handled. The popular ones are:
基于请求(动作)的 MVC:这是最容易实现的.(Business) Model 直接与
HttpServletRequest
和HttpServletResponse
对象一起使用.您必须(主要)自己收集、转换和验证请求参数.View 可以用普通的 HTML/CSS/JS 表示,它不会跨请求维护状态.这就是 Spring MVC、Struts 和 Stripes 有效.
Request (action) based MVC: this is the simplest to implement. The (Business) Model works directly with
HttpServletRequest
andHttpServletResponse
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.
基于组件的 MVC:这更难实现.但是你最终会得到一个更简单的模型和视图,其中所有原始"的Servlet API 被完全抽象出来.您不需要自己收集、转换和验证请求参数.Controller 执行此任务并在 Model 中设置收集、转换和验证的请求参数.您需要做的就是定义直接与模型属性一起使用的操作方法.视图由组件"表示.以 JSP 标签库或 XML 元素的形式生成 HTML/CSS/JS.后续请求的 View 状态在会话中维护.这对于服务器端转换、验证和值更改事件特别有用.这就是 JSF、Wicket 和 Play! 有效.
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 框架是一种非常好的学习练习,我推荐它,只要您出于个人/私人目的而保留它.但是一旦你变得专业,那么强烈建议选择一个现有的框架,而不是重新发明你自己的.从长远来看,学习一个现有的和完善的框架比自己开发和维护一个健壮的框架所花费的时间更少.
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.
首先,Controller 部分应该实现 前端控制器模式(这是一种特殊的 Mediator 模式).它应该只包含一个 servlet,它提供所有请求的集中入口点.它应该根据请求可用的信息(例如 pathinfo 或 servletpath、方法和/或特定参数)创建 Model.Business Model 在下面的 ActionHttpServlet.html" rel="noreferrer">HttpServlet
示例.
First, the Controller part 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 Model based on information available by the request, such as the pathinfo or servletpath, the method and/or specific parameters. The Business Model is called Action
in the below HttpServlet
example.
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
.
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-pattern
in web.xml
, e.g. /pages/*
, *.do
or even just *.html
.
如果是前缀模式,例如 /pages/*
您可以调用 URL,例如 http://example.com/pages/register, http://example.com/pages/login 等,并为 /WEB-INF/register.jsp
、/WEB-INF/login.jsp
提供适当的 GET 和 POST 操作.register、login
等部分http/HttpServletRequest.html#getPathInfo--" rel="noreferrer">request.getPathInfo()
如上例.
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.jsp
with the appropriate GET and POST actions. The parts register
, login
, etc are then available by request.getPathInfo()
as in above example.
当您使用像 *.do
、*.html
等后缀模式时,您可以调用像 http://example.com/register.do, http://example.com/login.do 等,您应该更改此答案中的代码示例(也是 ActionFactory
)以提取 注册
和 login
部分由 request.getServletPath()
代替.
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 register
and login
parts by request.getServletPath()
instead.
Action
应该遵循 策略模式.它需要被定义为一个抽象/接口类型,它应该根据抽象方法的传入参数来完成工作(这是与 命令模式,其中抽象/接口类型应该根据在创建过程中传入的参数来完成工作 的实现).
The Action
should follow the Strategy pattern. It needs to be defined as an abstract/interface type which should do the work based on the passed-in arguments 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 creation of the implementation).
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
您可能希望使用 ActionException
等自定义异常来使 Exception
更加具体.这只是一个基本的启动示例,其余的完全取决于您.
You may want to make the Exception
more specific with a custom exception like ActionException
. It's just a basic kickoff example, the rest is all up to you.
这是一个 LoginAction
的示例,它(如其名称所示)登录用户.User
本身又是一个Data Model.View 知道 User
的存在.
Here's an example of a LoginAction
which (as its name says) logs in the user. The User
itself is in turn a Data Model. The View is aware of the presence of the 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 路径之后的部分,不包括查询字符串).
Factory method pattern
The ActionFactory
should 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 Action
interface based on the information provided by the request. For example, the method and pathinfo (the pathinfo is the part after the context and servlet path in the request URL, excluding the query string).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
actions
反过来应该是一些静态/应用程序范围的 Map
,它包含所有已知的动作.如何填写此地图由您决定.硬编码:
The actions
in 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.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
或者基于类路径中的属性/XML配置文件进行配置:(伪)
Or configurable based on a properties/XML configuration file in the classpath: (pseudo)
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());
}
}
记住要创建一个什么都不做"的;Action
用于没有映射的情况.例如让它直接返回 request.getPathInfo().substring(1)
然后.
Keep in mind to create a "do nothing" Action
for the case there's no mapping. Let it for example return directly the request.getPathInfo().substring(1)
then.
到目前为止,这些是重要的模式.
Those were the important patterns so far.
为了更进一步,您可以使用 Facade 模式 创建一个 Context
类依次包装请求和响应对象,并提供几种方便的方法,委托给请求和响应对象,并将其作为参数传递给 Action#execute()
方法.这增加了一个额外的抽象层来隐藏原始的 Servlet API.然后,您应该基本上在每个 Action
实现中都有 zero import javax.servlet.*
声明.在 JSF 术语中,这就是 FacesContext
和 ExternalContext
类正在做.您可以在 this回答.
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.
然后是 状态模式,用于您想要添加额外抽象的情况层来拆分收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务.在 JSF 术语中,这就是 LifeCycle
正在做.
Then there's the State pattern for 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 LifeCycle
is doing.
然后是 复合模式,如果您想创建一个基于可以附加到模型的视图,其行为取决于基于请求的生命周期的状态.在 JSF 术语中,这就是 UIComponent
表示.
Then there's the Composite pattern for 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 UIComponent
represent.
通过这种方式,您可以一点一点地向基于组件的框架发展.
This way you can evolve bit by bit towards a component based framework.
- Java 核心库中的 GoF 设计模式示例
- 请求MVC和组件MVC的区别
- 使用 MVC 和 DAO 模式在 JSP 页面的 HTML 中显示 JDBC ResultSet
- JSF MVC框架中的MVC有哪些组件?
- JSF 控制器、服务和 DAO
相关文章