人人都玩开心网:Ext JS+Android+SSH整合开发Web与移动SNS
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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的最新版本:

http://struts.apache.org

在下载完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页面的显示结果,而且更改了服务端的业务逻辑。