我正在设计一个简单的基于 Web 的应用程序。我是这个基于 Web 的领域的新手。我需要您对设计模式的建议,例如如何在 Servlet 之间分配责任、制作新 Servlet 的标准等。
实际上,我的主页上几乎没有实体,并且对应于每个实体,我们几乎没有添加、编辑和删除等选项。早些时候,我为每个选项使用一个 Servlet,例如 Servlet1 用于添加实体 1,Servlet2 用于编辑实体 1 等等,这样我们最终拥有了大量的 Servlet。
现在我们正在改变我们的设计。我的问题是您如何准确地选择如何选择 servlet 的职责。我们是否应该每个实体有一个 Servlet,它将处理它的所有选项并将请求转发到服务层。或者我们应该为整个页面设置一个 servlet 来处理整个页面请求,然后将其转发到相应的服务层?另外,请求对象是否应该转发到服务层。
有点像样的 Web 应用程序由多种设计模式组成。我只会提到最重要的那些。
模型视图控制器模式
您想使用的核心(架构)设计模式是Model-View-Controller 模式。 控制器 将由一个 Servlet 表示,该 Servlet 根据请求(内)直接创建/使用特定的模型 和 视图 。 模型 将由 Javabean 类表示。这通常可以在包含操作(行为)的 业务模型 和包含数据(信息)的 数据模型中进一步划分。 视图 将由 JSP 文件表示,这些文件可以通过 EL(表达式语言)直接访问(数据 ) 模型 。
然后,根据操作和事件的处理方式会有所不同。流行的是:
基于请求(动作)的 MVC :这是最容易实现的。( 业务 ) 模型 直接与HttpServletRequest对象HttpServletResponse一起工作。您必须(主要)自己收集、转换和验证请求参数。 视图 可以用普通的 HTML/CSS/JS 表示,它不会跨请求维护状态。这就是Spring MVC、Struts和Stripes的工作方式。
HttpServletRequest
HttpServletResponse
基于组件的 MVC :这更难实现。但是您最终会得到一个更简单的模型和视图,其中所有“原始”Servlet API 都被完全抽象掉了。您不需要自己收集、转换和验证请求参数。 Controller 执行此任务并在 Model 中设置收集、转换和验证的请求 参数 。您需要做的就是定义直接与模型属性一起使用的操作方法。 视图 由 JSP 标签库或 XML 元素风格的“组件”表示,这些元素又生成 HTML/CSS/JS 。 视图 的状态 __后续请求在会话中维护。 这对于服务器端转换、验证和值更改事件特别有用。这就是JSF、Wicket和Play 中的其他内容!作品。
附带说明一下,喜欢使用自制的 MVC 框架是一个非常好的学习练习,只要您出于个人/私人目的保留它,我就推荐它。但是一旦你变得专业,那么强烈建议选择一个现有的框架,而不是重新发明你自己的。从长远来看,学习一个现有的和完善的框架比自己开发和维护一个健壮的框架所花费的时间更少。
在下面的详细解释中,我将限制自己使用基于请求的 MVC,因为这更容易实现。
前端控制器模式(中介者模式)
首先, Controller 部分应该实现Front Controller 模式(这是一种特殊的Mediator 模式)。它应该只包含一个 servlet,它提供所有请求的集中入口点。它应该根据请求可用的信息(例如 pathinfo 或 servletpath、方法和/或特定参数)创建 模型。 在下面的示例中调用了 业务模型。ActionHttpServlet
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); } }
执行动作应该返回一些标识符来定位视图。最简单的方法是将它用作 JSP 的文件名。将此 servlet 映射到特定url-pattern的 in web.xml,例如/pages/*,*.do甚至只是*.html。
url-pattern
web.xml
/pages/*
*.do
*.html
例如,在前缀模式的情况下,/pages/*您可以调用诸如http://example.com/pages/register、http://example.com/pages/login等 URL 并提供适当/WEB-INF/register.jsp的/WEB-INF/login.jspGET 和 POST 操作. 零件register,login等随后可通过request.getPathInfo()如上例中的方式获得。
/WEB-INF/register.jsp
/WEB-INF/login.jsp
register
login
request.getPathInfo()
当您使用 , 等后缀模式*.do时*.html,您可以调用诸如http://example.com/register.do、http://example.com/login.do等 URL,您应该更改此答案(也是ActionFactory)中的代码示例来提取register和login部分request.getServletPath()。
ActionFactory
request.getServletPath()
Action应该遵循策略模式。它需要被定义为一个抽象/接口类型,它应该基于抽象方法的 传入 参数来完成工作(这是与命令模式的区别,其中抽象/接口类型应该基于在 创建 实现期间传入的参数)。
public interface Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception; }
您可能希望Exception使用自定义异常(如ActionException. 这只是一个基本的启动示例,其余的完全取决于您。
Exception
ActionException
这是一个LoginAction(如其名称)登录用户的示例。它User本身又是一个 数据模型 。 View 知道User. _
LoginAction
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根据请求提供的信息返回接口的实现。例如方法和路径信息(路径信息是请求URL中上下文和servlet路径之后的部分,不包括查询字符串)。
public static Action getAction(HttpServletRequest request) { return actions.get(request.getMethod() + request.getPathInfo()); }
反过来actions应该是一些静态的/应用程序Map<String, Action>范围的,它包含所有已知的操作。如何填写此地图由您决定。硬编码:
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请记住为没有映射的情况创建“什么都不做” 。例如让它直接返回request.getPathInfo().substring(1)then。
request.getPathInfo().substring(1)
到目前为止,这些是重要的模式。
为了更进一步,您可以使用Facade 模式创建一个Context类,该类依次包装请求和响应对象,并提供一些方便的方法来委派请求和响应对象,并将其作为参数传递给Action#execute()方法。这增加了一个额外的抽象层来隐藏原始的 Servlet API。然后,您应该基本上在每个实现中都以 零 import javax.servlet.*声明结束。Action在 JSF 术语中,这就是FacesContext和ExternalContext类正在做的事情。你可以在这个答案中找到一个具体的例子。
Context
Action#execute()
import javax.servlet.*
FacesContext
ExternalContext
然后是状态模式,您想添加一个额外的抽象层来拆分收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务。在 JSF 术语中,这就是LifeCycle正在做的事情。
LifeCycle
然后是复合模式,您希望创建一个基于组件的视图,该视图可以附加到模型中,并且其行为取决于基于请求的生命周期的状态。在 JSF 术语中,这就是所UIComponent代表的。
UIComponent
通过这种方式,您可以一点一点地向基于组件的框架发展。