我正在设计一个简单的基于Web的应用程序.我是这个基于Web的域的新手.我需要您对设计模式的建议,例如如何在Servlet中分配责任,制作新Servlet的标准等.
实际上,我的主页上有很少的实体,并且每个实体都对应于我们添加,编辑和删除等几个选项.之前我每个选项使用一个Servlet,比如Servlet1,用于添加entity1,Servlet2用于编辑entity1等等,这样我们最终得到了大量的servlet.
现在我们正在改变我们的设计.我的问题是你如何选择如何选择servlet的责任.我们是否应该为每个实体安装一个Servlet,它将处理所有选项并将请求转发给服务层.或者我们应该为整个页面都有一个servlet来处理整个页面请求,然后将其转发到相应的服务层?此外,请求对象是否应转发到服务层.
有点像样的Web应用程序由多种设计模式组成.我只会提到最重要的一些.
您要使用的核心(架构)设计模式是模型 - 视图 - 控制器模式.该控制器是由(上)直接创建/使用特定一个Servlet来表示模型和察看基于该请求.该模型由Javabean类表示.在商业模型中,这通常可以进一步分类,其中包含动作(行为)和包含数据(信息)的数据模型.该视图是由具有对(直接访问JSP文件来表示数据)模型由EL(表达式语言).
然后,根据动作和事件的处理方式进行变化.受欢迎的是:
基于请求(动作)的MVC:这是最简单的实现.的(业务)模式直接与HttpServletRequest
和HttpServletResponse
对象.您必须自己收集,转换和验证请求参数(主要是).该视图可以通过普通的香草HTML/CSS/JS来表示,它不跨请求维护状态.这就是其他Spring MVC,Struts和Stripes的工作原理.
基于组件的MVC:这更难实现.但最终会得到一个更简单的模型和视图,其中所有"原始"Servlet API都被完全抽象出来.您不应该自己收集,转换和验证请求参数.所述控制器执行此任务,并设置在所收集的,转换和验证请求参数模型.您需要做的就是定义直接与模型属性一起使用的操作方法.该视图由JSP标记库或依次产生HTML/CSS/JS的XML元素的味道"组件"来表示.观点的状态后续请求在会话中保留.这对于服务器端转换,验证和值更改事件特别有用.这是其他JSF,Wicket和Play等等!作品.
作为旁注,使用自行开发的MVC框架进行游戏是一项非常好的学习练习,只要您将其保留用于个人/私人目的,我就会推荐它.但是一旦你专业,那么强烈建议选择一个现有的框架,而不是重新发明自己的框架.与自己开发和维护一个强大的框架相比,学习现有的,完善的框架需要更长的时间.
在下面的详细解释中,我将限制自己请求基于MVC,因为这更容易实现.
首先,Controller部分应该实现Front Controller模式(这是一种特殊的Mediator模式).它应该只包含一个servlet,它提供所有请求的集中入口点.它应该基于请求可用的信息创建模型,例如pathinfo或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); } }
执行操作应返回一些标识符以定位视图.最简单的方法是将它用作JSP的文件名.将此servlet映射到特定url-pattern
的web.xml
,例如/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行为.的部件register
,login
等然后可通过request.getPathInfo()
如在上面的例子.
当你使用后缀模式,比如*.do
,*.html
等等,那么你可以然后调用URL的喜欢http://example.com/register.do,http://example.com/login.do,等你应该改变这个答案中的代码示例(也是ActionFactory
)来代替提取register
和login
部分request.getServletPath()
.
本Action
应遵循的策略模式.它需要被定义为一个抽象/接口类型,它应该基于抽象方法的传入参数来完成工作(这与Command模式的区别在于,抽象/接口类型应该基于在创建实现期间传入的参数).
public interface Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception; }
您可能希望Exception
使用自定义异常更具体ActionException
.这只是一个基本的启动示例,其余部分由您决定.
这是一个例子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
应遵循工厂方法模式.基本上,它应该提供一个创建方法,它返回一个抽象/接口类型的具体实现.在这种情况下,它应该Action
根据请求提供的信息返回接口的实现.例如,方法和pathinfo(pathinfo是请求URL中上下文和servlet路径之后的部分,不包括查询字符串).
public static Action getAction(HttpServletRequest request) { return actions.get(request.getMethod() + request.getPathInfo()); }
该actions
反过来应该是一些静态/应用程序范围Map
持有所有已知的行动.这取决于你如何填写这张地图.硬编码:
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)
.
到目前为止,这些都是重要的模式.
为了更进一步,您可以使用Facade模式创建一个Context
类,该类又包装请求和响应对象,并提供委托给请求和响应对象的几个便捷方法,并将其作为参数传递给Action#execute()
方法.这会添加一个额外的抽象层来隐藏原始的Servlet API.那么你应该在每个实现中基本上都以零 import javax.servlet.*
声明结束Action
.在JSF术语中,这就是FacesContext
和ExternalContext
类正在做的事情.你可以在这个答案中找到一个具体的例子.
然后是状态模式,您希望添加一个额外的抽象层来分割收集请求参数,转换它们,验证它们,更新模型值和执行操作的任务.在JSF术语中,这就是它LifeCycle
正在做的事情.
然后是您要创建基于组件的视图的复合模式,该视图可以与模型连接,其行为取决于基于请求的生命周期的状态.在JSF术语中,这就是UIComponent
代表.
通过这种方式,您可以逐步向基于组件的框架发展.
Java核心库中的GoF设计模式示例
请求MVC和组件MVC之间的区别
使用MVC和DAO模式在JSP页面中以HTML格式显示JDBC ResultSet
JSF MVC框架中的MVC是什么组件?
JSF控制器,服务和DAO
在被打败的MVC模式中,Servlet是"C" - 控制器.
其主要工作是进行初始请求评估,然后根据初始评估将处理发送给特定工作人员.工作者的职责之一可能是设置一些表示层bean并将请求转发到JSP页面以呈现HTML.因此,仅凭此原因,您需要将请求对象传递给服务层.
不过,我不会开始写原始Servlet
课程.他们所做的工作是非常可预测的和样板,这个框架非常好.幸运的是,有许多可用的,经过时间考验的候选者(按字母顺序排列):Apache Wicket,Java Server Faces,Spring等等.
恕我直言,如果你从责任分配的角度来看它,在网络应用的情况下没有太大的区别.但是,要保持图层的清晰度.在表示层中保留任何纯粹用于演示目的的内容,例如特定于Web控件的控件和代码.只需将您的实体保留在业务层和业务层中的所有功能(如添加,编辑,删除)等.但是,将它们渲染到要在表示层中处理的浏览器上.对于.Net,ASP.NET MVC模式在保持层分离方面非常好.看看MVC模式.