第3章 Struts 2必备开发技能

本章进入Struts 2框架的学习,Struts 2框架是基于MVC模式的开源Java EE框架,而Struts 2框架在SSH结构中或在软件项目中的作用是非常重要的。也就是,开发基于Web的Java EE软件项目时,程序员需要使用Struts 2来处理种类非常多的功能,比如,上传下载、数据验证等,所以本章的内容也较多,但读者应该着重掌握如下内容:

· 自定义MVC框架的使用;

· Struts 2数据验证;

· Strust 2国际化;

· 重定向与转发;

· 多模块处理;

· 紧耦合与松耦合开发。

3.1 使用Struts 2进行登录功能的开发

本章将通过若干个常用案例介绍Struts 2的开发,掌握这些案例是学习Struts 2的必经之路。本节将用一个最经典的登录示例来学习Struts 2框架,在此过程中可以掌握如何用Struts 2框架搭建一个Web开发环境,Struts 2组件间的调用顺序及配置文件struts.xml的设计。

在开发Struts 2框架之前先要从网址http://struts.apache.org/下载相关的jar包,Struts的官方网站截图如图3-1所示。

图3-1 Struts的官网

单击Download按钮开始下载,出现如图3-2所示的界面。

下载的ZIP文件内容包含Struts 2的示例、源代码及开发文档。

3.1.1 为什么要使用MVC

介绍到这里,还有一个疑问,为什么要学习Struts 2呢?其实,解答这样的问题应该先知道什么是MVC模式,因为Struts 2就是基于MVC模式的框架。MVC模式是一种开发方式,它主要的用途就是对组件之间进行隔离、分层。其中M代表业务逻辑层,也就是软件的功能;V代表视图层,也就是用什么组件显示数据,常用的就是HTML和JSP等文件;而C代表控制层,代表软件大方向的执行流程以及用哪个视图对象将数据展示给客户。所以MVC就是将不同功能的组件进行隔离与分层,从而有利于代码的后期维护,使用MVC模式有很多优点。

图3-2 下载Struts 2的最新版

那不使用MVC模式,对代码进行不分层的效果是什么样子的呢?看下面的代码。

        <%@ page language="java" import="java.util.*,java.sql.*"
            pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <%
                    boolean isLoginSuccess = false;//逻辑标记
                    //准备数据库参数
                    String url = "jdbc:sqlserver://localhost:1079;databaseName=y2106db";
                    String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
                    String username = "sa";
                    String password = "";
                    Class.forName(driverName);
                    //获得连接
                    Connection connection = DriverManager.getConnection(url, username,
                                password);
                    String sql = "select * from userinfo where username=? and password=?";
                    PreparedStatement ps = connection.prepareStatement(sql);
                    ps.setString(1, "a");
                    ps.setString(2, "aa");
                    ResultSet rs = ps.executeQuery();
                    //逻辑判断
                    while (rs.next()) {
                        isLoginSuccess = true;
                    }
                    rs.close();
                    ps.close();
                    connection.close();
                    //跳转控制
                    if (isLoginSuccess == true) {
                        response.sendRedirect("listUserinfo");
                    } else {
                        response.sendRedirect("wrong.jsp");
                    }
                %>
            </body>
        </html>

上面的代码是一个JSP文件,里面包含很多种功能的代码。这样的设计虽然在软件开发的初期使项目的开发进度加快,但后期的维护量是非常庞大的,所有的功能代码混杂在一起就像面条一样难以拆分。所以,就要对代码进行分层, M层用JavaBean服务组件,V层用JSP文件只显示数据,C层呢?使用Servlet来进行代替,分层后的servletLogin项目代码结构如图3-3所示。

图3-3 使用Servlet实现MVC模式

这些文件的代码细节如下。

视图V层文件login.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <head>
            </head>
            <body>
                <form action="login" method="post">
                    username:
                    <input type="text" name="username">
                    <br />
                    password:
                    <input type="text" name="password">
                    <br />
                    <input type="submit" value="登录">
                </form>
            </body>
        </html>

此文件主要用来显示登录界面,在此JSP文件中并未出现<%%>标签,也就是,JSP只负责显示数据,而不处理业务逻辑。

视图V层文件ok.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                ok.jsp
            </body>
        </html>

视图V层文件no.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                no.jsp
            </body>
        </html>

上面3个JSP文件都是视图层,而文件ok.jsp和no.jsp负责显示登录成功或失败的提示,也是视图层组件。

业务逻辑层UserinfoService.java的代码如下。

        package service;
        import java.sql.SQLException;
        import dao.UserinfoDao;
        public class UserinfoService {
            public boolean login(String username, String password) throws SQLException,
                    ClassNotFoundException {
                UserinfoDao userinfoDao = new UserinfoDao();
                if (userinfoDao.findUserinfo(username, password) == null) {
                    return false;
                } else {
                    return true;
                }
            }
        }

文件UserinfoService.java中的代码主要负责处理登录,而业务逻辑层需要调用DAO数据访问层UserinfoDao.java类进行数据库的查询操作,它的代码如下。

        package dao;
        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.ResultSet;
        import java.sql.SQLException;
        import dbtools.GetConnection;
        import entity.Userinfo;
        public class UserinfoDao {
            public Userinfo findUserinfo(String username, String password)
                    throws SQLException, ClassNotFoundException {
                Userinfo userinfo = null;
                String sql = "select * from userinfo where username=? and password=?";
                Connection connection = GetConnection.getConnectionFromJDBC();
                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1, username);
                ps.setString(2, password);
                ResultSet rs = ps.executeQuery();
                while (rs.next()) {
                    String iddb = rs.getString("id");
                    String usernamedb = rs.getString("username");
                    String passworddb = rs.getString("password");
                    userinfo = new Userinfo();
                    userinfo.setId(iddb);
                    userinfo.setUsername(usernamedb);
                    userinfo.setPassword(passworddb);
                }
                rs.close();
                ps.close();
                connection.close();
                return userinfo;
            }
        }

在UserinfoDao.java类中将数据表中的记录封装进Userinfo.java类中,该类结构如图3-4所示。

图3-4 实体Userinfo类的结构

在UserinfoDao.java数据访问层中调用GetConnection.java类获取Connection对象,它的代码如下。

        package dbtools;
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;
        public class GetConnection {
            public static Connection getConnectionFromJDBC()
                    throws ClassNotFoundException, SQLException {
                String url = "jdbc:sqlserver://localhost:1079;databaseName=ghydb";
                String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
                String username = "sa";
                String password = "";
                Class.forName(driverName);
                Connection connection = DriverManager.getConnection(url, username,
                        password);
                return connection;
            }
        }

以上是视图层V、业务逻辑层M以及数据访问层DAO的代码,继续查看控制层Controller的代码,它使用的组件是Servlet,核心代码如下。

        public class login extends HttpServlet {
            public void doPost(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                try {
                    String username = request.getParameter("username");
                    String password = request.getParameter("password");
                    UserinfoService usRef = new UserinfoService();
                    if (usRef.login(username, password) == true) {
                        response.sendRedirect("ok.jsp");
                    } else {
                        response.sendRedirect("no.jsp");
                    }
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

此项目运行时的调用顺序如下所示。

(1)在IE中调用login.jsp文件,输入用户名和密码(控制层V)。

(2)单击“提交”按钮。

(3)进入Servlet,取得username和password参数(控制层C)。

(4)在Servlet中调用UserinfoService.java类(业务逻辑层M)。

(5)UserinfoService.java类调用UserinfoDao.java类(DAO数据访问层)进行数据库的查询操作。

(6)UserinfoDao.java类(DAO数据访问层)将查询到的值返回给UserinfoService.java类(业务逻辑层M)进行判断处理。

(7)UserinfoService.java类(业务逻辑层M)再将登录的true或false值返回给Servlet(控制层C),Servlet再对true或false值跳转到不同的JSP文件(视图层),以把登录的结果告诉给客户。

所以通过上面的代码,可以得知,使用Servlet就可以实现MVC设计模式,使组件之间进行分层、隔离,便于后期功能上的维护。但由于使用Servlet开发基于MVC模式的应用程序还是接近于原生的API开发方式,因为大多数的功能还未得到封装,比如,上传、国际化等还需要由程序员自己写代码来处理,所以开发效率上并不算太高,这时就要学习并使用Struts 2框架。

3.1.2 准备JAR文件

Struts 2框架并不是Oracle公司官方发布的框架,它是在WebWork框架和Struts 1框架的基础上综合开发出来的产物,WebWork和Struts 1都是实现MVC模式的Web框架,而Struts 2框架的核心代码主要还来自于WebWork框架,所以使用Strust2就得需要jar包文件,官方的下载网址为:struts.apache.org。

可以用几点来总结Struts。

(1)它是基于MVC模式的Java EE技术的Web开发框架,可以对使用Java EE技术的Web项目开发进行代码的分层,优点是有利于维护。

(2)Struts 2框架的源代码主要来源于WebWork框架,前者是在WebWork框架基础上再与Struts 1的优点进行整合而设计出新的MVC分层框架。

(3)Struts 2的优点主要体现在解耦上,其他的附属技术也比Struts 1有所加强,使用上更加方便快捷,比如简化了配置文件的代码。

(4)在网站http://struts.apache.org/上可以找到其全部相关资料,包括源代码、开发帮助文档、Java API的使用帮助等。

(5)如果以前使用过Struts 1,那么阅读本教程将非常容易。如果没有接触过Struts 1框架或MVC框架,也要至少掌握如何使用Servlet开发基于MVC模式的应用,这样将有益于后面的学习。

在官方网站中下载当前最新版的zip文件struts-2.3.15-all.zip,解压后的内容如图3-5所示。

图3-5 解压内容

其中,apps是Struts 2的demo示例项目,docs是开发文档及使用手册,lib是jar包,src则是Strust 2框架的源代码。

进入lib文件夹看到很多jar包文件,如图3-6所示。

图3-6 Struts 2包中的jar文件

在我们开发登录功能时并不需要这里全部的jar文件,那需要哪几个呢?需要如图3-7所示的jar文件。

图3-7 当前Struts 2版本必备的jar文件

3.1.3 创建Web项目、添加jar文件及配置web.xml文件

创建名为Struts 2Login的Web项目,添加jar文件及配置web.xml文件,代码如下。

        <?xml version="1.0" encoding="UTF-8"?>
        <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
            <filter>
                <filter-name>Struts 2</filter-name>
        <filter-class>org.apache.Struts 2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        </filter-class>
            </filter>
            <filter-mapping>
                <filter-name>Struts 2</filter-name>
                <url-pattern>*.jsp</url-pattern>
            </filter-mapping>
            <filter-mapping>
                <filter-name>Struts 2</filter-name>
                <url-pattern>*.js</url-pattern>
            </filter-mapping>
            <filter-mapping>
                <filter-name>Struts 2</filter-name>
                <url-pattern>*.action</url-pattern>
            </filter-mapping>
            <welcome-file-list>
                <welcome-file>index.jsp</welcome-file>
            </welcome-file-list>
        </web-app>

从web.xml中的配置可以看到,Struts 2通过使用Filter过滤器来拦截以jsp和js或action为后缀的请求,再把这些请求转给StrutsPrepareAndExecuteFilter类进行后续的处理。

3.1.4 创建控制层Controller文件—Login.java

      Login.java文件的核心代码如下。
        package controller;
        import service.UserinfoService;
        public class Login {
            private String username;
            private String password;
            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            public String getPassword() {
                return password;
            }
            public void setPassword(String password) {
                this.password = password;
            }
            public String execute() {
                UserinfoService usRef = new UserinfoService();
                if (usRef.login(username, password)) {
                    return "toOKJSP";
                } else {
                    return "toNOJSP";
                }
            }
        }

使用Servlet技术作为控制层时,自定义的Servlet类还需要继承自HttpServlet类,代码如下。

        public class login extends HttpServlet

而Struts 2并不需要继承自任何类就可以实现Controller控制层的功能,这也是Struts 2框架一直倡导的“解耦合”。

方法execute()是固定的写法,也就是,Struts 2对这种写法是有声明格式规定的,必须是一个公开(public)的方法,该方法的返回数据类型是字符串(String),方法名是execute。该约定就像public static void main(String args[ ])一样固定,它是Struts 2默认会调用的方法,返回值String类型代表一个逻辑的名称,通过这个逻辑的名称找到对应的物理JSP视图文件。

属性username和password并不是随便写的,它与视图层中<input>标签中的name属性值对应。

3.1.5 创建业务逻辑层Model文件—UserinfoService.java

业务逻辑层UserinfoService.java文件的代码如下。

        package service;
        public class UserinfoService {
            public boolean login(String username, String password) {
                if (username.equals("a") && password.equals("aa")) {
                    return true;
                } else {
                    return false;
                }
            }
        }

它是登录功能的核心业务,返回true或false作为登录成功或失败的结果值。

3.1.6 创建视图层View文件—login.jsp

视图层文件login.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <head>
            </head>
            <body>
                <form action="login.action" method="post">
                    username:
                    <input type="text" name="username">
                    <br />
                    password:
                    <input type="text" name="password">
                    <br />
                    <input type="submit" value="登录">
                </form>
            </body>
        </html>

标签<form>的action属性值为login.action代表要把username和password提交给Login.java类,但在现在的情况下Login.java类仅仅是一个普通的Java类,并没有处理HTTP协议的功能,所以必须在src中创建struts.xml配置文件。

3.1.7 添加核心配置文件struts.xml及解释

创建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>
            <constant name="struts.devMode" value="true" />
            <package name="Struts 2login" extends="struts-default">
                <action name="login" class="controller.Login">
                    <result name="toOKJSP">/ok.jsp</result>
                    <result name="toNOJSP">/no.jsp</result>
                </action>
            </package>
        </struts>

配置代码如下。

        <constant name="struts.devMode" value="true" />

这行配置代码的作用是使Struts 2框架运行在开发模式,在此种模式下对异常的报错信息显示的比较完整,便于排错。

配置代码如下。

        <package name="Struts 2login" extends="struts-default">

这行配置代码的作用是定义一个package包。与Java中package包不同的是,Struts 2中的package包可以继承,这也体现了配置代码也是可以复用的,属性extends值为struts-default的含义是自定义的包struts2login继承自系统自带的包struts-default,这样就可以将系统自带的功能得到复用,比如上传下载等。

该包在Struts 2-core-2.3.12.jar文件中的struts-default.xml配置文件中进行定义,如图3-8所示。

图3-8 struts-default是系统自带的包

包struts-default的作用是提供一些默认的通用功能,比如,上传、验证、下载等,在项目中只需要继承它,项目也就拥有了这些功能。

配置代码如下。

        <action name="login" class="controller.Login">

这行配置代码的作用相当关键,controller包中的Login.java类是一个普通的JavaBean,但通过这行代码的配置,此类就可以处理http请求中的数据了,name属性是访问的路径,也就是<form>标签中的action的值,但需要加.action后缀。

在<action>标签中有以下配置代码。

        <result name="toOKJSP">/ok.jsp</result>

它的作用是用一个逻辑名称toOKJSP对应物理ok.jsp文件,它就是execute()方法的返回值。

3.1.8 添加ok.jsp和no.jsp登录结果文件

添加文件ok.jsp,代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                ok.jsp
            </body>
        </html>

添加文件no.jsp,代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                no.jsp
            </body>
        </html>

3.1.9 运行项目

设计完上面的步骤后,将项目部署到tomcat中,在IE中输入网址:http://localhost:8081/Struts 2Login/login.jsp。显示的界面如图3-9所示。

图3-9 显示登录界面

什么都不输入直接单击“登录”按钮显示的登录失败界面如图3-10所示。

图3-10 登录失败

输入a和aa显示登录成功的界面如图3-11所示。

图3-11 显示登录成功

3.1.10 Struts 2的拦截器

在实现登录的功能时,Action中的属性会自动设置值,这样的效果其实是拦截器在起作用,拦截器的作用是什么?Struts 2框架底层依赖的是XWork框架,XWork框架是命令模式的实现,它也是Struts 2框架的基础,它提供Action的管理,Result对象的处理以及最重要的组件“拦截器”都在它的内部进行默默的工作。在Struts 2从接收请求(request)到完成响应(response)的过程中,Struts 2框架内部会有很多的类进行功能上的封装实现,比如,上传、数据验证、以及登录功能中对Action属性的赋值,都是拦截器在起作用。有了拦截器可以将Struts 2框架中的组件进行松耦合的开发,像七巧板一样根据业务的需要随意地拼装功能。

可以在项目中创建多个拦截器,但为了对拦截器有效地进行管理,可以对它们进行分组,形成拦截器栈,在struts-default.xml中就有一个名为defaultStack拦截器栈,它也是默认的拦截器栈,Struts 2中的一些拦截器栈的示例代码如下。

        <interceptor-stack name="basicStack">
        </interceptor-stack>
        <interceptor-stack name="validationWorkflowStack">
        </interceptor-stack>
        <interceptor-stack name="fileUploadStack">
        </interceptor-stack>
        <interceptor-stack name="modelDrivenStack">
        </interceptor-stack>
        <interceptor-stack name="chainStack">
        </interceptor-stack>
        <interceptor-stack name="i18nStack">
        </interceptor-stack>
        <interceptor-stack name="paramsPrepareParamsStack">
        </interceptor-stack>
        <interceptor-stack name="defaultStack">
        </interceptor-stack>
        <interceptor-stack name="completeStack">
        </interceptor-stack>
        <interceptor-stack name="executeAndWaitStack">
        </interceptor-stack>

使用拦截器时必须要继承AbstractInterceptor类,然后重写intercept()方法,在该方法中进行业务的处理,其中,ActionInvocation的invoke()方法的主要作用就是执行Action中的内容,返回的字符串就是result的逻辑名称。

创建名为Struts 2AbstractInterceptorTest的Web项目,添加Struts 2开发环境。

创建控制层PrintUsername.java的代码如下。

        package controller;
        public class PrintUsername {
            public String execute() {
                System.out.println("PrintUsername execute()");
                return "toPrintUsernameJSP";
            }
        }

配置文件struts.xml中的核心内容如下。

        <package name="testtest" extends="struts-default">
            <action name="printUsername" class="controller.PrintUsername">
                <result name="toPrintUsernameJSP">
                    /printUsername.jsp
            </result>
            </action>
        </package>

程序运行后,在控制台准确输出相关的信息,如图3-12所示。

图3-12 运行结果1

这是默认的结果,没有什么特别之处,下面开始添加拦截器,MyInterceptor.java类的代码如下。

        package myinterceptor;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
        public class MyInterceptor extends AbstractInterceptor {
            @Override
            public String intercept(ActionInvocation arg0) throws Exception {
                System.out.println("前");
                String resultValue = arg0.invoke();
                System.out.println("后");
                return resultValue;
            }
        }

在配置文件struts.xml中注册拦截器并应用到action中,代码如下。

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
            "http://struts.apache.org/dtds/struts-2.1.dtd">
        <struts>
            <package name="testtest" extends="struts-default">
                <interceptors>
                    <interceptor name="myinterceptor" class="myinterceptor.MyInterceptor">
                        </interceptor>
                </interceptors>
                <action name="printUsername" class="controller.PrintUsername">
                    <result name="toPrintUsernameJSP">
                        /printUsername.jsp
                </result>
                    <interceptor-ref name="myinterceptor"></interceptor-ref>
                </action>
            </package>
        </struts>

程序运行后的结果如图3-13所示。

图3-13 运行结果2

通过上面的示例可以看到,在不更改任何Action代码的前提下,对功能进行了扩展,极大地方便组件式的开发。

下面继续更改Action,代码更改如下。

        package controller;
        public class PrintUsername {
            private String username;
            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            public String execute() {
                System.out.println("PrintUsername execute() username=" + username);
                return "toPrintUsernameJSP";
            }
        }

输入URL网址:http://localhost:8081/Struts 2AbstractInterceptorTest/printUsername.action? username=ghy,控制台输出的信息如图3-14所示。

图3-14 运行结果3

为什么无法获取username了呢?因为现在使用的拦截器只有自己创建的MyInterceptor.java,系统中的拦截器被屏蔽掉了,如何启用它呢?很简单,把它引入进来即可,struts.xml配置文件的更改代码如下。

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
    "http://struts.apache.org/dtds/struts-2.1.dtd">
        <struts>
            <package name="testtest" extends="struts-default">
                <interceptors>
                    <interceptor name="myinterceptor" class="myinterceptor.MyInterceptor">
                        </interceptor>
                    <interceptor-stack name="myInterceptorStack">
                        <interceptor-ref name="defaultStack"></interceptor-ref>
                        <interceptor-ref name="myinterceptor"></interceptor-ref>
                    </interceptor-stack>
                </interceptors>
                <action name="printUsername" class="controller.PrintUsername">
                    <result name="toPrintUsernameJSP">
                        /printUsername.jsp
                </result>
                    <interceptor-ref name="myInterceptorStack"></interceptor-ref>
                </action>
            </package>
        </struts>

控制层printUsername引用自定义的拦截器栈myInterceptorStack,而myInterceptorStack中又包含默认拦截器栈defaultStack,所以这样的设计就能正确输出url中的参数了,重启tomcat重新执行URL,控制台输出的信息如图3-15所示。

图3-15 成功取得参数

如果想在每一个<action>标签中使用拦截器,就必须使用<interceptor-ref>子标签进行引用,有多少个<action>得需要引用多少次<interceptor-ref>。所以,在这种情况下,可以创建一个默认拦截器栈,配置代码如下。

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
            "http://struts.apache.org/dtds/struts-2.1.dtd">
        <struts>
            <package name="testtest" extends="struts-default">
                <interceptors>
                    <interceptor name="myinterceptor" class="myinterceptor.MyInterceptor">
                        </interceptor>
                    <interceptor-stack name="myInterceptorStack">
                        <interceptor-ref name="defaultStack"></interceptor-ref>
                        <interceptor-ref name="myinterceptor"></interceptor-ref>
                    </interceptor-stack>
                </interceptors>
                <default-interceptor-ref name="myInterceptorStack"></default-interceptor-ref>
                <action name="printUsername" class="controller.PrintUsername">
                    <result name="toPrintUsernameJSP">
                        /printUsername.jsp
                </result>
                </action>
            </package>
        </struts>

在浏览器中输入网址,控制台打印如图3-16所示。

图3-16 使用默认拦截器栈

3.1.11 Struts 2的数据类型自动转换

Struts 2的控制层Action属性通过使用拦截器可以进行自动赋值,但多个属性的数据类型有时会出现不一样的情况,而Struts 2能将常用的数据类型有效地自动转换。下面在Web项目Struts 2DataTypeAutoSet中进行演示。

将index.jsp文件的代码改成如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <s:form action="register.action" method="post">
                    <s:textfield name="username" label="username"></s:textfield>
                    <s:textfield name="age" label="age"></s:textfield>
                    <s:textfield name="insertdate" label="insertdate"></s:textfield>
                    <s:textfield name="point" label="point"></s:textfield>
                    <s:submit value="提交按钮"></s:submit>
                </s:form>
            </body>
        </html>

创建实体类Point.java的代码如下。

        package entity;
        public class Point {
            private String x;
            private String y;
            public String getX() {
                return x;
            }
            public void setX(String x) {
                this.x = x;
            }
            public String getY() {
                return y;
            }
            public void setY(String y) {
                this.y = y;
            }
        }

创建Register.java控制层,代码如下。

        public class Register extends ActionSupport {
            private String username;
            private int age;
            private Date insertdate;
            private Point point;
          //省略set和get方法
            public String execute() {
                System.out.println(username);
                System.out.println(age + 1);
                System.out.println(insertdate);
                System.out.println(point);
                StrutsTypeConverter converter;
                return null;
            }
        }

运行index.jsp文件,输入的数据如图3-17所示。

图3-17 在index.jsp中输入数据

单击“提交按钮”在控制台输出的数据如图3-18所示。

图3-18 年龄加1了

这说明类型已自动转换,再回到index.jsp输入如图3-19所示的信息。

图3-19 输入point值

单击“提交按钮”在控制台输出如下异常信息。

        ognl.MethodFailedException:   Method   "setPoint"   failed   for   object   controller.
    Register@124532  [java.lang.NoSuchMethodException:  controller.Register.setPoint  ([Ljava.
    lang.String;)]

这说明字符串“100_200”不能转换成Point数据类型,也就是,Struts 2并不会将所有数据类型都转换成功。遇到这种情况,可以创建一个数据类型转换器,只需要继承org.apache.Struts 2.util.StrutsTypeConverter类就可以创建自定义类型转换器。为了使用自定义数据类型转换器,还需要在配置文件中进行注册,有两种方式。

· 应用于全局范围的类型转换器 在src目录中创建xwork-conversion.properties属性文件,内容为:

转换类全名=类型转换器类全名

· 应用于特定类的类型转换器 在特定类的相同目录下创建名为ClassName-conversion. properties的属性文件,内容为:

特定类的属性名=类型转换器类全名

继续解决上述Point类型转换异常的问题,创建一个自定义类型转换器PointConverter,代码如下。

        package extconverter;
        import java.util.Map;
        import org.apache.Struts 2.util.StrutsTypeConverter;
        public class PointConverter extends StrutsTypeConverter {
            @Override
            public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
                Object object = arg1;
                System.out.println(object);
                return null;
            }
            @Override
            public String convertToString(Map arg0, Object arg1) {
                return null;
            }
        }

在controller类中创建Register-conversion.properties文件,其中,Register和控制层Java文件同名,内容如图3-20所示。

图3-20 属性文件1

上述代码主要定义的就是point这个对象数据类型转换器类的全路径。部署项目,执行URL,在控制台输出的信息如图3-21所示。

图3-21 转换器已经发挥作用

从输出结果来看,先调用的是PointConverter.java类中的方法:

        @Override
        public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
            Object object = arg1;
            System.out.println(object);
            return null;
        }

输出的是数组类型,数组中仅有一个值,继续更改代码。

        @Override
        public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
            System.out.println(arg1[0]);
            return null;
        }

再次运行项目,运行结果如图3-22所示。

图3-22 取得要转换的数据值

从图3-22中可以看到,数组下标[0]的值其实就是<input>表单中输入的字符串。下面开始对这个字符串进行解析,更改PointConverter.java类的代码如下。

        public class PointConverter extends StrutsTypeConverter {
            @Override
            public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
                System.out.println("执行了convertFromString");
                String[] pointArray = arg1[0].split("\\_");
                Point point = new Point();
                point.setX(pointArray[0]);
                point.setY(pointArray[1]);
                return point;
            }
            @Override
            public String convertToString(Map arg0, Object arg1) {
                System.out.println("执行了convertToString");
                return ((Point) arg1).getX() + " " + ((Point) arg1).getY();
            }
        }

将控制层Register.java中的execute()方法作如下更改。

        public String execute() {
            System.out.println(username);
            System.out.println(age + 1);
            System.out.println(insertdate);
            System.out.println(point.getX() + " " + point.getY());
            StrutsTypeConverter converter;
            return null;
        }

程序运行后的结果如图3-23所示。

图3-23 正确得到解析

成功转换成Point数据类型,并且取出x和y坐标。

上述示例创建的是一个应用于特定类的类型转换器。下面继续测试,创建一个应用于全局范围的类型转换器。

创建名为Struts 2DateConverter的Web项目,添加Struts 2开发环境。

创建数据类型转换器DateConverter.java,代码如下。

        package extconverter;
        import java.text.ParseException;
        import java.text.SimpleDateFormat;
        import java.util.Date;
        import java.util.Map;
        import javax.xml.bind.TypeConstraintException;
        import org.apache.Struts 2.util.StrutsTypeConverter;
        public class DateConverter extends StrutsTypeConverter {
            private static SimpleDateFormat[] formatArray = {
                    new SimpleDateFormat("yyyy-MM-dd"),
                    new SimpleDateFormat("yyyy年MM月dd日"),
                    new SimpleDateFormat("yyyy/MM/dd") };
            @Override
            public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
                System.out.println("执行了Date日期格式转换!");
                String dateString = arg1[0];
                for (int i = 0; i < formatArray.length; i++) {
                    try {
                        return formatArray[i].parse(dateString);
                    } catch (ParseException e) {
                        e.printStackTrace();
                        continue;
                    }
                }
                throw new TypeConstraintException("日期转换失败");
            }
            // convertToString()方法仅仅在使用Struts 2标签输出时调用
            @Override
            public String convertToString(Map arg0, Object arg1) {
                System.out.println("DateConverter convertToString");
                return "在IE中输出:"
                        + new SimpleDateFormat("yyyy-MM-dd").format((Date) arg1);
            }
        }

在src下创建全局数据类型转换配置文件xwork-conversion.properties,代码如图3-24所示。

图3-24 全局数据类型转换器配置文件

创建控制层Register.java,代码如下。

        public class Register extends ActionSupport {
            private Date insertdate;
            public Date getInsertdate() {
                return insertdate;
            }
            public void setInsertdate(Date insertdate) {
                this.insertdate = insertdate;
            }
            public String execute() {
                System.out.println(insertdate);
                return "toIndexJSP";
            }
        }

文件index.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <s:property value="insertdate" />
            </body>
        </html>

重启Tomcat,输入网址:

http://localhost:8081/Struts 2DateConverter/register.action?insertdate=2000-1-1

在控制台及JSP页面输出的信息如图3-25所示。

图3-25 日期已转换

3.2 MVC框架的开发模型

前面的章节使用Struts 2框架来实现一个基于MVC模式的登录案例,那Struts 2到底是怎么实现MVC模式的呢?本节将用一个简短的示例实现一个MVC框架的模型,此模型的MVC功能和Struts 2一模一样。

3.2.1 基础知识准备1—解析并创建xml文件

在使用Struts 2框架时,在web.xml和struts.xml中都有xml代码,xml代码的主要作用就是配置,那在Java中如何读取xml中的内容呢?

创建名为Struts 2_xml的Java项目,在项目中引入dom4j-1.6.1.jar文件,创建struts.xml文件,代码如下。

        <mymvc>
            <actions>
                <action name="list" class="controller.List">
                    <result name="toListJSP">
                        /list.jsp
                  </result>
                    <result name="toShowUserinfoList" type="redirect">
                        showUserinfoList.ghy
                    </result>
                </action>
                <action name="showUserinfoList" class="controller.ShowUserinfoList">
                    <result name="toShowUserinfoListJSP">
                        /showUserinfoList.jsp
                    </result>
                </action>
            </actions>
        </mymvc>

创建Reader.java类,代码如下。

        package test;
        import java.util.List;
        import org.dom4j.Attribute;
        import org.dom4j.Document;
        import org.dom4j.DocumentException;
        import org.dom4j.Element;
        import org.dom4j.io.SAXReader;
        public class Reader {
            public static void main(String[] args) {
                try {
                    SAXReader reader = new SAXReader();
                    Document document = reader.read(reader.getClass()
                                .getResourceAsStream("/struts.xml"));
                    Element mymvcElement = document.getRootElement();
                    System.out.println(mymvcElement.getName());
                    Element actionsElement = mymvcElement.element("actions");
                    System.out.println(actionsElement.getName());
                    System.out.println("");
                    List<Element> actionList = actionsElement.elements("action");
                    for (int i = 0; i < actionList.size(); i++) {
                        Element actionElement = actionList.get(i);
                        System.out.println(actionElement.getName());
                        System.out.print("name="
                                + actionElement.attribute("name").getValue());
                        System.out.println("action class="
                                + actionElement.attribute("class").getValue());
                        List<Element> resultList = actionElement.elements("result");
                        for (int j = 0; j < resultList.size(); j++) {
                                Element resultElement = resultList.get(j);
                                System.out.print("  result name="
                              + resultElement.attribute("name").getValue());
                                Attribute typeAttribute = resultElement.attribute("type");
                                if (typeAttribute != null) {
                                System.out.println(" type=" + typeAttribute.getValue());
                                } else {
                                System.out.println("");
                                }
                                System.out.println("   " + resultElement.getText().trim());
                                System.out.println("");
                        }
                        System.out.println("");
                    }
                } catch (DocumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

运行程序后的结果如图3-26所示。

图3-26 用dom4j解析的XML文件内容

上面的案例是读取并解析xml文件,那如何创建xml文件呢?继续创建一个名为createXML的Java项目,创建Java类Writer.java,代码如下。

        package test;
        import java.io.FileWriter;
        import java.io.IOException;
        import org.dom4j.Document;
        import org.dom4j.DocumentHelper;
        import org.dom4j.Element;
        import org.dom4j.io.OutputFormat;
        import org.dom4j.io.XMLWriter;
        public class Writer {
            public static void main(String[] args) {
                try {
                    Document document = DocumentHelper.createDocument();
                    Element mymvcElement = document.addElement("mymvc");
                    Element actionsElement = mymvcElement.addElement("actions");
                    // /
                    Element listActionElement = actionsElement.addElement("action");
                    listActionElement.addAttribute("name", "list");
                    listActionElement.addAttribute("class", "controller.List");
                    Element toListJSPResultElement = listActionElement
                                .addElement("result");
                    toListJSPResultElement.addAttribute("name", "toListJSP");
                    toListJSPResultElement.setText("/list.jsp");
                    Element toShowUserinfoListResultElement = listActionElement
                                .addElement("result");
                    toShowUserinfoListResultElement.addAttribute("name",
                                "toShowUserinfoList");
                    toShowUserinfoListResultElement.addAttribute("type", "redirect");
                    toShowUserinfoListResultElement.setText("showUserinfoList.ghy");
                    // /
                    Element showUserinfoListActionElement = actionsElement
                                .addElement("action");
                    showUserinfoListActionElement.addAttribute("name",
                                "showUserinfoList");
                    showUserinfoListActionElement.addAttribute("class",
                                "controller.ShowUserinfoList");
                    Element toShowUserinfoListJSPResultElement = showUserinfoListActionElement
                                .addElement("result");
                    toShowUserinfoListJSPResultElement.addAttribute("name",
                                "toShowUserinfoListJSP");
                    toShowUserinfoListResultElement.setText("/showUserinfoList.jsp");
                    // /
                    OutputFormat format = OutputFormat.createPrettyPrint();
                    XMLWriter writer = new XMLWriter(new FileWriter("ghy.xml"), format);
                    writer.write(document);
                    writer.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

运行程序后,在当前项目中创建了名为ghy.xml的文件,文件内容如图3-27所示。

图3-27 创建的ghy.xml中的内容

3.2.2 基础知识准备2—Java的反射

反射技术可以对对象中的内容进行动态访问,在开发底层框架时经常使用到。

创建名为reflect的Java项目。

创建测试用的实体类Userinfo.java,代码如下。

        package entity;
        import java.util.Date;
        public class Userinfo {
            private int id;
            private String username;
            private String password;
            private int age;
            private Date insertDate;
            public Userinfo(int id, String username, String password, int age) {
                super();
                this.id = id;
                this.username = username;
                this.password = password;
                this.age = age;
            }
            public Userinfo() {
                super();
                id = 100;
                username = "中国";
                password = "中国人";
                age = 200;
                insertDate = new Date();
            }
            public Userinfo(int id, String username, String password, int age,
                    Date insertDate) {
                super();
                this.id = id;
                this.username = username;
                this.password = password;
                this.age = age;
                this.insertDate = insertDate;
            }
            public String printDate() {
                System.out.println("调用了printDate()无参方法!");
                return "将高洪岩返回!";
            }
              //省略get和set
        }

创建运行类Run.java的代码如下:

        package test;
        import java.lang.reflect.Constructor;
        import java.lang.reflect.InvocationTargetException;
        import java.lang.reflect.Method;
        import entity.Userinfo;
        public class Run {
            public static void main(String[] args) {
                try {
                    // /用3种方式取得Userinfo类的Class对象
                    Class<?> classRef1 = Class.forName("entity.Userinfo");
                    Class<?> classRef2 = new Userinfo().getClass();
                    Class<?> classRef3 = Userinfo.class;
                    System.out.println("证明获取的类名称一模一样:" + classRef1.getName() + "--"
                                + classRef2.getName() + "--" + classRef3.getName());
                    // /使用Class类的newInstance()方法实例化Userinfo对象
                    Class<?> classRef = Userinfo.class.getClassLoader().loadClass(
                                "entity.Userinfo");
                    Userinfo userinfoRef = (Userinfo) classRef.newInstance();
                    System.out.println("使用Class类的newInstance()方法调用无参的构造函数产生的默认
                    值:id="
                                + userinfoRef.getId() + " username="
                                + userinfoRef.getUsername() + " password="
                                + userinfoRef.getPassword() + " age="
                                + userinfoRef.getAge() + " insertDate="
                                + userinfoRef.getInsertDate());
                    // /使用Constructor类的newInstance()方法实例化Userinfo对象
                    Constructor<?> constructorUserinfo1 = classRef
                                .getDeclaredConstructor(null);
                    Userinfo userinfoRef1 = (Userinfo) constructorUserinfo1
                                .newInstance(null);
                    System.out
                                .println("使用Constructor类的newInstance()方法调用无参的构造函数产
                                生的默认值:id="
                              + userinfoRef1.getId()
                              + " username="
                              + userinfoRef1.getUsername()
                              + " password="
                              + userinfoRef1.getPassword()
                              + " age="
                              + userinfoRef1.getAge()
                              + " insertDate="
                              + userinfoRef1.getInsertDate());
                    // /使用Constructor类的newInstance()方法实例化Userinfo对象
                    Constructor<?> constructorUserinfo2 = classRef
                                .getDeclaredConstructor(int.class, String.class,
                              String.class, int.class);
                    Userinfo userinfoRef2 = (Userinfo) constructorUserinfo2
                                .newInstance(100, "高洪岩账号", "高洪岩密码", 200);
                    System.out
                                .println("使用Constructor类的newInstance()方法调用有参的构造函数产
                                生的默认值:id="
                              + userinfoRef2.getId()
                              + " username="
                              + userinfoRef2.getUsername()
                              + " password="
                              + userinfoRef2.getPassword()
                              + " age="
                              + userinfoRef2.getAge());
                    System.out.println("打印通过反射对属性进行赋值的password:"
                                + userinfoRef2.getPassword());
                    // /对无参方法进行反射调用
                    Method methodPrintDate = classRef.getDeclaredMethod("printDate");
                    System.out.println(methodPrintDate.invoke(userinfoRef));
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (SecurityException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

程序运行结果如下所示。

        证明获取的类名称一模一样:entity.Userinfo--entity.Userinfo--entity.Userinfo
        使用Class类的newInstance()方法调用无参的构造函数产生的默认值:id=100 username=中国password=
    中国人age=200 insertDate=Thu May 30 13:10:47 CST 2013
        使用Constructor类的newInstance()方法调用无参的构造函数产生的默认值:id=100 username=中国
    password=中国人age=200 insertDate=Thu May 30 13:10:47 CST 2013
        使用Constructor类的newInstance()方法调用有参的构造函数产生的默认值:id=100 username=高洪岩账号
    password=高洪岩密码age=200
        打印通过反射对属性进行赋值的password:高洪岩密码
        调用了printDate()无参方法!
        将高洪岩返回!

3.2.3 实现MVC模型—自定义配置文件

此案例完整的项目结构如图3-28所示。

图3-28 完整的项目结构

其中,mymvc1.xml文件的代码如下。

        <mymvc>
            <actions>
                <action name="listString" class="controller.ListString">
                    <result name="toListJSP">
                        /listString.jsp
                </result>
                    <result name="toShowUserinfoList" type="redirect">
                        showUserinfoList.ghy
                    </result>
                </action>
            </actions>
        </mymvc>

文件mymvc2.xml的代码如下。

        <mymvc>
            <actions>
                <action name="showUserinfoList" class="controller.ShowUserinfoList">
                    <result name="toShowUserinfoListJSP">
                        /showUserinfoList.jsp
                    </result>
                </action>
            </actions>
        </mymvc>

上面的两个xml配置文件中各有一些<action>控制层的配置,也就是,可以将多个控制层<action>分散到不同的配置文件中,这样更有利于项目的模块化设计。

3.2.4 实现MVC模型—ActionMapping.java封装<action>信息

创建ActionMapping.java类,此类的功能是对<action>中的信息进行封装,该类的代码结构如图3-29所示。

图3-29 ActionMapping.java类的代码结构

3.2.5 实现MVC模型—ResultMapping.java以封装<result>信息

创建ResultMapping.java类,此类的主要作用就是对<result>标签中的信息进行封装,代码结构如图3-30所示。

图3-30 ResultMapping.java类的代码结构

3.2.6 实现MVC模型—管理映射信息的ActionMappingManager.java对象

ActionMappingManager.java类主要的作用就是对ActionMapping.java进行管理,因为在一个项目中有可能有多个<action>标签,它的代码如下。

        package entity;
        import java.util.HashMap;
        import java.util.List;
        import java.util.Map;
        import javax.servlet.http.HttpServletRequest;
        import org.dom4j.Document;
        import org.dom4j.DocumentException;
        import org.dom4j.Element;
        import org.dom4j.io.SAXReader;
        public class ActionMappingManager {
            private Map<String, ActionMapping> actionMappingMap = new HashMap<String,
            ActionMapping>();
            public ActionMappingManager(String[] configFileArray) {
                createActionMapping(configFileArray);
            }
            private void createActionMapping(String[] configFileArray) {
                try {
                    for (int i = 0; i < configFileArray.length; i++) {
                        String configFile = configFileArray[i];
                        SAXReader reader = new SAXReader();
                        Document document = reader.read(this.getClass()
                                    .getResourceAsStream("/" + configFile));
                        List<Element> actionElementList = document.getRootElement()
                                .element("actions").elements("action");
                        for (int j = 0; j < actionElementList.size(); j++) {
                                ActionMapping actionMapping = new ActionMapping();
                                Element actionElement = actionElementList.get(j);
                                String actionValue = actionElement.attributeValue("name");
                                String classValue = actionElement.attributeValue("class");
                                actionMapping.setActionName(actionValue);
                                actionMapping.setClassName(classValue);
                                List<Element> resultElementList = actionElement
                              .elements("result");
                                if (resultElementList.size() > 0) {
                                actionMapping.setResultMapping(new HashMap());
                                for (int y = 0; y < resultElementList.size(); y++) {
                              ResultMapping resultMappping = new ResultMapping();
                              Element resultElement = resultElementList.get(y);
                              String resultName = resultElement
                                                .attributeValue("name");
                              String resultPath = resultElement.getText().trim();
                              resultMappping.setResultName(resultName);
                              resultMappping.setResultPath(resultPath);
                              if (resultElement.attribute("type") != null) {
                                            String typeValue = resultElement
                                                    .attributeValue("type");
                                            resultMappping.setIsRedirect("true");
                              }
                              actionMapping.getResultMapping().put(resultName,
                                                resultMappping);
                                }
                                }
                                actionMappingMap.put(actionValue, actionMapping);
                        }
                    }
                } catch (DocumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                String abc = "";
            }
            public ActionMapping getActionMapping(HttpServletRequest request) {
                String uri = request.getRequestURI();
                String contextPath = request.getContextPath();
                String actionPath = uri.substring(contextPath.length() + 1);
                String actionName = actionPath.substring(0, actionPath.indexOf("."));
                return actionMappingMap.get(actionName);
            }
        }

3.2.7 实现MVC模型—创建反射Action的ActionManager.java对象

ActionManager.java类主要的作用就是创建Action对象,代码如下。

        package entity;
        public class ActionManager {
            public static Action getAction(String className) {
                Action action = null;
                try {
                    action = (Action) Class.forName(className).newInstance();
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return action;
            }
        }

3.2.8 实现MVC模型—创建核心控制器ActionServlet.java

ActionServlet.java类主要的作用就是读取web.xml中的配置,用代码Servlet API可以读取web.xml中的一些配置信息,在本示例中使用两种方式来演示配置信息的读取,代码如下。

            <servlet>
                <servlet-name>test1</servlet-name>
                <servlet-class>controller.test1</servlet-class>
                <init-param>
                    <param-name>test1Key</param-name>
                    <param-value>test1Value</param-value>
                </init-param>
            </servlet>
            <servlet>
                <servlet-name>test2</servlet-name>
                <servlet-class>controller.test2</servlet-class>
            </servlet>
            <servlet-mapping>
                <servlet-name>test1</servlet-name>
                <url-pattern>/test1</url-pattern>
            </servlet-mapping>
            <servlet-mapping>
                <servlet-name>test2</servlet-name>
                <url-pattern>/test2</url-pattern>
            </servlet-mapping>
            <context-param>
                <param-name>servletKey</param-name>
                <param-value>servletValue</param-value>
            </context-param>

在上面的代码中创建了两个Servlet,分别是test1和test2,test1的核心代码如下。

        public class test1 extends HttpServlet {
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                System.out.println(this.getInitParameter("test1Key"));
                System.out
                        .println(this.getServletConfig().getInitParameter("test1Key"));
                System.out.println(this.getServletContext().getInitParameter(
                        "servletKey"));
            }
        }

运行程序后的结果如图3-31所示。

图3-31 test1的运行结果

在图3-31中可以看到,test1既可以取得<init-param>中的参数配置,也可以取得<context-param>中的参数配置。

继续执行test2,运行结果如图3-32所示。

图3-32 test2的运行结果

从图3-32中可以发现,<init-param>标签中的参数配置仅当前的Servlet对象可以获取,而<context-param>中的参数配置所有的Servlet都可以获取。

核心控制器ActionServlet.java也有从web.xml中读取配置参数的功能,但其主要作用就是将request请求和自定义MVC框架的相关Java组件进行关联,代码如下。

        package entity;
        import java.io.IOException;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServlet;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        public class ActionServlet extends HttpServlet {
            private ActionMappingManager actionMappingManager;
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                ActionMapping actionMapping = actionMappingManager
                        .getActionMapping(request);
                Action action = ActionManager.getAction(actionMapping.getClassName());
                String resultName = action.execute(request, response);
                ResultMapping resultMapping = actionMapping.getResultMapping().get(
                        resultName);
                if (resultMapping.getIsRedirect().equals("true")) {
                    response.sendRedirect(resultMapping.getResultPath());
                } else {
                    request.getRequestDispatcher(resultMapping.getResultPath().trim())
                                .forward(request, response);
                }
            }
            public void doPost(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                this.doGet(request, response);
            }
            public void init() throws ServletException {
                System.out.println("ActionServlet init()无参");
                String configFileValue = this.getInitParameter("configFile");
                actionMappingManager = new ActionMappingManager(configFileValue
                        .split(","));
            }
        }

3.2.9 实现MVC模型—创建Action接口及控制层Controller实现类

Action接口主要的作用就是定义Controller控制层的接口规范,代码如下。

        package entity;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        public interface Action {
            public String execute(HttpServletRequest request,
                    HttpServletResponse response);
        }

创建控制层ListString.java的代码如下。

        package controller;
        import java.util.ArrayList;
        import java.util.List;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import entity.Action;
        public class ListString implements Action {
            public String execute(HttpServletRequest request,
                    HttpServletResponse response) {
                System.out.println("执行了ListString");
                List listString = new ArrayList();
                for (int i = 0; i < 10; i++) {
                    listString.add("高洪岩" + (i + 1));
                }
                request.setAttribute("listString", listString);
                return "toShowUserinfoList";
            }
        }

创建控制层ShowUserinfoList.java的代码如下。

        package controller;
        import java.util.ArrayList;
        import java.util.List;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import entity.Action;
        public class ShowUserinfoList implements Action {
            public String execute(HttpServletRequest request,
                    HttpServletResponse response) {
                System.out.println("执行了ShowUserinfoList");
                List userinfoList = new ArrayList();
                for (int i = 0; i < 5; i++) {
                    userinfoList.add("userinfo" + (i + 1));
                }
                request.setAttribute("userinfoList", userinfoList);
                return "toShowUserinfoListJSP";
            }
        }

3.2.10 实现MVC模型—创建视图层V对应的JSP文件

创建JSP文件listString.jsp,代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                listString.jsp!
                <br />
                ${listString}
            </body>
        </html>

创建JSP文件showUserinfoList.jsp,代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                showUserinfoList.jsp!
                <br />
                ${userinfoList}
            </body>
        </html>

本示例使用JSP作为数据展示的视图层,其实通过配置还可以在Excel文件中显示出来,后面的内容不是自定义MVC框架的一部分,仅仅作为技术要点的演示。

示例代码在excelShow项目中,Servlet的核心代码如下。

        public class test extends HttpServlet {
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                List list = new ArrayList();
                list.add("a11");
                list.add("a12");
                list.add("a13");
                list.add("a14");
                list.add("a15");
                request.setAttribute("list", list);
                request.getRequestDispatcher("excel.jsp").forward(request, response);
            }
        }

JPS文件代的码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"
            contentType="application/vnd.ms-excel;charset=utf-8"%>
        <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <c:forEach var="eachString" items="${list}">
                  ${eachString}<br />
                </c:forEach>
            </body>
        </html>

运行结果如图3-33所示。

图3-33 在Excel中显示

但在默认情况下用Excel文件格式显示数据不太美观,在商业软件项目中都使用报表框架来进行数据的统计及显示,在这里推荐作者的另外一本实用教程《JasperReports+iReport报表开发详解》,该书详细介绍了商业软件中报表模板的设计及程序的开发。

另外本书源码中还有Struts 2、Spring3MVC、Hibernate、MyBatis和Spring彼此之间的各种整合案例可供读者参考。

3.2.11 实现MVC模型—在web.xml中配置核心控制器

web.xml文件中的代码如下。

        <?xml version="1.0" encoding="UTF-8"?>
        <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
            <servlet>
                <servlet-name>ActionServlet</servlet-name>
                <servlet-class>entity.ActionServlet</servlet-class>
                <init-param>
                    <param-name>configFile</param-name>
                    <param-value>mymvc1.xml,mymvc2.xml</param-value>
                </init-param>
                <load-on-startup>0</load-on-startup>
            </servlet>
            <servlet-mapping>
                <servlet-name>ActionServlet</servlet-name>
                <url-pattern>*.ghy</url-pattern>
            </servlet-mapping>
            <welcome-file-list>
                <welcome-file>index.jsp</welcome-file>
            </welcome-file-list>
        </web-app>

3.2.12 实现MVC模型—运行结果

在IE中输入网址:

http://localhost:8081/newmymvc/listString.ghy

运行结果如图3-34所示。

图3-34 运行结果

至此,已经用基于Servlet的核心控制器实现了一个自定义MVC框架模型。

3.3 Struts 2的刷新验证功能

前面示例中的登录功能是通过跳转到不同的JSP文件来显示登录成功或失败的结果,在Struts 2中可以实现在当前页面中显示出错信息的功能,它具有有刷新浏览器的功能。

3.3.1 Action接口

在Struts 2中验证功能使用ActionSupport.java类实现,使用的方式就是从控制层继承它即可,它的类继承关系如下。

        public class com.opensymphony.xwork2.ActionSupport
        implements
        com.opensymphony.xwork2.Action,
        com.opensymphony.xwork2.Validateable,
        com.opensymphony.xwork2.ValidationAware,
        com.opensymphony.xwork2.TextProvider,
        com.opensymphony.xwork2.LocaleProvider,
        java.io.Serializable

在上面的结构中可以看到ActionSupport实现了6个接口,其中,Action接口的主要作用就是提供一个execute()访问接口。也就是,所有的控制层必须要实现此方法,此方法在前面的知识点中也应用过。

Action接口的声明如图3-35所示。

图3-35 Action接口的声明

在Action接口中声明了5个常量,这5个常量就是execute()方法返回的字符串可以使用的result逻辑名称,较常用的是success(成功)和error(出错)。通过实现Action接口就可以将result对象的名称进行标准化,项目组中的程序员可以将常用的result名称进行统一,常量对应的常量值如图3-36所示。

图3-36 5个常量值

应用的示例代码如下。

        public class Login extends ActionSupport {
            public String execute() {
                if (1 == 1) {
                    return Action.SUCCESS;
                } else {
                    return Action.ERROR;
                }
            }
        }

在struts.xml中的action配置代码如下。

        <action name="login" class="controller.Login">
            <result name="success">/ok.jsp</result>
            <result name="error">/no.jsp</result>
        </action>

但在实际的软件项目中这样的使用非常少,因为如果<result>的逻辑名称报错,也会很容易地进行排查。

当找不到result的逻辑名称toOKJSP1时,报告如下错误。

        No result defined for action controller.Login and result toOKJSP1

当找不到action路径login1时,报告如下错误。

        There is no Action mapped for namespace [/] and action name [login1] associated with context
        path [/Struts 2Login]. - [unknown location]

3.3.2 Validateable和ValidationAware接口

ActionSupport类实现Validateable接口,接口的声明如图3-37所示。

图3-37 Validateable接口的声明

Validateable接口只有一个方法validate(),ActionSupport的子类必须覆盖该方法来实现自定义验证功能。

ValidationAware接口的声明如图3-38所示。

图3-38 ValidationAware接口的声明

Validateable接口中validate()方法的功能是把自定义验证代码放在里面执行,那么如何才能对出错信息进行进一步的处理呢?此时,就需要使用ValidationAware接口,ValidationAware接口规定了如果验证出错应该用哪些方法来对出错信息进行处理。所以,ActionSupport类实现Validateable接口即取得了验证功能,而又实现了ValidationAware接口,从而还可以对出错信息进行处理。

3.3.3 TextProvider和LocaleProvider接口

TextProvider接口的作用是从properties属性文件中取得资源文本,它的声明内容如图3-39所示。

图3-39 TextProvider接口的声明

而LocaleProvider接口的主要作用就是访问国际化的环境,根据不同的国家语言环境,就可以从不同语言的properties文件中取出对应的文本资源,LocaleProvider接口的声明如图3-40所示。

图3-40 LocaleProvider接口的声明

3.3.4 使用ActionSupport实现有刷新的验证

创建名为Struts 2ActionSupportLogin的Web项目,搭建正确的Struts 2开发环境。

(1)创建控制层Login.java,核心代码如下。

        public class Login extends ActionSupport {
            private String username;
            private String password;
          //set和get方法省略 ! 一定要在Login.java中生成get和set方法
            @Override
            public void validate() {
                super.validate();
                if ("".equals(this.getUsername())) {
                    this.addFieldError("username", "对不起,用户名不可以为空!");
                    // 通过使用addFieldError方法可以对出错信息进行处理
                    // 再将这些出错信息文本在JSP页面上显示
                }
                if ("".equals(this.getPassword())) {
                    this.addFieldError("password", "对不起,密码不可以为空!");
                }
            }
            public String execute() {
                UserinfoService usRef = new UserinfoService();
                if (usRef.login(username, password)) {
                    return "toOKJSP";
                } else {
                    return "toNOJSP";
                }
            }
        }

(2)Login.java中的username和password的属性值来自于JSP,所以继续创建login.jsp文件,代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <s:form action="login" method="post">
                    <s:textfield name="username" label="账号"></s:textfield>
                    <br />
                    <s:textfield name="password" label="密码"></s:textfield>
                    <br />
                    <s:submit value="提交"></s:submit>
                </s:form>
            </body>
        </html>

在这里需要注意的是JSP文件中使用的是Struts 2的标签,目的是可以自动从作用域中获取出错信息并显示出来。

使用s:textfield标签的目的是在浏览器中生成一个单行文本域,而它的name属性的值一定要和Login.java类中的属性名称相同,这样才可以实现在JSP页面中单击“提交”按钮自动填充Login.java类中的属性值。label属性是标签内的文本提示内容。

(3)更改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>
            <constant name="struts.devMode" value="true" />
            <package name="Struts 2login" extends="struts-default">
                <action name="login" class="controller.Login">
                    <result name="success">/true.jsp</result>
                    <result name="error">/false.jsp</result>
                    <result name="input">/login.jsp</result>
                </action>
            </package>
        </struts>

配置代码如下。

        <result name="input">/login.jsp</result>

这行配置代码的作用是当调用Login.java类中的this.addFieldError()函数时,说明验证没有通过,则转到哪个JSP页面进行报错信息的显示,若在Struts 2中使用ActionSupport类进行有刷新的验证,则必须在struts.xml中配置名为input的<result>,不然会出现异常。

        No result defined for action controller.Login and result input

(4)运行项目。

输入URL:http://localhost:8081/Struts 2ActionSupportLogin/login.jsp后,按回车键出现登录界面(见图3-41)。

图3-41 Struts 2标签自动转成html标签

什么都不输入而单击“提交”按钮出现的界面如图3-42所示。

图3-42 自动显示出错信息

至此,在Struts 2中使用ActionSupport类进行有刷新的登录验证的实验结束。

3.4 对Struts 2有刷新验证的示例进行升级

为什么要升级?一定是有弊端,所以才会升级!有什么弊端呢?查看前面章节中使用Struts 2的标签<%@ taglib uri="/struts-tags" prefix="s"%>显示登录界面的html代码,如图3-43所示。

图3-43 存在大量自动生成的table/tr/td代码

在查看HTML源代码时可以发现,自动加入了大量的table、tr、td等标签,这样在前、后台程序员联合开发时,会给前台程序员带来大量重定义标签的CSS样式代码。实际上,可以通过配置来将Struts 2自动生成的HTML标签进行屏蔽,并且配置的方式非常简单。

3.4.1 加入xml配置来屏蔽自动生成的table/tr/td代码

去掉Struts 2标签自动生成的HTML代码很简单,只需要在配置文件struts.xml中添加如下代码即可。

        <constant name="struts.ui.theme" value="simple"></constant>

创建名为Struts 2ActionSupportLogin_2的Web项目,将前面名为Struts 2Action SupportLogin的项目中的代码复制到Struts 2ActionSupportLogin_2项目中,更改后的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>
            <constant name="struts.devMode" value="true" />
            <package name="Struts 2login" extends="struts-default">
                <action name="login" class="controller.Login">
                    <result name="success">/true.jsp</result>
                    <result name="error">/false.jsp</result>
                    <result name="input">/login.jsp</result>
                </action>
            </package>
            <constant name="struts.ui.theme" value="simple"></constant>
        </struts>

部署项目到tomcat中,运行login.jsp,运行结果及生成的HTML代码如图3-44所示。

图3-44 运行结果及生成的HTML代码

生成的HTML代码并没有出现table/tr/td等标签,但在IE中并没有将s:textfield标签的label属性显示出来:

        <s:textfield name="username" label="账号">

这正是代码<constant name="struts.ui.theme" value="simple"></constant>的执行结果,但是可以改由普通的文本标题来进行设计,也就是,弃用label属性。重新设计的login.jsp代码如下。

        <s:form action="login" method="post">
            账号:<s:textfield name="username"></s:textfield>
            <br />
            密码:<s:textfield name="password"></s:textfield>
            <br />
            <s:submit value="提交"></s:submit>
        </s:form>

在IE中重新运行的结果如图3-45所示。

图3-45 用文本代替label标签

3.4.2 解决“出错信息不能自动显示”的问题

什么也不输入,直接单击“提交”按钮进行登录,运行结果如图3-46所示。

图3-46 执行了action但出错信息并未显示

之所以出现这样的结果,是由下面的配置代码导致的。

        <constant name="struts.ui.theme" value="simple"></constant>

如果加入配置代码<constant name="struts.ui.theme" value="simple"></constant>就不会自动添加多余的table/tr/td等HTML标签,但又显示不了出错信息,那又如何解决这样的问题呢?先别着急,仔细思考现在的情况。

(1)使用Struts 2的标签目的就是能自动显示出错信息,而又增加了多余的HTML代码。(2)使用<constant name="struts.ui.theme">配置屏蔽了使用Struts 2的标签后自动生成的多余HTML代码,但又显示不了出错信息。

(3)所以现在的情况是没有必要再使用Strust2的标签了,理由是虽然现在不生成多余的HTML代码,但是无法自动显示出错信息了。

(4)所以弃用Struts 2的HTML标签,改用普通的HTML代码来进行表单的设计。

更改后的login.jsp代码如下。

        <form action="login.action">
            账号:
            <input type="text" name="username">
            <br />
            密码:
            <input type="text" name="password">
            <br />
            <input type="submit" value="提交">
            <br />
        </form>

改用普通的<form>和<input>标签后还是不能自动显示出错信息,但单击“提交”按钮后又在Login.java中执行了addFieldError函数,这说明在后台中产生出了出错信息,仅仅取出的方法不得当,那出错信息在哪里呢?接触过Servlet编程的程序员在把出错信息显示到JSP页面时,都会将出错信息保存到request中,然后在JSP页面中通过JSTL/EL表达式显示出来,而Struts 2也使用这种方法,只不过封装得更加完善。那么如何才能看到Struts 2的出错信息的封装格式呢?在login.jsp文件中加入<s:debug></s:debug>标签即可,完整的login.jsp代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <s:debug></s:debug>
                <br />
                <form action="login.action">
                    账号:
                    <input type="text" name="username">
                    <br />
                    密码:
                    <input type="text" name="password">
                    <br />
                    <input type="submit" value="提交">
                    <br />
                </form>
            </body>
        </html>

在IE中运行login.jsp的结果如图3-47所示。

图3-47 加入s:debug标签的运行结果

什么都不输入,继续单击“提交”按钮进行登录,再次跳转到login.jsp文件,显示结果如图3-48所示。

图3-48 经过控制层的验证再返回login.jsp文件

单击Debug超级链接,出现的界面如图3-49所示。

图3-49 出错信息在ValueStack对象中

如何获取出错信息呢?设计login.jsp的代码如下。

        <form action="login.action">
            账号:
            <input type="text" name="username">
            ${errors.username[0] }
            <br />
            密码:
            <input type="text" name="password">
            ${errors.password[0] }
            <br />
            <input type="submit" value="提交">
            <br />
        </form>

重新运行login.jsp,直接单击“提交”按钮,运行结果如图3-50所示。

图3-50 成功获取出错信息

到此,实验已经实现在Struts 2框架中使用ActionSupport类进行有刷新的验证,解决了如何获取出错信息的问题,以及屏蔽自动生成的table/tr/td代码。

3.5 用<s:actionerror>标签显示全部出错信息

前面的章节用${}EL表达式来从作用域中逐条地获取出错信息,Struts 2中还可以使用<s:actionerror>标签批量地显示出错信息。

创建名为actionerrorTAG的Web项目,添加Struts 2开发环境。

创建控制层Login.java的代码如下。

        public class Login extends ActionSupport {
            private String username;
            private String password;
            // set和get方法省略
            @Override
            public void validate() {
                super.validate();
                if ("".equals(this.getUsername())) {
                    this.addActionError("对不起,用户名不可以为空!");
                }
                if ("".equals(this.getPassword())) {
                    this.addActionError("对不起,密码不可以为空!");
                }
            }
            public String execute() {
                UserinfoService usRef = new UserinfoService();
                if (usRef.login(username, password)) {
                    return "toOKJSP";
                } else {
                    return "toNOJSP";
                }
            }
        }

登录界面login.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <s:actionerror />
                <br />
                <s:debug></s:debug>
                <br />
                <form action="login.action">
                    账号:
                    <input type="text" name="username">
                    <br />
                    密码:
                    <input type="text" name="password">
                    <br />
                    <input type="submit" value="提交">
                    <br />
                </form>
            </body>
        </html>

运行结果如图3-51所示。

图3-51 批量显示出错信息

对比前面的ValueStack中的内容可以发现,addFieldError是使用Map来进行存储出错信息的,而addActionError是使用ArrayList进行存储的,掌握显示这两种出错信息的技能是掌握Struts 2验证功能的必要条件。

3.6 出错信息进行传参及国际化

前面章节中的出错文本信息是直接写在Java文件中的,不便于维护及国际化,本节将使用国际化技术来针对不同语言的浏览器客户端显示指定语言的文本信息,还可以向出错信息传递参数。

3.6.1 创建info_en_US.properties和info_zh_CN.properties属性文件

因为国际化的文本信息要存放在properties文件中,所以要创建info_en_US.properties和info_zh_CN.properties属性文件。

为什么要在properties属性文件中写上en和US以及zh和CN呢?这就得需要取得Java支持所有国家和语言的简码,创建名为的Java项目,新建Run.java的代码如下。

        public class Test {
            public static void main(String[] args) {
                Locale[] localeArray = Locale.getAvailableLocales();
                for (int i = 0; i < localeArray.length; i++) {
                    Locale eachLocale = localeArray[i];
                    System.out.println(eachLocale.getCountry() + " "
                                + eachLocale.getLanguage());
                }
            }
        }

运行结果如下。

        JP ja
        PE es
          en
        JP ja
        PA es
        BA sr
        mk
        GT es
        AE ar
        NO no
        AL sq
          bg
        IQ ar
        YE ar
          hu
        PT pt
        CY el
        QA ar
        MK mk
          sv
        CH de
        US en
        FI fi
          is
          cs
        MT en
        SI sl
        SK sk
          it
        TR tr
          zh
          th
        SA ar
          no
        GB en
        CS sr
          lt
          ro
        NZ en
        NO no
        LT lt
        NI es
          nl
        IE ga
        BE fr
        ES es
        LB ar
          ko
        CA fr
        EE et
        KW ar
        RS sr
        US es
        MX es
        SD ar
        ID in
          ru
          lv
        UY es
        LV lv
          iw
        BR pt
        SY ar
          hr
          et
        DO es
        CH fr
        IN hi
        VE es
        BH ar
        PH en
        TN ar
          fi
        AT de
          es
        NL nl
        EC es
        TW zh
        JO ar
          be
        IS is
        CO es
        CR es
        CL es
        EG ar
        ZA en
        TH th
        GR el
        IT it
          ca
        HU hu
          fr
        IE en
        UA uk
        PL pl
        LU fr
        BE nl
        IN en
        ES ca
        MA ar
        BO es
        AU en
          sr
        SG zh
          pt
          uk
        SV es
        RU ru
        KR ko
          vi
        DZ ar
        VN vi
        ME sr
          sq
        LY ar
        ar
        CN zh
        BY be
        HK zh
          ja
        IL iw
        BG bg
          in
        MT mt
        PY es
          sl
        FR fr
        CZ cs
        CH it
        RO ro
        PR es
        CA en
        DE de
          ga
        LU de
          de
        AR es
          sk
        MY ms
        HR hr
        SG en
          da
          mt
          pl
        OM ar
          tr
        TH th
          el
          ms
        SE sv
        DK da
        HN es

其中,在上面输出的内容中就有en和US,以及zh和CN,本实验要在项目中针对中文和英语的浏览器客户端显示指定的文本信息,所以要创建info_en_US.properties和info_zh_CN. properties文件,而文件的前缀info就是此文件的标识。

3.6.2 在JSP文件中显示国际化的静态文本

创建名为localeTest的Web项目,创建info_en_US.properties和info_zh_CN.properties文件,两个属性文件的初始内容如图3-52所示。

图3-52 两个属性文件的初始内容

在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>
            <constant name="struts.devMode" value="true" />
            <package name="localeTest" extends="struts-default">
            </package>
            <constant name="struts.custom.i18n.resources" value="info"></constant>
        </struts>

起主要配置作用的就是如下代码。

        <constant name="struts.custom.i18n.resources"
        value="info"></constant>

value属性的值为info也就是properties属性文件的标识。

按照下面的代码更改index.jsp文件的内容。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <s:text name="staticText"></s:text>
                <br />
            </body>
        </html>

部署项目,运行结果如图3-53所示。

图3-53 显示中文文本

那怎么显示英语文本呢?进入IE的“Internet选项”菜单,单击“语言”菜单,添加英语,并将英语上移至顶端,效果如图3-54所示。

图3-54 添加英语

“英语(美国)”添加完成后,单击“确定”按钮完成配置,再刷新IE,则显示英语的提示文本,效果如图3-55所示。

图3-55 显示英语的提示文本

3.6.3 在JSP文件中显示国际化的静态文本时传递参数

更改属性文件内容,如图3-56所示。更改index.jsp的代码如下。

        <body>
            <s:text name="staticText"></s:text>
            <br />
            <s:text name="staticTextParam">
                <s:param>高洪岩1</s:param>
                <s:param>高洪岩2</s:param>
            </s:text>
            <br />
        </body>

图3-56 文本具有占位符

重新运行index.jsp,显示结果如图3-57所示。

图3-57 静态文本具有参数值

3.6.4 在Action中使用国际化功能

在控制层的Java文件中也可以取得国际化属性文件中的文本。

创建login.jsp文件,代码如下。

        <body>
            ${errors.usernameKey1[0] }
            <br />
            ${errors.usernameKey2[0] }
            <br />
            <form action="login.action" method="post">
                username:
                <input type="text" name="username">
                <br />
                <input type="submit" value="登录">
            </form>
        </body>

创建控制层Login.java类,代码如下。

        package controller;
        import java.util.ArrayList;
        import java.util.List;
        import com.opensymphony.xwork2.ActionSupport;
        public class Login extends ActionSupport {
            private String username;
            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            @Override
            public void validate() {
                List paramValue = new ArrayList();
                paramValue.add(" 我是参数值");
                if ("".equals(username)) {
                    this.addFieldError("usernameKey1", this.getText("usernameNull1",
                                paramValue));
                }
                if ("".equals(username)) {
                    this.addFieldError("usernameKey2", this.getText("usernameNull2",
                                paramValue));
                }
            }
            public String execute() {
                return null;
            }
        }

更改属性文件,如图3-58所示。

图3-58 更改属性文件

继续更改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>
            <constant name="struts.devMode" value="true" />
            <package name="localeTest" extends="struts-default">
                <action name="login" class="controller.Login">
                    <result name="input">
                        /login.jsp
                  </result>
                </action>
            </package>
            <constant name="struts.custom.i18n.resources" value="info"></constant>
        </struts>

部署项目并运行URL:http://localhost:8081/localeTest/login.jsp,运行结果如图3-59所示。

图3-59 显示login.jsp运行结果

单击“登录”按钮,界面显示结果如图3-60所示。

图3-60 显示控制层中的国际化文本

3.7 用实体类封装URL中的参数——登录功能的URL封装

在登录Login.java类中有username和password属性,并且有相应的get和set方法。如果JSP的表单数量较大,那么Action类中的set和get方法代码将会非常多,不便于软件的代码维护,可以用一个实体类封装URL中的参数值。步骤如下所示。

(1)创建名为urlEntityLogin的Web项目,新建控制层Login.java文件的代码如下。

        package controller;
        import com.opensymphony.xwork2.ActionSupport;
        import entity.Userinfo;
        public class Login extends ActionSupport {
            private Userinfo userinfo = new Userinfo();
            public Userinfo getUserinfo() {
                return userinfo;
            }
            public void setUserinfo(Userinfo userinfo) {
                this.userinfo = userinfo;
            }
            @Override
            public void validate() {
                if ("".equals(this.userinfo.getUsername())) {
                    this.addFieldError("usernameNull", "账号为空!");
                }
                if ("".equals(this.userinfo.getPassword())) {
                    this.addFieldError("passwordNull", "密码为空!");
                }
            }
            public String execute() {
                return null;
            }
        }

(2)创建封装URL参数的实体,如图3-61所示。

图3-61 Userinfo实体类的结构

(3)login.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <head>
            </head>
            <body>
                ${errors.usernameNull[0] }
                <br />
                ${errors.passwordNull[0] }
                <br />
                <form action="login.action" method="post">
                    username:
                    <input type="text" name="userinfo.username">
                    <br />
                    password:
                    <input type="text" name="userinfo.password">
                    <br />
                    <input type="submit" value="登录">
                </form>
            </body>
        </html>

(4)登录失败时,出现的异常如图3-62所示。

图3-62 登录失败

3.8 Struts 2中的转发操作

在前面的章节中已经实现了转发的操作,登录成功转发到ok.jsp,失败转发到no.jsp。

3.8.1 Servlet中的转发操作

转发操作是在服务器端的行为,也就是使用一个request对象,可以在request作用域中存入对象,最终在JSP页面显示。在JSP页面中,使用如下语句实现转发操作。

        request.getRequestDispatcher("jsp").forward(request, response);

3.8.2 Struts 2中的转发操作

在本示例中将实现一个列表的功能,此示例也是一个转发操作。实现转发操作的步骤如下所示。

(1)创建名为的Web项目,创建控制层ListString.java的代码如下。

        package controller;
        import java.util.ArrayList;
        import java.util.List;
        public class ListString {
            private List<String> listString = new ArrayList<String>();
            public List<String> getListString() {
                return listString;
            }
            public void setListString(List<String> listString) {
                this.listString = listString;
            }
            public String execute() {
                listString.add("a1");
                listString.add("a2");
                listString.add("a3");
                listString.add("a4");
                return "toListStringJSP";
            }
        }

(2)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>
            <constant name="struts.devMode" value="true" />
            <package name="Struts 2forward" extends="struts-default">
                <action name="listString" class="controller.ListString">
                    <result name="toListStringJSP" type="dispatcher">
                        /listString.jsp
                </result>
                </action>
            </package>
        </struts>

(3)listString.jsp的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                <c:forEach var="eachString" items="${listString}">
            ${eachString }<br />
                </c:forEach>
            </body>
        </html>

(4)运行结果如图3-63所示。

图3-63 运行结果

Struts 2中的转发操作主要是在struts.xml配置文件中使用type="dispatcher"属性进行定义,而写不写type="dispatcher"对转发操作毫无影响,默认情况下不写type属性即是转发操作,因为在struts-default.xml配置文件中有默认定义。查看struts-default.xml中的result-types节点下的相关配置,具体如下。

        <result-types>
            <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
            <result-type name="dispatcher"
                class="org.apache.Struts 2.dispatcher.ServletDispatcherResult"
                default="true"/>
            <result-type name="freemarker"
                class="org.apache.Struts 2.views.freemarker.FreemarkerResult"/>
            <result-type name="httpheader"
                class="org.apache.Struts 2.dispatcher.HttpHeaderResult"/>
            <result-type name="redirect"
                class="org.apache.Struts 2.dispatcher.ServletRedirectResult"/>
            <result-type name="redirectAction"
                class="org.apache.Struts 2.dispatcher.ServletActionRedirectResult"/>
            <result-type name="stream" class="org.apache.Struts 2.dispatcher.StreamResult"/>
            <result-type name="velocity"
                class="org.apache.Struts 2.dispatcher.VelocityResult"/>
            <result-type name="xslt" class="org.apache.Struts 2.views.xslt.XSLTResult"/>
            <result-type name="plainText"
                class="org.apache.Struts 2.dispatcher.PlainTextResult" />
        </result-types>

其中,result-types即为可以返回的种类。dispatcher有一个default属性,且值为true,含义是如果在result标记中不注明type,默认就是dispatcher转发操作。

上面列表中有几个result对象是开发中经常使用到的。例如,当result的类型是redirect时,通常在由Action重定向到一个JSP页面时使用,而redirectAction通常在重定向到一个Action时使用。