Java EE核心框架实战
上QQ阅读APP看书,第一时间看更新

3.9 由Action重定向到Action——无参数

在Servlet中使用如下代码:

        response.sendRedirect("jsp");

进行重定向操作,它是客户端的行为。

3.9.1 何种情况下使用重定向

使用重定向的经典场景就是登录成功后显示全部的书籍列表,这是从一个登录的Action重定向到另一个列表的Action。

3.9.2 新建起始控制层Login.java

新建项目Struts 2.5,并且新建模拟登录验证类,代码如下。

        package controller;
        public class Login {
            public String execute() {
                return "list";
            }
        }

代码“return "list";”在本示例要达到的目标是一个“重定向”操作,目的是改变IE的URL地址,执行List.action而列出图书列表。

重定向操作是指由Login.java重定向到List.java。

3.9.3 新建目的控制层List.java

设计List.java类,将所有书籍名称放入一个ArrayList中,并且在JSP文件显示出来,代码如下。

        package controller;
        import java.util.ArrayList;
        public class List {
            private ArrayList bookList = new ArrayList();
            public ArrayList getBookList() {
                return bookList;
            }
            public void setBookList(ArrayList bookList) {
                this.bookList = bookList;
            }
            public String execute() {
                bookList.add("book1");
                bookList.add("book2");
                bookList.add("book3");
                bookList.add("book4");
                return "bookListJsp";
            }
        }

代码很简单,声明一个ArrayList变量bookList,然后在execute中往这个bookList中存放书籍的名称,然后通过return"bookListJsp"转发到一个显示书籍列表的JSP文件。

3.9.4 在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>
            <package name="ghyStruts 1" extends="struts-default">
                <action name="login" class="controller.Login">
                    <result name="list" type="redirectAction">
                        <param name="actionName">List</param>
                    </result>
                </action>
                <action name="List" class="controller.List">
                    <result name="bookListJsp">/bookListJsp.jsp</result>
                </action>
            </package>
        </struts>

标记:

        <result name="list" type="redirectAction">
            <param name="actionName">List</param>
        </result>

的功能是从当前Actoin(即login)重定向到List这个Action,其中重定向的关键操作是type的属性值:redirectAction。

重定向到List后,再转发到bookListJsp.jsp文件,显示书籍详细的列表。

3.9.5 新建显示列表的JSP文件

显示书籍列表的bookListJsp.jsp文件的代码如下。

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

在IE地址栏中输入地址:http://localhost:8081/Struts 2.5/login.action之后,它立即变成地址:http://localhost:8081/Struts 2.5/List.action

IE界面如图3-64所示。

图3-64 程序运行结果

3.10 由Action重定向到Action——有参数

3.10.1 何种情况下需要重定向传递参数

如果有一个需求,在登录之后,默认显示的图书列表是第100页,这样的需求不仅仅是登录Action与图书列表Action进行重定向的操作,还需要向这两个Action传递参数。

比如,在登录成功之后显示图书列表第100页,本节就实现这样的需求。

3.10.2 新建起始控制层Login.java文件

新建项目Struts 2.6,新建控制层Login.java类,代码如下。

        package controller;
        public class Login {
            private int id;
            public int getId() {
                return id;
            }
            public void setId(int id) {
                this.id = id;
            }
            public String execute() {
                this.setId(100);
                return "list";
            }
        }

要把id的值100传递给List.java,也就是,图书列表的控制层。

3.10.3 更改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>
            <package name="ghyStruts 1" extends="struts-default">
                <action name="login" class="controller.Login">
                    <result name="list" type="redirectAction">
                        <param name="idInList">${id}</param>
                        <param name="actionName">List</param>
                    </result>
                </action>
                <action name="List" class="controller.List">
                    <result name="bookListJsp">/bookListJsp.jsp</result>
                </action>
            </package>
        </struts>

如何把Login.java中的id传递给List.java呢?其中最关键的配置如下:

        <result name="list" type="redirectAction">
            <param name="idInList">${id}</param>
            <param name="actionName">List</param>
        </result>

在从Login.java控制层重定向到List.java这个Action时,还要传递参数${id},这个值来自Login.java类的id属性,将Login.java类中的id属性传出去,传到哪里去?传到List.action中去。参数名称是什么?是idInList。那么这段配置代码其实就是本节的核心:揭示如何在Action与Action中传递参数,并且是重定向的操作。

type属性为redirectAction的作用正是从一个action重定向到另外一个action。

3.10.4 新建目的控制层List.java文件

显示图书列表的控制层List.java的代码如下:

        package controller;
        import java.util.ArrayList;
        public class List {
            private ArrayList bookList = new ArrayList();
            private int idInList;
            public ArrayList getBookList() {
                return bookList;
            }
            public void setBookList(ArrayList bookList) {
                this.bookList = bookList;
            }
            public int getIdInList() {
                return idInList;
            }
            public void setIdInList(int idInList) {
                this.idInList = idInList;
            }
            public String execute() {
                bookList.add("book1");
                bookList.add("book2");
                bookList.add("book3");
                bookList.add("book4");
                return "bookListJsp";
            }
        }

在List.java中可以看到,并没有发现针对idInList这个int类型变量赋值的操作,其实这个操作是由Struts 2底层来实现赋值的,只需要在List.java文件中声明一个int类型的idInList变量即可,框架底层自动将url中的同名参数赋给idInList同名属性。

3.10.5 用JSTL和EL在JSP文件中输出数据

显示图书列表的bookListJsp.jsp页面的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
        <%@ page isELIgnored="false"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                显示第${param.idInList }页
                <br />
                <c:forEach var="bookName" items="${bookList}">
                    <c:out value="${bookName}"></c:out>
                    <br />
                </c:forEach>
            </body>
        </html>

在IE地址栏输入地址:http://localhost:8081/Struts 2.6/login.action来进行模拟的登录表单提交之后,页面立即变化,并且地址为:http://localhost:8081/Struts 2.6/List.action?idInList=100。

显示内容如图3-65所示。

图3-65 IE显示的内容

3.11 让Struts 2支持多模块多配置文件开发

Struts 2支持多模块多配置文件开发,并且使用起来比Struts 1更加方便,下面就模拟项目里面有“仓库”、“前台”、“财务”3个模块,并且把这3个模块整合到一起的实验。

3.11.1 新建4个模块的控制层

新建项目Struts 2.7,创建4个控制层Action类,分别是“财务”类、“仓库”类、“前台”类和“从财务模块的财务重定向到仓库模块的财务”,代码如下。

        package controller;
        public class Caiwu {//财务类
            public String execute() {
                System.out.println("执行了财务统计模块");
                return "toCaiwu";
            }
        }
        package controller;
        public class Qiantai {//前台类
            public String execute() {
                System.out.println("执行了前台模块");
                return "toQiantai";
            }
        }
        package controller;
        public class Cangku {//仓库
            public String execute() {
                System.out.println("执行了仓库模块");
                return "toCangku";
            }
        }
        package controller;
        public class Fromcaiwu_caiwu_tocangku_caiwu {//不同模块之间的重定向的操作
            private int id;//属性id的作用是在重定向的过程中传递参数
            public int getId() {
                return id;
            }
            public void setId(int id) {
                this.id = id;
            }
            public String execute() {
                id = 999;
                System.out.println("从财务模块中的财务转到了仓库中的财务");
                return "fromcaiwu_caiwu_tocangku_caiwu";
            }
        }

3.11.2 新建3个模块的配置文件

创建3个配置文件,分别是“仓库模块xml文件”、“前台模块xml文件”、“财务模块xml文件”,3个xml配置文件的代码如下。

caiwu.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="caiwu" extends="struts-default" namespace="/caiwu">
                <action name="caiwu" class="controller.Caiwu">
                    <result name="toCaiwu">/caiwu.jsp</result>
                </action>
                <action name="fromcaiwu_caiwu_tocangku_caiwu"
                    class="controller.Fromcaiwu_caiwu_tocangku_caiwu">
                    <result name="fromcaiwu_caiwu_tocangku_caiwu"
                        type="redirectAction">
                        <param name="namespace">/cangku</param>
                        <param name="actionName">caiwu?id=${id}</param>
                    </result>
                </action>
            </package>
        </struts>

cangku.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="cangku" extends="struts-default" namespace="/cangku">
                <action name="caiwu" class="controller.Cangku">
                    <result name="toCangku">/cangku.jsp</result>
                </action>
            </package>
        </struts>

前面这两个配置文件一个共同的特点就是都有路径为caiwu的Action,但它们在不同的namespace命名空间中。命名空间就像Java中的包一样,将相同的类名放在不同的包中是允许的。那么在Struts 2中进行多模块开发时,如果请求的action路径相同,就根据命名空间的路径来划分,也就是说,在请求action时,路径要加上命名空间,因为命名空间不允许重复,所以即使Action路径重复也没有问题。

qiantai.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="qiantai" extends="struts-default" namespace="/qiantai">
                <action name="qiantai" class="controller.Qiantai">
                    <result name="toQiantai">/qiantai.jsp</result>
                </action>
            </package>
        </struts>

3.11.3 使用include标记导入多个配置文件

整合各子模块的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>
            <include file="caiwu.xml"></include>
            <include file="cangku.xml"></include>
            <include file="qiantai.xml"></include>
        </struts>

前3个配置文件中的代码与普通配置文件配置Action信息别无两样,但struts.xml和以前实现的示例代码完全不同。因为本示例模拟3个模块的整合,所以在struts.xml配置文件中要将这3个模块进行集中配置,主要使用的就是include标记,file属性是其他模块配置文件所在的路径。

3.11.4 创建各模块使用的JSP文件

各模块显示数据的JSP文件的代码如下。

caiwu.jsp文件代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                显示财务模块
            </body>
        </html>

cangku.jsp文件的代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                显示仓库模块
            </body>
        </html>
      qiantai.jsp文件的代码如下。
        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                显示前台模块
            </body>
        </html>

3.11.5 运行各模块的结果

程序运行结果如图3-66所示。

图3-66 多模块程序示例运行结果

在IE中打开3个窗口,分别输入各模块action的url地址,一定要把namespace命名空间的路径加进去,然后在控制台输出执行各模块功能代码的字符串。

还要执行最重要的步骤,也就是,从一个模块重定向到另外一个模块的操作,打开IE输入地址:

http://localhost:8081/Struts 2.7/caiwu/fromcaiwu_caiwu_tocangku_caiwu.action

然后按回车键,出现如图3-67所示的界面。

图3-67 从一个模块重定向到另外一个模块

3.12 在Action中有多个业务方法时的处理

3.12.1 第一种实现方式—通过url叹号“!”参数

如果一个List.java类中有很多个listXXX方法,比如,有列出员工详细信息的列表,有列出员工平均工资的列表,有列出每月为员工支出全部工资的列表等的业务方法,这些业务方法的功能非常相似:列表。所以,可以在控制层List.java类中进行处理,但在默认情况下,Action中自动调用的是一个execute()方法,那么如何才能调用其他方法呢?Struts 2支持调用指定Action类中的某一个业务方法,实现的目的与Struts 1中的DispatchAction类一致。

新建项目Struts 2.8,新建List.java的Action类。

        package controller;
        public class List {
            public String listUser() {
                System.out.println("列出员工信息");
                return "user";
            }
            public String listSalarySum() {
                System.out.println("列出员工总工资信息");
                return "sum";
            }
            public String listSalaryAvg() {
                System.out.println("列出员工平均工资信息");
                return "avg";
            }
            public String execute() {
                System.out.println("执行了execute方法");
                return "default";
            }
        }

List.java控制层类有3个自定义方法:listUser、listSalarySum和listSalaryAvg,在这3个自定义方法中分别转发不同的JSP文件,以显示相关的列表信息。还有一个默认的execute()方法。

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="Struts 2.8" extends="struts-default">
                <action name="list" class="controller.List">
                    <result name="user">/user.jsp</result>
                    <result name="sum">/sum.jsp</result>
                    <result name="avg">/avg.jsp</result>
                    <result name="default">/default.jsp</result>
                </action>
            </package>
        </struts>

配置文件struts.xml有4个result(结果)。

部署项目,运行程序。

在IE地址栏中依次输入下面的4个网址,每输入一个之后按回车键。

        http://localhost:8081/Struts 2.8/list.action
        http://localhost:8081/Struts 2.8/list!listUser.action
        http://localhost:8081/Struts 2.8/list!listSalarySum.action
        http://localhost:8081/Struts 2.8/list!listSalaryAvg.action

程序运行结果如图3-68所示。

图3-68 执行指定Action中自定义方法的结果

在Struts 2中访问Action指定的自定义方法非常简单,需要注意的仅仅是URL访问的样式,比如,下面的网址:

http://localhost:8081/Struts 2.8/list!listUser.action

其含义就是访问名为list的Action中的自定义方法listUser,中间用一个“!”叹号作为分隔符。

3.12.2 第二种实现方式—在action标记中加入method属性

新建Struts 2.8.1项目,添加Struts 2的jar包及必要的xml配置文件,新建控制层Action类List.java的代码如下。

        package controller;
        public class List {
            public String listUser() {
                System.out.println("列出员工信息");
                return "user";
            }
            public String listSalarySum() {
                System.out.println("列出员工总工资信息");
                return "sum";
            }
            public String listSalaryAvg() {
                System.out.println("列出员工平均工资信息");
                return "avg";
            }
            public String execute() {
                System.out.println("执行了execute方法");
                return "default";
            }
        }

新建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="Struts 2.8" extends="struts-default">
                <global-results>
                    <result name="user">/user.jsp</result>
                    <result name="sum">/sum.jsp</result>
                    <result name="avg">/avg.jsp</result>
                    <result name="default">/default.jsp</result>
                </global-results>
                <action name="listUser" class="controller.List"
                    method="listUser">
                </action>
                <action name="listSalarySum" class="controller.List"
                    method="listSalarySum">
                </action>
                <action name="listSalaryAvg" class="controller.List"
                    method="listSalaryAvg">
                </action>
                <action name="list" class="controller.List"></action>
            </package>
        </struts>

在本示例的配置文件中定义了全局的result转发,而在定义action标记时加入了method属性,这个属性就是执行的action里对应的方法,也就是Action控制层中的方法名。

JSP文件的代码如下。

· avg.jsp

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                平均工资列表
            </body>
        </html>

· default.jsp

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                默认的execute()语句
            </body>
        </html>

· sum.jsp

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

· user.jsp

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

在IE中依次输入从屏幕上方到下方的网址,在控制台中查看运行后的结果,如图3-69所示。

图3-69 运行结果

从结果中可以看到直接输入url后就可以运行指定action中的自定义方法了,主要的实现技术点为struts.xml文件中action节点的method属性。

3.13 自定义全局result

看下面的配置定义:

        <action name="List" class="controller.List">
            <result name="bookListJsp">/bookListJsp.jsp</result>
        </action>

Action的路径List下有一个名为bookListJsp的result对象,但这个bookListJsp对象只能在当前List的Action路径下使用,如果有其他Action也想转到这个bookListJsp.jsp页面,也必须在其他Action下注册一个result。

        <result name="bookListJsp">/bookListJsp.jsp</result>

这样造成配置的冗余。其实,完全可以配置一个全局result,使所有Action都可以访问这个result就可以了。

3.13.1 新建全局result实例和控制层代码

新建Web项目Struts 2.9。

新建两个Action,代码如下。

        package controller;
        import com.opensymphony.xwork2.Action;
        public class List1 implements Action {
            public String execute() {
                return "toOneResult";
            }
        }
        package controller;
        import com.opensymphony.xwork2.Action;
        public class List2 implements Action {
            public String execute() {
                return "toOneResult";
            }
        }

3.13.2 声明全局result对象

在struts.xml配置文件中声明全局result,配置代码如下。

        <?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="ghyStruts 1" extends="struts-default">
                <global-results>
                    <result name="toOneResult">/toOneResult.jsp</result>
                </global-results>
                <action name="list1" class="controller.List1"></action>
                <action name="list2" class="controller.List2"></action>
            </package>
        </struts>

使用global-results标记创建一个全局的result。JSP代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                转到同一个JSP文件
            </body>
        </html>

3.13.3 部署项目并运行

部署项目,启动Tomcat。在IE地址栏中输入下面的两个地址:

        http://localhost:8081/Struts 2.9/list1.action
        http://localhost:8081/Struts 2.9/list2.action

发现同时显示一个页面,如图3-70所示。

图3-70 全局result的运行结果

注意 如果局部result名称和全局名称一样,那么局部的优先级比全局的要高。

3.14 在Action中使用Servlet的API(紧耦版)

由于在Struts 2中采用的是松耦合的方式进行设计,所以在Struts 2的Action类中就可以不用Servlet的API来实现功能,但在控制层Action中想使用Servlet的API时,该如何操作呢?很简单!使用ServletActionContext类。

3.14.1 将数据放到不同的作用域中

新建Struts 2.10项目,新建控制层List.java文件,代码如下。

        package controller;
        import org.apache.Struts 2.ServletActionContext;
        public class List {
            private String usernameRequest;
            private String usernameSession;
            private String usernameApplication;
            public String getUsernameApplication() {
                return usernameApplication;
            }
            public String getUsernameRequest() {
                return usernameRequest;
            }
            public void setUsernameRequest(String usernameRequest) {
                this.usernameRequest = usernameRequest;
            }
            public String getUsernameSession() {
                return usernameSession;
            }
            public void setUsernameSession(String usernameSession) {
                this.usernameSession = usernameSession;
            }
            public void setUsernameApplication(String usernameApplication) {
                this.usernameApplication = usernameApplication;
            }
            public String execute() {
                usernameRequest = "usernameRequestValue";
                usernameSession = "usernameSessionValue";
                usernameApplication = "usernameApplicationValue";
                ServletActionContext.getRequest().setAttribute("usernameRequest",
                        usernameRequest);
                ServletActionContext.getRequest().getSession().setAttribute(
                        "usernameSession", usernameSession);
                ServletActionContext.getServletContext().setAttribute(
                        "usernameApplication", usernameApplication);
                return "default";
            }
        }

要使用Servlet的API,必须通过ServletActionContext类。

新建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="Struts 2.8" extends="struts-default">
                <action name="list" class="controller.List">
                    <result name="default">/default.jsp</result>
                </action>
            </package>
        </struts>

3.14.2 从不同作用域中取值

新建default.jsp文件,代码如下。

        <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
        <%@ page isELIgnored="false"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
            <body>
                request:${requestScope.usernameRequest}
                <br />
                session:${sessionScope.usernameSession}
                <br />
                application:${applicationScope.usernameApplication}
                <br />
            </body>
        </html>

在IE中的运行结果如图3-71所示。

图3-71 运行结果

在IE中输入action的路径后,JSP显示出在不同作用域中的值。

3.15 在Action中使用Servlet的API(松耦版)

本节就使用ActionContext类来对request、session和application对象进行操作。

新建Web项目Struts 2.11。添加必要的Struts 2支持环境和jar文件。

3.15.1 新建控制层

新建控制层ShowScopeValue.java文件,代码如下。

        package controller;
        import java.util.Map;
        import com.opensymphony.xwork2.ActionContext;
        public class ShowScopeValue {
            public String execute() {
                // 第一种方法往request对象中放数据
                Map request = (Map) ActionContext.getContext().get("request");
                request.put("requestValue", "this is reqeustValue");
                // 第二种方法往request对象中放数据
                ActionContext.getContext().put("otherrequest",
                        "this is otherrequest value");
                Map session = (Map) ActionContext.getContext().getSession();
                session.put("sessionValue", "this is sessionValue");
                Map application = (Map) ActionContext.getContext().getApplication();
                application.put("applicationValue", "this is applicationValue");
                return "showscopevalue";
            }
        }

在程序中使用如下代码。

        (Map) ActionContext.getContext().get("request");
        ActionContext.getContext().put("otherrequest","this is otherrequest value");
        (Map) ActionContext.getContext().getSession();
        (Map) ActionContext.getContext().getApplication();

分别获取request、session和application,由于Struts 2没有类似getRequest()这样的方法,因此必须使用“(Map) ActionContext.getContext().get("request");”这样的代码获得request对象,也可以使用“ActionContext.getContext().put("otherrequest","this is otherrequest value");”这样的代码直接往request对象中存放数据。

3.15.2 新建JSP视图

新建showscopevalue.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>
                request1:${requestValue}
                <br />
                request1:${otherrequest}
                <br />
                session:${sessionValue}
                <br />
                application:${applicationValue}
            </body>
        </html>

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="strutsTest" extends="struts-default">
                <action name="showscopevalue"
                    class="controller.ShowScopeValue">
                    <result name="showscopevalue">/showscopevalue.jsp</result>
                </action>
            </package>
        </struts>

程序运行结果如图3-72所示。

图3-72 运行结果

3.16 Session与Cookie在request与response对象中的运行机制

Session的运行机制主要通过Cookie对象,当IE访问服务器端时,在服务器端生成1个Map对象,此Map的key就是一个唯一的sessionId,处理结束后以response响应对象的方式进行返回。其中返回的数据就带有符合HTTP协议的Cookie数据,IE接收到response响应返回来的数据,IE再根据数据中的格式就能识别出在客户端欲创建一个Cookie的要求。所以,在客户端就创建了一个Cookie,Cookie中的值就是刚刚在服务器端生成的那个唯一值sessionId,这是生成Cookie阶段。

IE再次(也就是第2次)访问服务器端,在request请求中携带着刚刚创建的Cookie,Cookie中的值是sessionId,到达服务器端时,根据Cookie中的sessionId到Map中进行匹配。如果找到,则从Map中取出值并进行后期的处理,这是Cookie在服务器端匹配的阶段,也是为什么能找到自己数据的原因。

下面用示例的方式来展示上述文字描述的执行过程。

为了了解HTTP请求与响应的过程,需要捕获HTTP协议执行的过程,也就是抓包,所以需要安装fiddler软件,此软件就能监控HTTP执行的流程。

安装成功后,进入默认安装文件夹C:\Program Files\Fiddler2找到文件,双击启动fiddler软件,软件界面如图3-73所示。

图3-73 fiddler软件运行界面

下一步创建一个Web项目,再创建一个Servlet对象,核心代码如下。

        public class test extends HttpServlet {
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                System.out.println("testServlet");
            }
        }

将此项目部署到Tocmat中,并在浏览器地址栏输入如下URL。

http://localhost.:8081/test1/test

注意,在IE8中,网址localhost后面一定要带着一个小数点,不然fiddler软件无法捕获到HTTP请求。

URL执行后,fiddler成功捕获,显示的界面如图3-74所示。

图3-74 捕获的数据1

标签页的子标签中显示出request请求头的内容,从显示出来的信息来看,并没有Cookie对象发送给服务器端,内容如图3-75所示。

图3-75 并没有Cookie发送给服务器

而服务器响应response对象返回的数据也没有Cookie对象,内容如图3-76所示。

图3-76 服务器没有返回Cookie对象

此过程并没有产生任何Cookie对象,而且多次刷新IE界面也没有捕获到Cookie对象的传输过程。

按照以下代码更改Servlet代码。

        public class test extends HttpServlet {
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                System.out.println("testServlet");
                request.getSession().setAttribute("username", "usernameValue");
            }
        }

重启Tomcat,刷新IE界面,fiddler捕获到的HTTP访问的内容如图3-77所示。

从图3-77中可以看到,request并没有传输Cookie对象给服务器端,但在response响应界面中可以看到,服务器端返回了一个Cookie对象,如图3-78所示。

图3-77 从服务器端返回Cookie对象

图3-78 返回的Cookie对象

Cookie中的内容为JSESSIONID=1B697CA0D9610C6E8A109E7E1D7AE831; Path=/test1。

记住sessionId的后3位值831,这时IE中已经有一个Cookie对象。

再次刷新IE,fiddler再次捕获HTTP请求,内容如图3-79所示。

图3-79 发送到服务器的Cookie对象

从图3-79中可以看到,IE把Cookie用request对象传递给服务器端,Cookie的值正是831,通过此值Tomcat就可以在Map中根据831找到对应的value,以进行后面的业务处理。

图3-79演示的是Cookie与Session之间的关系。用户在Session中找到自己数据的原理是根据Cookie,那如果Cookie在IE中被屏蔽了该怎么办呢?屏蔽操作如图3-80所示。

图3-80 屏蔽Cookie

Servlet的代码如下。

        public class test extends HttpServlet {
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                if (request.getSession().getAttribute("username") == null) {
                    System.out.println("第一次访问!");
                    request.getSession().setAttribute("username", "accp10");
                    String testPath = "test";
                    System.out.println(response.encodeURL(testPath));
                } else {
                    System.out.println("不是第一次访问!");
                }
            }
        }

多次执行Servlet在控制台上输出的信息如图3-81所示。

从图3-81中来看,创建了多个Session,因为IE在本地将Cookie屏蔽了,没有任何Cookie对象由request请求发送给服务器,所以服务器每一次的request请求都产生新的Session对象。

图3-81 新的Cookie

但想要重获HttpSession中的数据,可以使用URL重写的方式,将控制台中的路径进行复制,然后,在IE上地址栏中输入以下URL:

http://localhost.:8081/sessionURL/test;jsessionid=72AD079999ED69FC7D7332FF4BA67B7A

控制台输出的信息如图3-82所示。

图3-82 URL重写访问Session中的数据

通过此实验可以证明,要找到自己或别人的Session中的数据,可以通过URL重写的方式来进行,这样有损软件的安全性。

3.17 在MyEclipse中使用Web Service

软件公司中的业务非常复杂,所以需要用不同的语言来进行处理,但这些不同语言的通信成了问题。绝不能做的是:不能告诉对方安全性非常高的数据库IP、用户名及密码。这可怎么办?这些语言的编译器又不一样,数据类型又有差异,所有这些原因都可以使用WebService来进行解决。

WebService是一种基于HTTP协议的交互数据技术,使用的是SOAP协议,实现协议的语言叫做wsdl,就像HTTP协议使用HTML语言一样。其实wsdl就是一种XML格式的语言,主流的编程语言都支持XML的解析,所以用wsdl来作为异构平台的规范,就可以实现不同平台之间的数据交互了。

创建Web项目,名称为webserviceServer,创建实体类Userinfo.java的结构,如图3-83所示。

图3-83 创建Userinfo实体类

注意,一定要为Userinfo.java实体添加无参构造函数。

创建WebService要使用业务逻辑层UserinfoService.java,其代码如下。

        package webservice.server;
        import java.util.ArrayList;
        import java.util.List;
        import entity.Userinfo;
        public class UserinfoService {
            public List<Userinfo> listUserinfo() {
                ArrayList listUserinfo = new ArrayList();
                for (int i = 0; i < 10; i++) {
                    listUserinfo.add(new Userinfo("" + (i + 1), "password" + (i + 1)));
                }
                return listUserinfo;
            }
        }

下面的步骤要把业务逻辑层UserinfoService.java转换成WebService。

单击如图3-84所示的New Web Service菜单创建WebService的接口。

图3-84 创建WebService的接口

弹出如图3-85所示的界面。

图3-85 WebService来自于一个普通的Java类

单击Next按钮继续配置,输入要转换成Web Service的业务层,如图3-86所示。

图3-86 使用普通的Java类转换成Web Service

设置完毕后直接单击Finish按钮完成配置,将项目部署到Tomcat中,启动时出现如下异常。

        java.lang.ClassNotFoundException: com.sun.xml.ws.transport.http.servlet.WSServlet
        ContextListener

在Oracle.com网站下载JAXWS2.2.7-20120813.zip,解压其中的jar,放入Web项目中,再重启Tomcat,不再出现异常。

打开IE输入以下网址,查看Web Service服务器端提供的WSDL文件。

http://localhost:8081/webserviceServer/UserinfoServicePort

打开网址后弹出的界面如图3-87所示。

图3-87 Web Service服务器端的配置信息

单击其中的链接http://localhost:8081/webserviceServer/UserinfoServicePort?wsdl,打开的界面如图3-88所示。

从图3-88中可以看到,wsdl文件其实就是一个XML配置文件,所以不同的语言都可以解析XML文件,再将解析出来的数据结合反射技术,就可以实现不同语言之间彼此通信的功能。

图3-88 文件wsdl的内容

到此Web Service服务器端创建完毕,继续创建客户端。

创建名为webserviceClient的Web项目,配置WebService的客户端环境,如图3-89所示。

图3-89 添加Web Service客户端支持环境

弹出如图3-90所示的界面,保持默认配置即可。

图3-90 使用JAX-WS框架

单击Next按钮继续配置,配置界面如图3-91所示。

图3-91 配置wsdl的URL路径及逆向客户端的包名

配置完成后,继续单击Next按钮继续配置,弹出验证wsdl的界面,如图3-92所示。

图3-92 解析wsdl文件

单击Next按钮继续配置,出现如图3-93所示的界面。

图3-93 添加客户端Web Service支持框架的jar文件

单击Finish按钮完成Web Service客户端的创建工作,逆向结束后,客户端项目的结构如图3-94所示。

图3-94 客户端的项目结构

创建名为teset的Servlet,核心代码如下。

        public class test extends HttpServlet {
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                UserinfoServiceService service = new UserinfoServiceService();
                List<Userinfo> listUserinfo = service.getUserinfoServicePort()
                        .listUserinfo();
                for (int i = 0; i < listUserinfo.size(); i++) {
                    Userinfo userinfo = listUserinfo.get(i);
                    System.out.println(userinfo.getId() + " " + userinfo.getPassword());
                }
            }
        }

将客户端项目webserviceClient部署到Tomcat中,在浏览器地址栏中输入以下URL:

http://localhost:8081/webserviceClient/test

在控制台输出的信息如图3-95所示。

图3-95 成功调用服务器端的Web Service

开发工具MyEclipse提供了最快捷的方式以开发Web Service。