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。