想改善这个问题吗? 更新问题,以便通过编辑此帖子以事实和引用的形式回答。
去年关闭。
我正在设计一个简单的基于Web的应用程序。 我是这个基于Web的领域的新手,我需要您提供有关设计模式的建议,例如如何在Servlet之间分配职责,创建新Servlet的条件等。
实际上,我主页上的实体很少,而与每个实体相对应,我们几乎没有添加,编辑和删除等选项。 早些时候,我为每个选项使用一个Servlet,例如Servlet1用于添加实体1,Servlet2用于编辑实体1,依此类推,这样我们最终拥有大量的Servlet。
现在,我们正在更改设计。 我的问题是,如何正确选择如何选择servlet的责任。 每个实体是否应该有一个Servlet,它将处理所有选项并将请求转发到服务层。 还是应该为整个页面设置一个servlet,它将处理整个页面请求,然后将其转发到相应的服务层? 同样,请求对象是否应该转发到服务层。
#1楼
我使用了struts框架,发现它相当容易学习。 使用struts框架时,网站的每个页面都会包含以下项目。
1)每次刷新HTML页面时,都会调用使用的操作。 该操作应在首次加载页面时填充表单中的数据,并处理Web UI与业务层之间的交互。 如果使用jsp页面修改可变的Java对象,则应以表单而不是原始形式存储Java对象的副本,这样除非用户保存页面,否则原始数据不会被修改。
2)用于在动作和jsp页面之间传输数据的表格。 该对象应该由一组getter和setter组成,这些setter和setter用于jsp文件需要访问的属性。 表单还具有在持久化之前验证数据的方法。
3)一个jsp页面,用于呈现页面的最终HTML。 jsp页面是HTML和特殊的struts标记的混合体,用于访问和操作表单中的数据。 尽管struts允许用户将Java代码插入jsp文件,但是您应该非常谨慎,因为这样做会使您的代码更难以阅读。 jsp文件中的Java代码很难调试,不能进行单元测试。 如果发现自己在一个jsp文件中编写了4-5行以上的Java代码,则该代码可能应该移至该动作中。
#2楼
恕我直言,从责任分配的角度来看,Web应用程序的区别不大。 但是,请保持层中的清晰度。 纯粹出于表示目的,将任何内容保留在表示层中,例如控件和特定于Web控件的代码。 只需将您的实体保留在业务层中,并将所有功能(如添加,编辑,删除)保留在业务层中即可。 但是,将它们渲染到浏览器中以在表示层中进行处理。 对于.Net,ASP.NET MVC模式在保持层分离方面非常好。 查看MVC模式。
#3楼
在传统的MVC模式中,Servlet是“ C”-控制器。
它的主要工作是进行初始请求评估,然后根据初始评估将处理分派给特定的工作人员。 工作人员的职责之一可能是设置一些表示层bean,并将请求转发到JSP页面以呈现HTML。 因此,仅出于这个原因,您需要将请求对象传递到服务层。
不过,我不会开始编写原始的Servlet
类。 他们所做的工作是非常可预测的,并且很容易完成,而框架做得很好。 幸运的是,有许多可用的,经过时间考验的候选人(按字母顺序): Apache Wicket , Java Server Faces , Spring等 。
#4楼
有点像样的Web应用程序包含多种设计模式。 我只提到最重要的那些。
模型视图控制器模式
您要使用的核心(架构)设计模式是Model-View-Controller模式 。 Controller将由一个Servlet表示,该Servlet将基于请求直接创建/使用特定的Model和View 。 该模型将由Javabean类表示。 在包含动作(行为)的业务模型和包含数据(信息)的数据模型中 ,这通常可以进一步划分。 视图将由JSP文件表示,该文件可通过EL(表达语言)直接访问( 数据 ) 模型 。
然后,根据操作和事件的处理方式而有所不同。 最受欢迎的是:
基于请求(动作)的MVC :这是最简单的实现。 ( 业务 ) 模型直接与
HttpServletRequest
和HttpServletResponse
对象一起使用。 您必须自己(主要)收集,转换和验证请求参数。 该视图可以用普通的HTML / CSS / JS表示,并且不会在请求中保持状态。 这就是Spring MVC , Struts和Stripes的工作方式。基于组件的MVC :这很难实现。 但是最终您得到了一个更简单的模型和视图,其中所有“原始” Servlet API都被完全抽象了。 您不需要自己收集,转换和验证请求参数。 控制器执行此任务并在Model中设置收集,转换和验证的请求参数。 您需要做的就是定义直接与模型属性一起使用的操作方法。 视图由具有JSP标签库或XML元素风格的“组件”表示,这些元素又生成HTML / CSS / JS。 会话中将维护后续请求的视图状态。 这对于服务器端转换,验证和值更改事件特别有用。 这就是JSF , Wicket和Play的方式! 作品。
附带说明一下,使用本地的MVC框架是一种非常不错的学习方法,只要您出于个人/私人目的保留它,我还是建议您这样做。 但是一旦您成为专业人士,则强烈建议您选择一个现有的框架,而不要重塑自己的框架。 学习现有的和完善的框架要比自己开发和维护一个健壮的框架花费更少的时间。
在下面的详细说明中,我将自己限制为基于请求的MVC,因为它更易于实现。
前控制器模式 ( 调解器模式 )
首先, Controller部分应实现Front Controller模式 (这是一种专门的Mediator模式 )。 它应该仅包含一个servlet,该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映射到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操作。 如上面的示例所示,然后可以通过request.getPathInfo()
获得部件register
, login
等。
当你使用后缀模式,如*.do
, *.html
,等等,那么你可以然后调用URL的喜欢http://example.com/register.do , http://example.com/login.do ,等等,您应该更改此答案中的代码示例(以及ActionFactory
),以改为通过request.getServletPath()
提取register
和login
部分。
策略模式
该Action
应遵循战略模式 。 需要将其定义为抽象/接口类型,该类型应基于抽象方法的传入参数来完成工作(这与Command模式不同,其中抽象/接口类型应基于在创建实现过程中传入的参数)。
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
您可能需要使用ActionException
类的自定义异常使Exception
更具体。 这只是一个基本的启动示例,其余的全由您决定。
这是一个LoginAction
的示例,该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.
}
}
}
工厂方法模式
ActionFactory
应该遵循Factory方法模式 。 基本上,它应该提供一种创建方法,该方法返回抽象/接口类型的具体实现。 在这种情况下,它应基于请求提供的信息返回Action
接口的实现。 例如,所述方法和PATHINFO (该PATHINFO是在请求URL中的上下文和servlet路径之后的部分,不包括查询字符串)。
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
反过来,这些actions
应该是一些包含所有已知动作的静态/应用程序级Map<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());
}
}
在没有映射的情况下,请记住创建“不执行任何Action
” Action
。 例如,让它直接返回request.getPathInfo().substring(1)
。
其他图案
这些是到目前为止的重要模式。
为了更进一步,您可以使用Facade模式创建一个Context
类,该类反过来包装请求和响应对象,并提供委托给请求和响应对象的几种便捷方法,并将其作为参数传递给Action#execute()
方法代替。 这增加了一个额外的抽象层以隐藏原始Servlet API。 然后,您基本上应该在每个Action
实现中以零个 import javax.servlet.*
声明结束。 用JSF术语来说,这就是FacesContext
和ExternalContext
类的作用。 您可以在此答案中找到一个具体示例。
然后是一种状态模式 ,您想添加一个额外的抽象层来拆分收集请求参数,对其进行转换,验证,更新模型值并执行操作的任务。 用JSF来讲,这就是LifeCycle
所做的。
然后是针对您想要创建基于组件的视图的Composite模式 ,该视图可以随模型一起附加,并且其行为取决于基于请求的生命周期的状态。 用JSF术语来说,这就是UIComponent
代表的。
这样,您可以一点一点地朝着基于组件的框架发展。
也可以看看:
- Java核心库中的GoF设计模式示例
- 请求MVC和组件MVC之间的区别
- 使用MVC和DAO模式在JSP页面的HTML中显示JDBC ResultSet
- JSF MVC框架中的MVC有哪些组件?
- JSF控制器,服务和DAO
#5楼
BalusC的出色答案涵盖了Web应用程序的大多数模式。
一些应用程序可能需要Chain-of-responsibility_pattern
在面向对象的设计中, 责任链模式是一种由命令对象的源和一系列处理对象组成的设计模式。 每个处理对象都包含定义其可以处理的命令对象类型的逻辑。 其余的将传递到链中的下一个处理对象。
用例使用此模式:
当处理程序处理一个请求(命令)是未知的并且此请求可以发送到多个对象时。 通常,您将后继对象设置为对象。 如果当前对象无法处理请求或无法部分处理请求,则将同一请求转发给后继对象。
有用的SE问题/文章:
oodesign 的责任链模式
chain_of_responsibility从sourcemaking
来源:oschina
链接:https://my.oschina.net/stackoom/blog/3171598