wei_qiu
发贴: 0
积分: 0
|
于 2003-02-17 17:01
转帖 用“模型-视图-控制器” 模式搭建中小型网站 作者 : 杨健 (ytjcopy@263.net) 中南工业大学 2001 年 12 月
网站开发是网络信息时代一个非常流行的话题。因此,各种网站开发技术层出不穷(这也正是市场的巨大作用的结果),如现在比较流行的CGI、Asp、Php、Jsp等等。这些技术都有各自的优缺点,也很难比较哪一种技术要特别好。升阳微系统公司推出的J2EE(Java 2 企业版)技术,以其特有的优势在网站建设等很多方面得到了很广的应用。本文将介绍如何运用Jsp、Servelet、JavaBean、xml等技术,用MVC模式(模型-视图-控制器模式)来搭建中小型网站。 面向中小型应用 目前中小型网站用Asp,Php技术的比较多,因为运用这些技术来开发有速度快,开发周期短,经济上可行等特点。但这些技术都不是面向对象的。这样就出现了很多问题。因为不是面向对象,所以在运用这些技术开发时更像是在写函数和将这些函数杂乱的堆积。可想而知,这样的程序设计,它的耦合程度有多高。应用可能在比较短的时间里完成了,但同时也埋下了隐患。如以后功能的修改,扩充,程序的维护等等。因为程序的高耦合,要进行功能修改和扩充是非常困难的。而且,往往维护人员与开发人员不是同一个人,所以即使有详尽的文档,也很难理清程序里纵横交错的联系。而J2EE技术是完全面向对象的。运用面向对象的程序设计思想来进行设计与编程,将大大改善程序的可理解性,增强以后程序的可维护性,从而弥补的前面提到的技术的不足。本文没有涉及到使用EJB(Enterprise Java Bean)技术(虽然这项技术是J2EE的核心,并且一般是运用在大型的应用中)。因为笔者认为一般中小型网站对应用的要求可能要低一些,而且还要考虑到经济等其它方面的因素。更重要的是,本文将着重介绍一种设计模式:即模型-视图-控制器模式(Model-View-Controller)。相信J2EE技术与该模式的结合,能带给网站设计人员一种崭新的思路。
模型-视图-控制器模式 模型-视图-控制器模式是一种“分治”的思想。模型,即相关的数据,它是对象的内在属性;视图是模型的外在表现形式,一个模型可以对应一个或者多个视图,视图还具有与外界交互的功能;控制器是模型与视图的联系纽带,控制器提取通过视图传输进来的外部信息转化成相应事件,然后由对应的控制器对模型进行更新;相应的,模型的更新与修改将通过控制器通知视图,保持视图与模型的一致性。下图(图1。1)揭示了这三者之间的关系:
(图1。1)
系统设计 弄清楚MVC的框架之后,下面介绍怎样运用该模式来设计实现一个网站。从上文分析可以知道,MVC模式主要包括三方面的内容:即模型(Model)、视图(View)、控制器(Controller)。所以,该系统的设计也将从这三方面来介绍。
系统概览 该系统属于浏览器/服务器模型(Browser/Server)。一般的,客户通过浏览器发送HTTP请求给服务器端Web 服务器,Web 服务器接收该请求并且进行相应处理,然后将处理后的结果返回到客户的浏览器中。在客户端,浏览器中呈现的正是该系统的视图部分。视图的作用就是提供给客户一个可视化的界面,并且允许客户输入一些数据来与服务器端程序交互。对客户来说,他只能看到视图,而模型和控制器对他则是透明的。在这里Web 服务器仅仅起到提供HTTP服务的作用。Web 服务器将客户提交的HTTP 请求交给后方的Jsp、Servlet引擎,并且进一步交给其中的控制器来处理。控制器按照从xml配置文件中提取的请求映射表将该请求映射到相应的处理器(Handler);处理器对模型进行更新、修改等操作,处理完后返回结果给控制器;控制器根据结果通知视图做相应变化,并且选择相应视图返回给客户。下图(图1。2)说明了这一协作过程。
(图1。2)
控制器设计 控制器是模型和视图联系的纽带,同时也是系统的控制中心。根据控制器在系统中的不同作用,将控制器抽象成四种控制器类型,即主控制器(MainController)、请求映射控制器(RequestMappingConntroller)、视图选择控制器(ViewController)和模型控制器(ModelController)。其中,主控制器在系统服务器最前端,用于从xml配置文件中获取HTTP请求映射表,接收客户的HTTP请求并且将该请求传送给请求映射控制器和视图选择控制器;请求映射控制器将传送来的HTTP请求映射到相应的处理器(处理器采用JavaBean形式)进行处理,其映射表直接从Servlet Context的变量中获取(因为该映射表已经由主控制器装入),处理完后返回结果;视图控制器根据HTTP请求映射表及处理器处理后的结果进行下一个视图的选择;模型控制器负责客户会话数据的处理:每一个客户对应一个会话模型,用来描述客户的各种状态等。下图(图1。3)是各控制器的类图,其中类XmlParser用来处理xml配置文件。
(图1。3)
视图设计 视图是与客户交互的窗口。这里,视图就是指HTML页面。为了使页面具有风格统一、整齐、可配置等特点,将采用模板(Template)技术。HTML页面模板一般由以下几部分组成:标题、横幅、主体和页脚(读者可以根据应用的需要而扩展)。所以该模板的Jsp代码如下(程序清单1。1):
<%@ taglib uri="/WEB-INF/tlds/taglib.tld" prefix="stu" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GB2312"> <link rel="stylesheet" href="/doc/style/global.css"> <title> <!—插入标题--> <stu:insert parameter="HtmlTitle"/> </title> </head> <body bgcolor="white"> <!—插入横幅--> <stu:insert parameter="HtmlBanner" /> <table height="85%" width="100%" cellspacing="0" border="0"> <tr><td> <!—插入主体--> <stu:insert parameter="HtmlBody" /> </td></tr> <tr><td> <!—插入页脚--> <stu:insert parameter="HtmlFooter" /> </td></tr> </table> </body>
</html>
程序清单1。1
从上面的代码可以看出该Jsp页面中使用了自定义标签库(有兴趣的读者可以参照我的一篇文章《用Tag Extension API实现业务逻辑与显示的分离》),用<stu:insert/>标签来动态的插入模板的各个组成部分。该标签定义在InsertTag.java文件中,而taglib.tld文件将在下文结合具体实例给出。运用模板技术,我们就可以用xml文件来定义各种视图以适用于不同的应用,而只需将参数值更改。视图控制器从视图定义xml配置文件中读取配置信息,即各种视图的模板参数,然后由该控制器选定要显示的视图返回给客户。因为视图的定义是与具体的应用相关的,下文将给出一个实例来加以说明。
模型设计 模型是系统的数据核心,是用以描述该系统状态的各种实体的总和。因为这里主要讨论的是Web的应用开发,所以要对MVC中的模型稍加修改。我们知道HTTP为了设计成一种简单性的协议,它是无状态的。这就是说,我们并不了解在线浏览的客户的各种状态信息。所以,如何及时的获取在线用户的信息成了模型设计首要考虑的问题。因为在线客户的信息是网站提供服务的依据,例如已登录用户的权限应该比未登录用户的权限更大,根据用户的历史记录来显示不同的视图等。所以,模型设计一个重要方面是与客户的在线状态相关的。Web 应用开发与其它浏览器/服务器应用开发比较,在线状态的维护是一大不同点,这是由Web应用协议的特点所决定的。而模型的其它方面是与具体应用紧密联系的,所以这里不作讨论。完成对模型的抽象之后,我们可以将模型交给模型控制器来完成各种业务逻辑。
应用举例 下面就以“学生成绩查询系统”为例来说明怎样用这样的设计思想来设计实现一个Web应用。
该查询系统实现了身份验证,学生成绩的查询,密码修改等几个简单的功能,但需要提醒读者不注意,请不要将重点放在这几个功能的实现上,而是要弄清楚这样一种设计思想和如何运用这种思想来设计。本系统的实现所运用的技术有Jsp、Servlet、JAXP(Java API for XML Processing)等,一些细节内容将不作介绍,读者可以参考其它资料。
首先来分析控制器的实现。控制器包括MainController、RequestMappingCtrl、ViewController和ModelController四个类。它们的类图见图1。3。主控制器通过类XmlParser从Xml配置文件 requestmappings.xml装入HTTP请求映射信息,并且将该映射信息封装在类URLMapping中,然后用一个哈希表存储在Servlet Context的一个变量中供所有的控制器访问;主控制器还通过其它控制器对当前客户请求的处理结果,来重新导向视图。鉴于主控制器的中心控制作用,列出MainController类的代码如下(程序清单2。2):
public class MainController extends HttpServlet { private HashMap urlMappings;//用来存储HTTP请求映射表 public void init () { String requestMappingsURL = null; try { //获取请求映射表的绝对URL地址 requestMappingsURL= getServletContext().getResource("/WEB-INF/xml/requestmappings.xml").toString(); } catch (java.net.MalformedURLException ex) { Debug.println("MainController: MainController malformed URL exception: "+ ex); } urlMappings = XmlParser.loadRequestMappings(requestMappingsURL);//从配置文件中读取HTTP请求映射表 getServletContext().setAttribute(WebUtility.URLMappingsKey, urlMappings);//并存在Servlet Context中 getViewController();//初始化视图控制器 getRequestMappingCtrl();//和请求映射控制器 } public void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } public void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String selectedURL = request.getPathInfo();//获取HTTP请求字符窜 HttpSession session = request.getSession(); ViewController viewController = null; ModelController modelController = //获取模型控制器,如果没有创建则实例化一个 (ModelController)request.getSession().getAttribute(WebUtility.ModelManagerKey); if (modelController == null) { try { modelController = (ModelController)Beans.instantiate(this.getClass().getClassLoader(), "student.ModelController"); } catch (Exception exc) { throw new ServletException("ModelController Initializing failed!"); } session.setAttribute(WebUtility.ModelManagerKey, modelController);//将该控制器存放在客户会话中 modelController.init(getServletContext(), session); } URLMapping current = getURLMapping(selectedURL);//获取当前请求映射参数,封装在类URLMapping中 if ((current != null) && current.requiresSignin()) {//判断是否要求登录 if (modelController.getStudent().isLogined()) { doProcess(request); } else {//如果没有登录则把下一个视图设置为登录页面 String signinScreen = getViewController().getSigninScreen(); session.setAttribute(WebUtility.CurrentScreen, signinScreen); session.setAttribute(WebUtility.SigninTargetURL, selectedURL); } } else { doProcess(request); } getServletConfig().getServletContext().getRequestDispatcher( getViewController().getTemplate()).forward(request, response); } private void doProcess (HttpServletRequest request) throws ServletException { try { getRequestMappingCtrl().processRequest(request); //首先交给请求映射控制器处理,然后 getViewController().getNextScreen(request); //由视图控制器处理,因为后者处理依赖前者结果 } catch (Throwable ex) { Debug.println(ex.getMessage()); } } private RequestMappingCtrl getRequestMappingCtrl () {//实例化请求映射控制器 RequestMappingCtrl rp = (RequestMappingCtrl)getServletContext().getAttribute(WebUtility.RequestMappingCtrlKey); if (rp == null) { Debug.println("MainController: initializing request processor"); rp = new RequestMappingCtrl(); rp.init(getServletContext()); getServletContext().setAttribute(WebUtility.RequestMappingCtrlKey, rp); } return rp; } private ViewController getViewController () {//实例化视图控制器 ViewController viewController = (ViewController)getServletContext().getAttribute(WebUtility.ScreenManagerKey); if (viewController == null) { Debug.println("MainController: Loading screen flow definitions"); viewController = new ViewController(); viewController.init(getServletContext()); getServletContext().setAttribute(WebUtility.ScreenManagerKey, viewController); } return viewController; } private URLMapping getURLMapping (String urlPattern) { if ((urlMappings != null) && urlMappings.containsKey(urlPattern)) { return (URLMapping)urlMappings.get(urlPattern); } else { return null; } } }
程序清单2。2
从MainController类的实现我们可以知道程序的控制流程。其中用到的相关类有:类XmlParser(用来读取Xml配置文件)、类URLMapping(封装了请求映射信息)、类WebUtility(值对象:Value Object)等。本文重点放在设计模式和方法,其功能的实现读者可以自己参考相关资料。
我们可以通过某学生的一次查询来看一下该系统的流程。学生在没登录之前应该是不能进行查询的,但可以适当给他一些浏览主页和其他信息的权限。如果这时学生发送查询请求,主控制器获取当前请求为“查询”请求,并且从请求映射表中查出该请求是要求客户登录的。但此时客户尚未登录,所以主控制器会将下一个视图设置为“登录”视图,把学生导向到登录页面;如果学生已经登录,则该请求先交给请求映射控制器处理,然后再交给视图控制器选定下一个视图(如果查询成功的话,这个视图应该是“查询结果”视图),最后由主控制器导向到下一个视图。
弄清楚了该系统的流程,我们再来看看其它几个控制器的实现。从图1。3可以看出,请求映射控制器类查询请求映射表确定处理该请求的处理器,然后由处理器处理并返回结果;而视图控制器类从配置文件中读取(通过类XmlParser)视图定义信息并封装在类View中,根据请求映射表和请求控制器处理的结果,视图控制器确定下一个视图的选择。请求映射控制器和视图控制器都是Servlet Context中的变量(即它们与客户的关系是一对多的关系)。模型控制器类这里主要实现对客户在线状态模型的控制,并且是一个客户对应一个模型控制器(即一对一的关系)。具体实现请参照所附源代码。
其次是模型和视图的设计。为了简单起见,该系统只抽象了客户在线状态这一模型。类StudentModel即是该模型的实现。该模型的三个私有属性分别用来表示学生的学号、密码及登录状态等信息,还包括了一些访问这些属性的方法;而对视图的设计可以看看视图定义Xml配置文件ViewDefinitions.xml。该文件描述了定义各种类型视图的参数,将其提供给视图控制器。主控制器获取视图控制器选择的当前视图参数,交给模板。这时模板所显示的即为当前要显示的视图。需要说明的是,对模型和视图的设计是与应用密切相关的。这里只举了很简单的模型和视图作为对该系统设计的补充说明。读者完全可以根据自己的应用需要进行定制。
总结 按照这样一种思想进行设计的好处是:第一,基本上实现了业务逻辑和显示的分离。运用“分治”的思想,程序员可以把精力放在对业务逻辑的处理上;第二,对系统的维护和扩展容易。例如添加一项发送Email的功能:首先网页设计员将模板的各个组成页面设计好,并且修改配置文件(添加新视图);程序员完成发Email的功能Bean,并修改配置文件(添加请求映射)。即完成了该功能的扩展。很明显,这样的设计方法更有利于对系统的维护和功能扩展。上面的实例中并没有详细分析完成各种业务逻辑的处理器,例如查询成绩的处理器,是为了把注意力集中在系统的设计模式上。
参考文献
《JavaTM 2 Platform, Standard Edition, v 1.4.0 API Specification》 《JavaTM Pet Store 1.1.2》源代码
关于作者 杨健,中南工业大学计算机科学与技术专业硕士研究生,参与过大型MIS系统开发,网站建设等,现在正致力于Java技术的研究。Email:ytjcopy@263.net。
|