看MVC模式所蕴含的设计模式~~ 前言 之所以说是再战,是因为在没有学习设计模式之前已经基于MVC体系结构做过一些项目,主要是小项目,当初理解MVC有一些困难。现在已经把Gof所说的相对简单但是最常见的这些设计模式:
- Abstract Factory
- Adapter
- Composite
- Decorator
- Factory Method
- Observer
- Strategy
- Template Method
都详细过了一遍了,而我们都知道MVC是一种比较特殊的模式,MVC并不属于GOF的23个设计模式之列,但是Smalltalk的MVC框架结构在著名的GOF书中作为一个重要的例子被提出来,并给予了很高的评价。一般的来讲,我们认为GOF的23个模式是一些中级的模式,在它下面还可以抽象出一些更为一般的低层的模式,在其上也可以通过组合来得到一些高级的模式,即架构模式。MVC就可以看作是一些模式进行组合之后的结果(实际上,MVC的出现要早于设计模式的提出,因而只是对它在设计模式的基础上进行在分析)。所以说现在有必要站在设计模式的思想上来看MVC模式。<<Java与模式>>在MVC模式与用户输入数据检查这个专题里大量地谈到MVC模式跟设计模式的关系,本文按这个专题的思路以笔记的形式展开。 MVC模式可以分解为以下设计模式 在GOF书的Introduction中,有一小节是“Design Patterns in Smalltalk MVC”即介绍在MVC模式里用到的设计模式。它大概向我们传达了这样的信息:合成模式+策略模式+观察者模式约等于MVC模式(当然MVC模式要多一些东西)。也就是说它在大部分情况下是下面几个模式: 1 观察者模式 类图结构在Gof里的表示如下:
2 合成模式 类图结构在Gof里的表示如下:
3 策略模式 除了这三种主要的设计模式,Gof最后也提到了MVC还使用了其它的设计模式,比如说工厂方法模式用来说明对一个view的默认的控制器,还有装饰模式用来为view增加一个滚动条等。但是在MVC模式里起主要作用的还是前面列出来的三个设计模式。
模式的分类 书中提到由于[GOF95]是论述软件模式的著作的第一本,也是OO设计理论著作中最流行的一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件架构,设计,程序实现的任何种类的模式。另外一种说法是强调要划分三种不同层次的模式:架构模式(Architectual Pattern),设计模式(Design Pattern),成例(Idiom)。成例有时称为代码模式(Codeing Pattern)。 架构模式(Architectual Pattern) 面向对象软件大师Martin Fowler有本书叫做<< Patterns of Enterprise Application Architecture>>中文名叫<<企业应用架构模式>>。这本书就是关于如何将企业应用分层,以及如何组织各层工作的。例如这本书在第二部分深入讲解模式时,在谈到了Web表示层的时候就分析了MVC模式,从这些我们可以知道架构模式提供一些事先定义好的子系统,指定他们的责任,并给出把它们组织在一起的法则和指南,一个架构模式常常可以分解成很多个设计模式的联合使用。 设计模式 设计模式描述在软件设计过程中普遍存在相互通信的组件中重复出现的结构,这种结构解决在一定的背景中的具有一般性的设计问题。 代码模式或成例(Coding Pattern或Idiom) 代码模式(或成例)是较低层次的模式,并与编程语言密切相关。代码模式描述怎样利用一个特定的编程语言的特点来实现一个组件的某些特定的方面或关系。 从上面的叙述中其实我们不难从一个软件系统中找到他们,因为三种不同的模式存在于它们自的抽象层次和具体层次上,所以少了其中任何一种模式,或者有一种设计的不好,都会给整个软件系统带来害处,如可维护性低等缺点。因为架构模式是一个系统的高层次策略,涉及到大尺度的组件以及整体性质和力学,它设计的好坏可以影响到总体布局和框架性结构。设计模式是中等尺度的结构策略。这些中等尺度的结构实现了一些大尺度组件的行为和它们之间的关系,所以设计模式的好坏不会影响到系统的总休布局和总体框架,但是它会影响到系统的扩展性,维护性等方面。代码模式是特定的范例和与特定编程语言有关的编程技巧。它不会影响到一个部件或子系统的中等尺度的结构,更不会影响到系统的总体布局和大尺度框架,但是会影响到一个中等尺度组件的内部,外部的结构或行为的底层细节。 谈MVC架构模式 MVC中的三个角色
Model(模型端) Mod封装的是数据源和所有基于对这些数据的操作。在一个组件中,Model往往表示组件的状态和操作这些状态的方法,往往是一系列的公开方法。通过这些公开方法,便可以取得模型端的所有功能。 在这些公开方法中,有些是取值方法,让系统其他部分可以得到模型端的内部状态参数,其他的改值方法则允许外部修改模型端的内部状态。模型端还必须有方法登记视图,以便在模型端的内部状态发生变化时,可以通知视图端。我们可以自己定义一个Subject接口来提供登记和通知视图所需的接口或者继承Java.util.Observable类,让父类完成这件事。 多个View(视图端) View封装的是对数据源Model的一种显示。一个模型可以由多个视图,并且可以在需要的时候动态地登记上所需的视图。而一个视图理论上也可以同不同的模型关联起来。 在前言里提到了,MVC模式用到了合成模式,这是因为在视图端里,视图可以嵌套,比如说在一个JFrame组件里面,可以有菜单组件,很多按钮组件等。 多个Controller(控制器端) 封装的是外界作用于模型的操作。通常,这些操作会转发到模型上,并调用模型中相应的一个或者多个方法(这个方法就是前面在介绍模型的时候说的改值方法)。一般Controller在Model和View之间起到了沟通的作用,处理用户在View上的输入,并转发给Model来更改其状态值。这样Model和View两者之间可以做到松散耦合,甚至可以彼此不知道对方,而由Controller连接起这两个部分。也在前言里提到,MVC用到了策略模式,这是因为View用一个特定的Controller的实例来实现一个特定的响应策略,更换不同的Controller,可以改变View对用户输入的响应。 MVC模式用在基于C/S的应用 尽管MVC设计模式通常是用来设计整个用户界面(GUI)的,JFC的设计者们却独创性的把这种设计模式用来设计Swing中的单个的组件(Component),例如表格Jtable等。下面的讨论也就是对于Swing单个组件所体现出来的这种MVC模式来说的。 严格地讲,Swing中的MVC实际上是MVC的一个变体:M-VC。 拿JTable来说,MVC 就是 TableModel JTable TableUI. Swing中只显示的定义了Model接口(TableModel),而在一个UI对象中集成了视图和控制器的部分机制(JTable)。View和Control比较松散的交叉组合在一起,而更多的控制逻辑是在事件监听者部分引入的为了更好地展现Swing是一个设计优秀的Java包,充满了大师的智慧,让我们更加深入的进行分析,我将采用最常见的组件button来说明。 注:以下例子是摘自网上一篇名为“通过Java Swing看透MVC设计模式”的佚名文章,感觉对从Swing库的设计来看MVC模式的应用有相当大的帮助,故插到此处同大家分享,黑色字迹部分为自己的说明。 Model的设计 一个按钮的model所应该具备的行为由一个接口ButtonModel来完成。一个按钮model实例封装了其内部的状态,并且定义了按钮的行为。它的所有方法可以分为四类: 1、查询内部状态 2、操作内部状态 3、添加和删除事件监听器 4、发生事件 程序员通常并不会直接和model以及view/controller打交道,他们通常隐藏于那些继承自java.awt.Component的组件里面了,这些组件就像胶水一样把MVC三者合三为一。也正是由于这些继承的组件对象,一个程序员可以很方便的混合使用Swing组件和AWT组件,然后,我们知道,Swing组件有很多都是直接继承自相应的AWT组件,它能提供比AWT组件更加方便易用的功能,所以通常情况下,我们没有必要混合使用两者。 一个实例 现在我们已经明白了Java类与MVC各个部分的对应关系,我们可以更加深入一点去分析问题了。下面将要讲述一个小型的使用MVC模式开发的例子。因为JFC十分的复杂,我只能把我的例子局限于一个用户界面组件里面(如果你猜是一个按钮的例子,那么你对了!) 让我们来看看这个例子的所有部分吧。 Button组件 最显而易见的开始的地方就是代表了按钮组件本身的代码,因为这个类是大部分程序员会接触的。 就像我前面提到的,按钮用户界面组件类实际上就是model和view/controller的之间的黏合剂。每个按钮组件都和一个model以及一个controller关联,model定义了按钮的行为,而view/controller定义了按钮的表现。而应用程序可以在任何事件改变这些关联。让我们看看得以实现此功能的代码。 注:以下代码是我从JDK1.4源代码AbstractButton类中提取出来的,因为查看Jbutton源代码(再次提供JDK1.4在线源代码地址)我们可以发现,它是继承自AbstractButton类的,所以下面的代码是来自AbstractButton的,跟原文有点不一样: 1. /** 2. 3. * Sets the model that this button represents. 4. 5. * @param m the new <code>ButtonModel</code> 6. 7. * @see #getModel 8. 9. * @beaninfo 10. 11. * bound: true 12. 13. *description: Model that the Button uses. 14. 15. */ 16. 17. public void setModel(ButtonModel newModel) { 18. 19. 20. 21. ButtonModel oldModel = getModel(); 22. 23. 24. 25. if (oldModel != null) { 26. 27. oldModel.removeChangeListener(changeListener); 28. 29. oldModel.removeActionListener(actionListener); 30. 31. changeListener = null; 32. 33. actionListener = null; 34. 35. } 36. 37. 38. 39. model = newModel; 40. 41. 42. 43. if (newModel != null) { 44. 45. changeListener = createChangeListener(); 46. 47. actionListener = createActionListener(); 48. 49. itemListener = createItemListener(); 50. 51. newModel.addChangeListener(changeListener); 52. 53. newModel.addActionListener(actionListener); 54. 55. newModel.addItemListener(itemListener); 56. 57. mnemonic = newModel.getMnemonic(); 58. 59. } else { 60. 61. mnemonic = '\0'; 62. 63. } 64. 65. 66. 67. updateDisplayedMnemonicIndex(getText(), mnemonic); 68. 69. 70. 71. firePropertyChange(MODEL_CHANGED_PROPERTY, oldModel, newModel); 72. 73. if (newModel != oldModel) { 74. 75. revalidate(); 76. 77. repaint(); 78. 79. } 80. 81. } 注:你可以多花一些时间按我上面提供的链接地址来仔细阅读一下Button类及其它相关类的源代码。 ButtonModel类 ButtonModel维护着三种类型的状态信息:是否被按下(pressed),是否“武装上了”(armed),是否被选择(selected)。它们都是boolean类型的值。 一个按钮被按下(pressed)是指当鼠标在按钮上面的时候,按下鼠标但是还没有松开鼠标按钮的状态,即使用户此时把鼠标拖拽到按钮的外面也没有改变这种状态。 一个按钮是否“武装了”(armed)是指按钮被按下,并且鼠标还在按钮的上面。 一些按钮还可能被选择(selected),这种状态通过重复的点击按钮取得true或者false的值。 注:查看ButtonModel源代码我们可以看到它是一个接口,定义了一组方法: public interface ButtonModel extends ItemSelectable { boolean isArmed(); boolean isPressed(); … boolean isRollover(); void addActionListener(ActionListener l); void removeActionListener(ActionListener l); void addItemListener(ItemListener l); … void removeChangeListener(ChangeListener l); 其实看到这里,如果你再回头看下这个例子刚开始时“Model的设计”以及前面的内容,就会明白什么是“Mod封装的是数据源和所有基于对这些数据的操作。在一个组件中,Model往往表示组件的状态和操作这些状态的方法,往往是一系列的公开方法。通过这些公开方法,便可以取得模型端的所有功能” 我们再来看看AbstractButton的源代码: 1.public abstract class implements ItemSelectable, SwingConstants { AbstractButton extends JComponent 2. protected ButtonModel model= null; 3. protected ChangeListener changeListener = null; 4. protected ActionListener actionListener = null; 5. protected ItemListener itemListener = null; 6. protected transient ChangeEvent changeEvent; 7. public boolean isSelected() { 8. return model.isSelected(); 9. } 10.public void doClick() { 11.doClick(68); 12. } 13.public void doClick(int pressTime) { 14. Dimension size = getSize(); 15.model.setArmed(true); 16. model.setPressed(true); 17. paintImmediately(new Rectangle(0,0, size.width, size.height)); 18.try { 19.Thread.currentThread().sleep(pressTime); 20. } catch(InterruptedException ie) { 21.} 22.model.setPressed(false); 23.model.setArmed(false); 24.} 25.//其它代码 26.} 上面的代码setArmed等方法是由DefaultButtonModel定义的,它是ButtonModel接口的一个默认实现。我们如果继承了DefaultButtonModel,这样可以覆盖缺省的状态定义,实现有个性的按钮。ButtonUI类 按钮的view/controller是负责构建表示层的。缺省情况下它仅仅是用背景色画一个矩形而已,他们的子类继承了他们并且覆盖了绘制的方法,使得按钮可以有许多不同的表现,例如MOTIF,Windows 95,Java样式等等。 注:在javax.swing.plaf这个包里我们可以找到ButtonUI,这是一个抽象类,类内部是空的,而在javax.swing.plaf.basic这个包里我们可以找到BasicButtonUI,它继承自ButtonUI,其实我们观察下包结构会发现有javax.swing.plaf.metal,javax.swing.plaf.multi等,这几个就是来控制Button在不同系统间的不同的显示效果。: public void update(Button button, Graphics graphics) { } public void paint(Button button, Graphics graphics) { Dimension dimension = button.getSize(); Color color = button.getBackground(); graphics.setColor(color); graphics.fillRect(0, 0, dimension.width, dimension.height); } ButtonUI类并不自己处理AWT事件,他们会使用一个定制的事件监听器把低级的AWT事件翻译为高级的Button模型期望的语义事件。下面就是安装/卸载事件监听器的代码。 private static ButtonUIListener buttonuilistener = null; public void uninstallUI(Button button) { button.removeMouseListener(buttonuilistener); button.removeMouseMotionListener(buttonuilistener); button.removeChangeListener(buttonuilistener); } View/Controller实际上就是一些方法。他们不维护任何自己的状态信息。因此,许多按钮的实例可以共享一个ButtonUI实例。ButtonUI是通过在方便的参数列表里面加上按钮的引用来区分各个不同的按钮。 ButtonUIListener类 ButtonUIListener类可以帮助Button类去转变鼠标或者键盘的输入为对按钮模型的操作。这个监听器类实现了: MouseListener,MouseMotionListener,ChangeListener接口,并且处理一下事件: public void mouseDragged(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource(); ButtonModel buttonmodel = button.getModel();
if (buttonmodel.isPressed()) { if (button.getUI().contains(button, mouseevent.getPoint())) { buttonmodel.setArmed(true); } else { buttonmodel.setArmed(false); } } } public void mousePressed(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource(); ButtonModel buttonmodel = button.getModel(); buttonmodel.setPressed(true); buttonmodel.setArmed(true); }
public void mouseReleased(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource(); ButtonModel buttonmodel = button.getModel(); buttonmodel.setPressed(false); buttonmodel.setArmed(false); }
public void stateChanged(ChangeEvent changeevent) { Button button = (Button)changeevent.getSource(); button.repaint(); } 通过这些相信大家能够感受到Swing的设计是MVC的典范,这是一点都不假的。下一节将要看看在基于B/S的应用中MVC模式是如何被应用的。 看MVC模式在J2EE技术中的应用~~ MVC模式在J2EE技术中的应用 MVC模式并不能自动保证一个结构设计是正确的,如何在一个系统的设计中正确地使用MVC架构模式与系统使用的技术有密切的关系。2EE中有几个核心的技术:JSP,JavaBean,Servlet,EJB,SessionBean,EntityBean构成了J2EE构架的基石,但是对于一个基于J2EE技术的Web应用来说,如何正确地使用MVC模式不是一句话就能得出结论的。 一般而言,一个J2EE系统应当适当地划分接收请求,根据请求采取行动,并将结果显示给用户等责任。因此也就有了开发Web应用时经常提到的一个概念Model 1/Model 2,它是对采用JSP技术构成Web应用的不同模型的描述。 Model 1架构 <<Java与模式>>提到模型一又称做以JSP为中心(JSP Centric)的设计模型,它的架构图如下图所示:
从上图可以看出来,JSP是整个应用系统的门户。它身兼三职啊。1 负责与客户端的所有通信 2处理所有的请求 3处理所有的答复。在处理答复的时候,从数据库中存取数据有两种方式,可以它自己直接去存取,也可以让一些JavaBean来完成。因为JavaBean可以被放在一个请求上下文或者用户会话中,这样就可以在不同的JSP之间通信。当然这种模式在进行快速和需求不是很复杂,规模较小的Web应用是有很大的优势的,比如说:JSP页面可以非常容易地结合业务逻辑(jsp:useBean)、服务端处理过程(jsp:scriplet)和HTML(<html>),在JSP页面中同时实现显示,业务逻辑和流程控制,从而可以快速地完成应用开发。 但是从工程化的角度考虑,它也有一些不足之处:把表现层和业务逻辑层柔和在一起,不利于以后的维护工作以及开发角色的分配,所以这种模式只能适合于小的系统开发。 Model 2架构 Servlet/JSP 规范的0.92版描述了在一个应用中使用servlet 和 JSP 的架构。在其后的规范中,Model 2 这个叫法消失了,但它已经在Java web 开发人员中非常通用了。 <<Java与模式>>提到模型二又称做以Servlet为中心(Servlet Centric)的设计模型, 它的架构图如下图所示:
对比这个图跟Model 1的结构图,从JSP这个角度来看,JSP页面至少少了二个任务即获取跟处理用户的请求,因为Servlet相当于控制器(Controller)角色,它负责接收客户端请求并处理此请求,将它传递给合适的JSP,而JSP则显示给用户。所以JSP页面这时候主要做两件事情:JavaBean直接与数据库打交道取得数据后,JSP从JavaBean中读取数据,这是第一,第二件事情就是把结果返回给客户端。 根据Model 2,servlet 处理数据存取和导航流, JSP处理表现。Model 2 使Java 工程师和HTML设计者分别工作于它们所擅长和负责的部分。Model 2应用的一部分发生改变并不强求其他部分也跟着发生改变。HTML 开发人员可以改变程序的外观和感觉,并不需要改变后端servlet的工作方式。 网上找到了一个很好的例子来说声明:JSP能够生成HTML,WML甚至XML,它对应于Web应用程序中的View部分。EJB作为数据库与应用程序的中介,提供了对数据的封装。一般SessionBean封装的是数据,EntityBean是封装对数据的操作。这两个部分合起来,对应于Web应用程序的Model部分。在技术上,JSP能够直接对EJB进行存取,但这并不是好办法,那样会混淆程序中的显示逻辑和控制逻辑,使得JSP的重用性能降低。这时候有两种解决方法,通过JavaBean或者Servlet作为中介的控制逻辑,对EJB所封装的数据进行存取。这时,JavaBean或者Servlet对应于Web引用程序中的Controller部分。两种类型的Controller各有其优缺点:JSP同Servlet的交互不容易规范化,使得交互的过程变得复杂,但是Servlet可以单独同用户交互,实际上JSP的运行时状态就是Servlet;而由于JavaBean的规范性,JSP同JavaBean的交互很容易,利用JavaBean的get/set方法,JSP不需要过多的语句就可以完成数据的存取,这能够让JSP最大限度的集中在其视图功能上,而且,在桌面应用程序中使用JavaBean也很容易,而用Servlet就相对麻烦许多。根据不同的问题背景,可以选取不同的Controller,有时候也可以两者混合使用,或者直接在Servlet中调用JavaBean。 任何一种解决方案都是双刃剑,在我们获得的同时必须有一定的付出。这种模式也带来了如下一些实际的问题: 1. 必须基于MVC组件的方式重新思考和设计应用结构。原来通过建立一个简单的JSP页面就能实现的应用现在变成了多个步骤的设计和实现过程。 2.所有的页面和组件必须在MVC框架中实现,所以必须进行附加地开发工作。 对于用原生的Servlet API来开发Model 2的程序的例子我将转载JavaResearch论坛上的文章,研究下对MVC模式在J2EE系统中的应用有很大帮助的。 下面以J2EE开发进行介绍。 Model层实现系统中的业务逻辑,通常可以用JavaBean或EJB来实现。 View层用于与用户的交互,通常用JSP来实现。 Controller层是Model与View之间沟通的桥梁,它可以分派用户的请求并选择恰当的视图以用于显示,同时它也可以解释用户的输入并将它们映射为模型层可执行的操作。 现在来看一个例子,看MVC模式是怎样工作的。 1.1.1 一个实例 例1-a: <servlet> <servlet-name>Controller</servlet-name> <servlet-class>nepalon.simplestruts.Controller</servlet-class> </servlet> <servlet-mapping> <servlet-name>Controller</servlet-name> <url-pattern>/simplestruts/servlet/control/Controller</url-pattern> </servlet-mapping> 上面是web.xml文件的片段,在这里定义了一个servlet用于处理请求。 例1-b(Test.jsp文件): <html> <%@pagecontentType="text/html;charset=gb2312"%> <head> <metahttp-equiv="Content-Type"content="text/html;charset=gb2312"> <title>实例首页</title> </head> <body> <tableborder="0"width="100%"> <tr> <td><divalign="center"> <ahref="/simplestruts/servlet/control/Controller?command=showarticle">显示文章</a> </div></td> </tr> </table> </body> </html> 在这个JSP中,我们并没有直接去调用JSP或JavaBean,而是把请求分送到Servlet中。下面,我们来看看Servlet的代码。 例1-c: packagenepalon.simplestruts; /** *<p>Title:MVCframework</p> *<p>Description:Controller<p> *<p>Copyright:R2003</p> *@authorNepalon *@version1.0 */
importjavax.servlet.*; importjavax.servlet.http.*; importjava.io.*; importjava.util.*;
publicclassControllerextendsHttpServlet {
publicvoidinit(ServletConfigconfig)throwsServletException { super.init(config); }
publicvoiddestroy(){}
/**用于处理HTTP的GET和POST请求的函数 *@paramrequestservletrequest *@paramresponseservletresponse */ protectedvoidprocessRequest(HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,java.io.IOException { //代码(1)通过if来实现对不同请求的分发 if(request.getParameter("command").equals("showarticle")) { ArticleCommandcommand=newArticleCommand(); next=command.getAllArticle(request,response); } //代码(2) dispatch(request,response,next); }
protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException,java.io.IOException { processRequest(request,response); }
protectedvoiddoPost(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException,java.io.IOException { processRequest(request,response); }
/**一个实现了分发者模式的函数 *@paramrequestservletrequest *@paramresponseservletresponse */ protectedvoiddispatch(HttpServletRequestrequest, HttpServletResponseresponse, Stringpage) throwsjavax.servlet.ServletException,java.io.IOException { RequestDispatcherdispatcher= getServletContext().getRequestDispatcher(page); dispatcher.forward(request,response); } } 在Servlet中并没有直接处理所提交的请求,而是把请求的处理推后到ArticleCommand类中,通过ArticleCommand对象来执行,如代码(1)。在处理完请求后,转到相应的页面中,如代码(2)。下面,我们看一下ArticleCommand类的代码。 例1-d: packagenepalon.simplestruts;
/** *<p>Title:MVCframework</p> *<p>Description:文章业务类<p> *<p>Copyright:R2003</p> *@authorNepalon *@version1.0 */
importjava.util.*; importjavax.servlet.*; importjava.io.*; importjava.lang.*; importjava.sql.*; importjavax.sql.*;
publicclassContribute { publicContribute(){} publicStringgetAllArticle(HttpServletRequestrequest,HttpServletResponseresponse) throwsjavax.servlet.ServletException,java.io.IOException { Connectionconn=null; Stringcon_user="example1"; Stringcon_password="example1"; Stringcon_dburl="jdbc:oracle:thin:@localhost:iasdb"; Stringcon_driver="oracle.jdbc.driver.OracleDriver"; PreparedStatementpstmt=null; ResultSetrsComment=null; VectorvectorComment=newVector(); StringselectSQL="SELECTcontent,timeFROMarticleORDERBYtimeDESC"; try { DriverManager.registerDriver(neworacle.jdbc.driver.OracleDriver()); Class.forName(con_driver); conn=DriverManager.getConnection(con_dburl,con_user,con_password); pstmt=conn.prepareStatement(selectSQL); rsComment=pstmt.executeQuery(); while(rsComment.next()) { CommentItemcommentItem=newCommentItem(); commentItem.setContent(rsComment.getString(1)); commentItem.setTime(rsComment.getDate(2)); vectorComment.add(commentItem); } vectorComment.trimToSize(); } catch(Exceptione){//做相应的处理} //代码(1)保存处理结果并返回跳转页面 request.setAttribute("vectorComment",vectorComment); return"/simplestruts/showallarticle.jsp"; } …… publicStringgetNewArticle(HttpServletRequestrequest,HttpServletResponseresponse) throwsjavax.servlet.ServletException,java.io.IOException {…} } 在这个类中进行的是取得所有文章的业务,最后返回如果成功执行操作后要跳转到的页面。当然,这个类中可能还有别的业务的相应函数,读者可自己实现。下面看一下要跳转到的页面的代码。 例1-e(showallarticle.jsp文件): <html> <%@pagecontentType="text/html;charset=gb2312"%> <%@pageimport="java.util.*,java.lang.*"%> <jsp:useBeanid="vectorComment"type="java.util.Vector"scope="request"/> <head> <metahttp-equiv="Content-Type"content="text/html;charset=gb2312"> <title>显示文章</title> </head> <body> <tableborder="0"width="100%">
<tr> <td>发表时间</td> <td>文章内容</td> </tr> <% if(vectorComment!=null&&vectorComment.size()>0) { intcounter=vectorComment.size(); CommentItemcommentlist=null; for(inti=0;i<counter;i++) { commentlist=null; commentlist=(CommentItem)(vectorComment.get(i)); %> <tr> <td><%=commentlist.getCmTime()%></td> <td><%=commentlist.getCmContent()%></td> </tr> <% } } %> </table> </body> </html> 在这个JSP中我们要做的只是取得结果并显示,没有涉及到相应的业务逻辑。1.1.2 实例分析首先,我们看一下这个例子的序列图
图1.1.2-1 1) 首先在Veiw层的test.jsp中提交一个请求/simplestruts/servlet/control/Controller?command=showarticle; 2) 在Controller层的Controller对象中,根据请求的类型来调用相应的业务处理类,在这里,command值为showarticle的请求的业务处理类为ArticleCommand类,所以调用该类的对象的相应函数; 3) 在Model层的ArticleCommand类主要实现请求的取得所有文章的业务功能,把结果保存在request中,并返回跳转页面作为返回值; 4) 回到Controller层的Controller对象,根据上一步骤的返回值进行页面转发。 5) 转发到View层的showallarticle.jsp页面,这个页面从request中取得结果并进行显示。在这个JSP中虽然也有Java代码,但这些代码只是用于显示结果,并没有涉及到任何业务逻辑。 MVC本身就是一个非常复杂的系统,所以采用MVC实现Web应用时,如果采用现成的MVC框架,在此之下进行开发,能够达到事半功倍的效果。 Struts与MVC模式的关系 Model 1跟Model 2 有关Model 1跟Model 2的介绍在之前的<<再战MVC>>的两篇文章中已经介绍了,总的来说就是Servlet可以应付控制流,而JSP则可专注于编写HTML的任务。 控制层使应用层和视图去耦合 认为区别于MVC 的一个原因是,观察者/通知模式不能在web 环境内工作 Model 2 并不合适。HTTP 是一个 “拉” 的协议: 客户请求然后服务器响应。没有请求就没有相应。观察者模式需要一种“推”协议来进行通知,以便服务器能在模型改变时将信息推送到客户端。书上提到,虽然也有一些方法能模拟将数据推送到客户端,但这和基本情况相悖,并且会视为是权宜的修补。 MVC 通常表示为3个互相连接的组件
上图是典型的MVC模式,经常被表示为:一个互相连接的三角形。对于一个基于Web的应用程序而言,是很难维护该三角形的“改变通知(state query/change notification)”这一部分。 这些东西在所有资源都在一台服务器上,而且客户端保持一个开放连接的情况下工作得非常好。如果资源分布在不同的服务器上,并且客户端不能维护一个开放的连接情况下,工作的并不理想。 许多分布式系统架构,包括web应用,在视图进行状态查询的概念时退缩了。针对这种不同,绝大多数情况是入分层的概念来设计的,层内的对象可以和同一层或者相邻层的对象进行通信。在一个复杂应用中,这可以在添加组件时,防止依赖关系呈指数增长。对于分布式应用程序而言,“分层“模式是设计中的核心模式。
正如上图上所示,使用平面设计而不是传统的MVC设计方式。控制层处于表现层(视图)和应用程序逻辑(模式)之间。 从MVC 上下文中,引入层模式将状态改变和状态查询的职责加于控制器之上,并伴随 着改变通知。 每一个模块的主要职责并没有改变。流程有轻微改变,即查询状态和改变通知都必须通过控制器。另一个改变是当视图(表现层)生成动态内容时,它使用由控制器提供的数据而不是直接由模式提供的数据。所以说这样的改变使得视图更加和模式无关了,即由控制器来选择数据并显示数据的视图,这样就从根本上去除了View 和 Model的耦合。 Struts框架与Servlet 我们知道Sun的Java Servlet作为一个基本的平台来为基于Java的Web应用程序提供了很多重要的功能。从Servlet API我们可以发现,Servlet提供了一个基本的接口来处理HTTP的请求和响应,而且我们可以用Session的上下文或其它的上下文(如请求上下文request)来作为跟踪应用程序的用户的重要的手段。其实大部分表现层框架都有一个共性的地方:以Servlet API为基础,封装一些那些经常要处理的而且是令人厌烦的、繁琐的实现细节,比如说:在一个Web应用中,表单数据的接收是一件经常要做的事,因为有大量的来自客户端用户提交的表单,然后针对这些表单,我们总是要详细分析Http请求来取出页面表单的值来。Struts是一个比较好的MVC体系结构的框架,提供了对开发MVC系统的底层支持,采用的主要技术是Servlet,JSP和Custom tag library。 Struts框架的组件结构图
从上面的组件结构图我们可以看出来,Struts的核心ActionServlet是一个MVC风格的控制器,它是连接应用程序的Model和View之间的一座桥梁。 Struts跟Model 2, MVC, 层层模式,在<<UML和模式应用>>第3版(Applying UML and Patterns)这本书讲到逻辑架构时说到了有关层模式的概念,其中有一个准则叫做:模型-视图分离原则。这个原则规定模型(领域)对象不应该直接与视图(UI)对象连接。Struts 通过提供一个控制器tionServlet实现了Sun的 Model 2 架构,这个控制器可以用来管理JSP页面和ActionMapping来保证表现层之外的控制流决策来实现MVC/层模式,通过Struts的配置文件,JSP可以引用一个逻辑目标。控制器组件在运行时提供准确的URI。 供一个控制器Servlet实现了Sun的 Model 2 架构,其他表现设备之间的流程。Struts 通过使用ActionForward MVC/层模式。 下表列出了Struts的核心类,即对应的经典的MVC组件职责。 核心Struts 类和MVC的对应
除了这些核心类,Struts使用一些配置文件和视图助手(view helpers)来沟通控制器和模型。下表列出了Struts 配置文件和描述了他们在架构中的角色。为将Struts配置数据暴露给视图,框架以JSP标签的形式提供了大量的助手类,这些都是struts自定义标签,如下表:
Strtuts配置文件 将以上内容放在一起,下表按层列出了Struts 组件:
Struts 组件,按层索引
在<<UML和模式应用>>这本书里提到了层模式中的组件应该只能和相同层和相邻层的组件交互。因此,Model 组件不能直接和View组件进行交互。我们还可以从上表中看出来,Struts中的大部分的组件其实是属于控制层的组件,因为Struts没有对模型层有严格的规定,我们可以用EJB或者其它的开源框架等。 实践中,控制器与视图的交互通过请求(request),会话(session)以及Servlet平台提供的应用上下文进行(context)。 控制器和模型的交互通过文件和存储系统完成 (比如装入sturts的XML配置文档或者属性文件),或者通过其他服务,如TCP, 创建一个到JDBC数据库的连接。 |