观点:在WEB应用中使用MVC模式
如今的商业世界里,厂商们在他们的服务和生产线方面做了很大的努力。陈列展示和客户交互变成了以Internet为中心的一种模式。公司们正在逐步建立Web展示平台以吸引更多新客户,并使之多元化。且简化了和现有客户群体的交互方式,也使得基于B2B的通讯更加便利,使得在老的服务上推新更加容易了。现在,实际上每个银行都提供了在线储蓄的方式;每个财政机构和经纪行都重新编写了自己的贸易系统来允许全球的访问,能够和任何已连上Internet的地方进行交易。在航空,国防,制药,资料管理和其他工业领域,都已经将Web接口加入到它们的商业模式中。
随着基于Web的服务和应用迅速地壮大,Web应用开发领域有了巨大的进步和发展。现有的编程方法学,设计模式,代码库都已经被重新应用(或者重写),使得它们都和基于Web的应用相关联。甚至还创建了整个框架(frameworks)来减少开发时间,缩短维护周期,简化在线应用程序的代码。因为Model-View-Controller (MVC)设计范例的主要目的是分离业务逻辑层和表示逻辑层,因为它在整合各种Web程序方面显得游刃有余,所以在基于Web的应用和服务方面,它是首选。Apache的Struts实际上是JAVA中的一种MVC实现框架。FuseBox是另一个MVC的实现,它是在多个Web技术的技术上实现的,如ColdFusion, PHP, Java, ASP和Lasso。
在这篇文章中,我会告诉你如何在基于Web的项目中使用MVC。我的控制器(controller)将依赖反射API(Reflection API)来动态调用行为方法(action methods),并重定向到适当的视图(View), 调整表示逻辑和模型(Model)之间的数据流。
因为是一个Web项目,所以我会使用Servlets做服务端处理和控制器(controller)实现,JavaBean做模型(Model)层,JSP作为表示(presentation)层。如果你不熟悉Servlets,JSP和J2EE组件开发,请先阅读本文末尾的参考教程。
如果要测试我的项目或者想在一个大的程序中将它作为核心程序,你需要配置支持J2EE的应用服务器(application server)。这里我使用免费的Tomcat,但是在一个企业环境中,你可能会使用IBM WebSphere 或者BEA WebLogic作为商业的J2EE应用服务和EJB/JSP容器。应用服务器的安装超过了本文的范围,但是项目的源程序是用XML配置描叙文件包裹为J2EE应用程序WAR(Web压缩)文件的;因此,要在一个已配置的服务器上运行它,你所需要做的是将它drop到一个适当的位置。而且,如果你想投入更多点的时间,不编写自己的控制器层(Controller layer),那么你可能还需要考虑一下Apache Struts框架,该框架伴随有一个巨大的JSP标签库,可以提供你需要的各种类型的功能。
项目
要演示如何在Web应用中使用MVC模式,我创建了一个简单的项目,该项目包含一些JSP视图(JSP Views)----在任何一个浏览器中可视----一些助手bean,行为类(action classes),一个Servlet controller类。项目的目的是要基于用户的邮政编码或者城市名来显示天气信息。这个项目的架构是要使任何类型的大型在线应用程序更容易和更方便修改。企业级的应用程序在后台通常有个数据库,在视图中取出动态数据,即所谓的三层架构所组成----客户应用程序,服务器处理,企业数据服务。我这次是要将所有的数据信息都存储到一个HashMap对象中。
架构图
主要的控制器
任何MVC设计的主要控制器是负责协调模型和视图层之间的数据流,响应用户的请求和用行为管理模型数据。这个是Model-View-Controller在Web应用中合适的原因。如果控制器被完好的编写,它就会引导请求数据,针对各种数量和类型的视图调用行为。因此,你能很容易的应用同一个控制器到任何项目中,然后添加你需要的视图就可以了。
我把我的控制器Servlet叫“MainServlet”,并在一个XML配置描叙文件添加了它的定义,该描叙文件在我的应用服务器一个称为WeatherAppWeb的新的Web应用(webapp)下。
<web-app id="WebApp">
<display-name>WeatherAppWeb</display-name>
<servlet>
<servlet-name>MainServlet</servlet-name>
<display-name>MainServlet</display-name>
<servlet-class>sys.MainServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MainServlet</servlet-name>
<url-pattern>/MainServlet</url-pattern>
</servlet-mapping>
...
Servlet有标准的doGet和doPost方法。但是,它也有HashMap,用来动态存储行为对象,该对象是从JSP视图传递的键值所创建的。
我没有用一个属性文件来匹配执行行为的键值或者用来显示的JSP视图,取而代之,我将行为键作为隐藏变量(hidden varivables)放入到JSP本身中,来指出控制器接下来会做什么。
当JSP提交自己的数据到控制器时,主要的“魔术”发生在doPost方法中。我使用反射API来实例化Action类,(首先我检查它是否已存在)。然后通过一个参数键值调用一个方法。Actio n类被控制器Controller使用来执行各种类型的行为。所有的行为类都实现一个空的ActionIn terface,以致能够使用反射(Reflection),并通过JSP表单传回的参数键值来实例化它们。 Action调用返回后, 我重定位到另一个视图----这个取决与在finally从句中传递过来的第二个键参数。
JSP视图
该视图是一个用户可以在任何Web浏览器中看到的简单HTML页面。它们是由JSP应用服务器生成的。Post视图有一个HTML表单,其中有个提交行为指向控制器Servlet。
<FORM action="../MainServlet" method="post">
两个关键参数指出控制器指出控制器做什么和重定向到哪里。
<input type="hidden" name="ACTIONKEY" value="WeatherAction.viewByZip">
<input type="hidden" name="REDIRECTKEY" value="weather_data">
在本例中,一个JSP视图取得邮政编码(ZIP code)并将它提交到控制器,使用第一个键值WeatherAction来实例化Action类,并通过传递到一个请求对象来调用viewByZip方法。从请求的对象中,我得到了所有的参数并在Data Store WeatherData对象中使用邮政编码方式进行查找。结果bean的引用被放回到请求的对象。(看 Listing 1)方法完成后,控制器Controller Servlet通过第二个键值重定向到“weather_data.jsp”。(看Listing 2)
正如你能够从上面的架构图表中看到的,我有两个JSP视图(一个是取Zip code,一个取City字符串),它们都有第二个键值用来指向第三个JSP页面(weather_data),我们可以看到检索结果。通过遵守该模式的规则,你能够使用适当的键值添加任何数量的视图和模型,控制器将不费力气的协调它们之间的数据流,而不用知道有关视图或响应模型的具体细节。
最后一个关于JSP视图的是,我使用服务器端的JSP标签库(Tag Library)来验证ZIP和City字段的合法性。代码如下:
<%@ taglib uri="/WEB-INF/validtag.tld" prefix="valTags" %>
如果你不熟悉JSP标签,不用担心。我会在我的下一篇文章“在MVC视图中使用JSP标签库验证用户输入”中详细描叙如何使用它们进行数据验证的。
Java Bean 模型层
在我的Web MVC项目中,model层包含容纳数据的助手beans。它们仅有实例字段,getter和方法。它们位于“beans”包中,在Action类下面。当控制器执行一个行为行为方法时,行为可能放住一个bean或者做一些其他的操作。在一个企业项目里,行为可能做更多的事情:连接到RDBMS,mainframe,或者其他数据源获得数据。发送数据到数据源,更新数据,做一些安全性检查和一些其他任务。行为实际上是控制器助手类,但是它们也属于模型层,因为它们控制模型数据状态和响应用户的请求。
结论
正如你看到的, MVC 确实很适合Web应用的开发。一旦控制器被做好,程序员们可以创建很多视图针对用户的情况和一些特殊的需求来响应模型(action+beans)。 因为控制器可以在新的应用中简单的重新使用,所以开发时间缩短了很多,使得能够花更多时间编写业务逻辑和模型表示的功能。视图能够作为原型(prototype)被创建,整个创建过程先用HTML完成,再转化到JSP或者被另一种技术代码所代替。关于这篇文章的任何可以e-mail我:vlad_kofman@yahoo.com
Code Listings
Listing 1
public void doPost(HttpServletRequest req,HttpServletResponse res)
throws ServletException, IOException{
res.setContentType("text/html");
String action = (String) req.getParameter("ACTIONKEY");
String redirect = (String) req.getParameter("REDIRECTKEY");
ActionInterface actionInterface = null;
HttpSession session = req.getSession();
try {
String classNameStr = action.substring(0, action.indexOf("."));
String methodName = action.substring(action.indexOf(".") + 1);
// if action exists - get it,
actionInterface = (ActionInterface) actions.get(classNameStr);
// if null - instantiate it
if
(actionInterface == null) {
className = Class.forName("actions." + classNameStr);
actionInterface = (ActionInterface) className.newInstance();
actions.put(classNameStr, actionInterface);
}
method = actionInterface.getClass().getMethod(methodName,
new
Class[] { o.getClass(), o.getClass()});
method.invoke(actionInterface, new
Object[] { req, res });
} catch (Exception e) {
session.setAttribute(
"msg",
"Problem in MainController Servlet doPost\n" + e);
System.out.println(
"Problem in MainController Servlet doPost\n" + e);
} finally {
this
.getServletContext()
.getRequestDispatcher("/jsp/" + redirect + ".jsp")
.include(req, res);
}
}
Listing 2
public synchronized void viewByZip(Object reqO, Object resO) {
HttpServletRequest req = (HttpServletRequest) reqO;
HttpServletResponse res = (HttpServletResponse) resO;
String zip = (String) req.getParameter("ZIP");
String data = (String) WeatherData.getData(zip);
if (data == null)
data = "Sorry; no weather data is available for zip:" + zip;
// fill new bean
WeatherBean wb = new WeatherBean();
wb.setZip(zip);
wb.setData(data);
// put bean in to request user's object
req.setAttribute("weather", wb);
}
Listing 3
<BODY>
<% beans.WeatherBean wb =
(beans.WeatherBean)request.getAttribute("weather"); %>
<P>Weather information for <%=wb.getZip() %></P>
<%=wb.getData() %>
<P>
<% if (session.getAttribute("msg") == null)
{ session.setAttribute("msg", "& "); } %>
<%=session.getAttribute("msg") %>
<% session.setAttribute("msg", "& "); %>
</P>
<backTags:BackLink text="<- Back" />
</BODY>
关于作者
Vlad Kofman is a System Architect currently working on projects under government defense contracts. He also has been involved with enterprise-level projects for major Wall Street firms and the U.S. government. His main interests are object-oriented programming methodologies and design patterns.
参考书籍
Java Servlet Technology by Stephanie Bodoff http://java.sun.com/webservices/docs/1.0/tutorial/doc/Servlets.html
Servlets and JavaServer Pages (JSP) 1.0: A Tutorial
http://www.apl.jhu.edu/~hall/java/Servlet-Tutorial/
Sun Reflection API by Dale Green
http://java.sun.com/docs/books/tutorial/reflect/
WebSphere Studio Application Developer Version 5 Programming Guide
By Ueli Wahli, Ian Brown, Fabio Ferraz, Maik Schumacher, and Henrik Sjostrand
IBM Redbook SG24-6957-00, May 9, 2003
Core Java 2, Volume II: Advanced Features (5th Edition)
by Cay Horstmann, Gary Cornell
Publisher: Prentice Hall; 5th edition (December 10, 2001)
Translated by Willpower,2003.12.3
注:本文是著名开发者网站developer.com于2003年12月2日刊登发表的一篇文章。