2.1 MVC框架的王者:Struts 2
Struts 2是当今最吸引眼球,也是使用最广泛的基于Java的MVC框架。该框架比其前身Struts 1.x更强大,也更容易使用。虽然Struts 2是Struts 1.x的升级版,但在技术实现上几乎和Struts 1.x一点关系都没有。实际上,Struts 2是从Webwork发展而来的。Struts 2借用了Struts的知名度和Webwork的优良的设计和理念,成为新的MVC框架的王者。
2.1.1 Struts 2与MVC模式
MVC这个词也许很多读者并不陌生,可能耳朵都听得磨出茧子来了。MVC实际上是三个英文单词的首字母的组合。M表示Model(模型), V表示View(视图), C表示Controller (控制器)。基于MVC模式的应用程序从逻辑上被分为Model-View-Controller三部分。也可以换句话说,所有从逻辑上可以分成Model-View-Controller三部分的应用程序都是基于MVC模式的(包括B/S和C/S结构的应用程序)。
那么MVC的这三个部分到底有什么作用呢?让我们先来回顾一下传统的Java Web程序是如何设计的。在MVC模式受到广泛关注之前,我们可能会这样设计Web应用程序。
在工程中建立很多的JSP页面和Servlet类。Web程序的用户接口主要是JSP页面,那么网页设计人员就用各种工具设计了很漂亮的JSP页面(有可能是HTML页面,然后由开发人员将这些页面转成JSP页面)。下一步,也是最核心的一步,就是为系统添加业务逻辑。开发人员可能会在JSP中添加大量的JSTL标签以及Java代码来处理需要动态生成的页面元素。当JSP页面需要提交请求时,会提交给另一个JSP页面,也可能会提交给Servlet。然后在这些JSP页面或Servlet中处理相应的业务逻辑(包括访问数据库等操作),最后返回给JSP页面,并以用户期望的格式显示处理结果。
从上面的处理过程来看,并没有什么不妥。但如果要发生下面的情况应该怎么办呢?
当JSP页面的显示风格有变化时,可能会修改原来的代码。例如,在JSP页面中原来是以列表的方式显示处理结果的,但现在要以树形结构显示处理结果。在这种情况下,修改JSP页面是必然的,但服务端程序(JSP页面或Servlet)返回的数据格式有可能并不符合JSP页面的要求,或者需要对返回的数据进一步加工才能满足需求。这时如果按传统的系统结构,就需要修改原来的JSP页面或Servlet中的代码。从技术上看,这么做没有任何问题。但这也违背了设计模式的一个重要原则:对修改关闭,对扩展开放。也就是在添加或修改系统的功能时,应尽量采用扩展的方式,而不是修改原来的代码。这样做可以大大避免由于修改原来的代码而引发的连锁反应。
那么如果不修改服务端程序的代码,应该如何做呢?聪明的程序员也许会想到另外一招。就是在服务端再添加一个Servlet(当然也可以使用JSP页面,不过处理客户端请求最好用Servlet),这个Servlet作为JSP页面和处理业务逻辑的Servlet的桥梁。也就是说,JSP页面并不直接将信息提交给处理业务逻辑的Servlet,而是提交给刚才新添加的Servlet。而由这个新添加的Servlet再访问处理业务逻辑的Servlet。当需要对返回数据进行二次加工时,并不需要修改那个处理业务逻辑的Servlet,而只需要修改那个中间的Servlet的代码即可。由于这个Servlet并不处理任何业务逻辑,而只是对返回的数据进行加工,因此,可以完全重写这个Servlet。
对于JSP页面来说,可以只向这个中间的Servlet提交信息。当服务端需要改变处理业务逻辑的模块时,只需要修改这个中间的Servlet即可。而这一步对JSP页面是完全透明的。从这一点可以看出,这个中间的Servlet起到了一个控制的作用。对于客户端来说,可以控制服务端返回的数据。对于服务端来说,可以决定将客户端提交过来的请求交给哪个服务端程序来处理。那么我们就可以将这个中间的Servlet看做是一个Controller(控制器)。这种模式也就是MVC模式。其中M可以看做处理业务逻辑的Servlet, V可以看做是采集用户数据的JSP页面,而C则可以看做是这个中间的Servlet。
在Struts 2中也采用了类似的方式,只是并不是用Servlet来实现的。在Struts 2中采用了过滤器的方式来截获客户端的请求,并根据请求来调用Struts 2中的控制器。Struts 2中的控制器也被称为Action对象。Action对象可以是任何类的对象实例,包括POJO类。而Struts 2 MVC中的M也并不是Servlet,而是一个或多个处理业务逻辑的JavaBean的对象实例。读者在2.1.3节将会看到MVC模式在Struts 2中的真实应用。
2.1.2 Struts 2最新版的下载与安装
在笔者写本书时,Struts 2的最新版本是Struts 2.1.6。读者可以从下面的网址下载Struts 2的最新版本:
在下载完Struts 2的压缩包后,将其解压。在lib目录中包含了当前Struts 2发行版所带的所有jar文件。但基本的Struts 2应用程序只需要下面7个jar文件:
· struts2-core-2.1.6.jar
· xwork-2.1.2.jar
· struts2-convention-plugin-2.1.6.jar
· ognl-2.6.11.jar
· freemarker-2.3.13.jar
· commons-logging-1.0.4.jar
· commons-fileupload-1.2.1.jar
在找到上面7个jar文件后,将这些jar文件复制到WEB-INF\lib目录中,并在WEB-INF\web.xml文件中添加如下的代码:
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAnd ExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping>
2.1.3 通过一个计算加减法的Web程序来体验MVC模式的好处
在这一节我们来做一个计算加减法的Struts 2应用程序。其中加法和减法分别由两个模型(Model)类完成。这两个模型类由Action类来调用。当JSP页面采集了两个操作数后,会将数据提交给Action,然后Action根据不同的需求调用加法或减法模型类来计算业务逻辑,并对处理结果进行二次加工。
1 编写计算加法的模型类。
Addition类负责计算两个操作数的加法,代码如下:
package net.blogjava.nokiaguy.models; public class Addition { public int add(int x, int y) { return x + y;
} }
2 编写计算减法的模型类。
Subtraction类负责计算两个操作数的减法,代码如下:
package net.blogjava.nokiaguy.models; public class Subtraction { public int sub(int x, int y) { return x - y; } }
3 编写CalcAction类。
CalcAction类是一个调用Addition和Subtraction类的Action类,代码如下:
package net.blogjava.nokiaguy.actions; import net.blogjava.nokiaguy.models.Addition; import net.blogjava.nokiaguy.models.Subtraction; public class CalcAction { // 获得客户端提交的两个操作数的值 private int operand1; private int operand2; // 向客户端返回结果Action类加工过的处理结果 private String result; . // 此处省略了属性的getter和setter方法 // 处理业务逻辑的方法 public String execute() { Addition addition = new Addition(); // 调用add方法执行业务逻辑 int value = addition.add(operand1, operand2); // 加工处理结果 result = operand1 +"+" + operand2 + "=" + value; return "success"; } }
从CalcAction类的代码可以看出,在execute方法中只调用了Addition类。如果需要调用Subtraction类,可以修改execute方法,或将该方法名改成execute1,并另外写一个execute方法来调用Subtraction。由于JSP页面只向CalcAction类提交请求,而且只从result属性中获得处理结果。因此,服务端的业务逻辑切换对于客户端(JSP页面)是透明的。
4 配置struts.xml文件。
struts.xml文件是Struts 2中的核心文件。在src目录中建立一个struts.xml文件,并输入如下的内容:
<? xml version="1.0" encoding="UTF-8" ? > <! DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="struts2" namespace="/" extends="struts-default"> <! -- 配置CalcAction类 --> <action name="calc" class="net.blogjava.nokiaguy.actions.CalcAction"> <result name="success">/WEB-INF/calc.jsp </result> </action> <! -- 使用通配符为所有在WEB-INF目录中的JSP页面指定一个对应的Action --> <action name="*_jsp"> <result>/WEB-INF/{1}.jsp </result> </action> </package> </struts>
由于本例中所有的JSP页面都放在了WEB-INF目录中,而且该目录中的资源不允许直接被客户端访问,因此,在这里需要使用通配符为每一个位于WEB-INF目录中的JSP页面指定一个对应的Action。比如在WEB-INF目录中有一个calc.jsp页面,那么可以通过如下的URL来访问这个JSP页面:
http://localhost:8080/sshregister/calc_jsp.action
5 编写calc.jsp页面。
calc.jsp页面有两个功能:采集两个操作数和显示处理结果。在WEB-INF目录中建立一个calc.jsp页面,并输入如下的代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <! -- 引用Struts 2的标签库 --> <%@ taglib prefix="s" uri="/struts-tags"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>计算器</title> </head> <body> <! -- 显示处理结果 --> <s:property value="result"/> <! -- 显示采集两个操作数的表单,并向calc提交请求 --> <s:form action="calc"> <s:textfield label="操作数1" name="operand1" /> <s:textfield label="操作数2" name="operand2" /> <s:submit value="计算"/> </s:form>
</body> </html>
到现在为止,这个例子已经编写完成了,启动Tomcat后,在浏览器地址栏中输入如下的URL:
http://localhost:8080/sshregister/calc_jsp.action在页面中的两个文本框中分别输入43和56,单击【计算】按钮,将会显示如图2.1所示的页面。
图2.1 计算加法
如果将CalcAction类中的execute方法名改成execute1,并加入如下的execute方法:
public String execute() { Subtraction subtraction = new Subtraction(); // 调用sub方法执行业务逻辑 int value = subtraction.sub(operand1, operand2); // 加工处理结果 result = operand1 +"-" + operand2 + "=" + value; return "success"; }
这时再单击图2.1所示页面上的【计算】按钮,则会显示如图2.2所示的页面。
图2.2 计算减法
从上面的测试过程可以看出,JSP页面和模型类的代码都未修改,而只修改的Action类(Controller)就可以改变JSP页面的显示结果,而且更改了服务端的业务逻辑。