接着Struts2_3_day的讲
注:使用Struts2的< s:debug>< /s:debug>就可获取数据储存的分布图
StrutsPrepareAndExecuteFilter都会创建一个ActionContext和ValueStack对象, 所以Struts2的数据存储分为两类: ActionMap(contextMap)以及ValueStack; ActionMap中都是以map的形式存取数据, 而ValueStack是以Stack的方式存取数据(底层就是List)
ActionMap数据操作
- 利用ActionMap存储数据
ActionMap是一个大的map, 而这个大map中又存有application, session, request, parameters, attr的小map, 所以在ActionMap中存放数据的时候既能存入大map中, 也能存入小map中, 在小map中还能存入map, 形成多层的存储, 如下代码操作:
1.直接在ActionMap中存入数据, 使用ActionMap的静态方法getContext获取ActionMap对象, 然后直接put
ActionMap context = ActionMap.getContext();
context.put("contextMap", "contextMap");
2.往ActionMap中的session对象中存入数据
使用ActionMap中的session, session也是一个map
Map<String, Object> sessionAttribute = context.getSession();
sessionAttribute.put("sessionMap1", "sessionMap1");
使用原始HttpSession对象存储数据
HttpSession session = ServletActionContext.getRequest().getSession();
session.setAttribute("sessionMap2", "sessionMap2");
3.往ActionMap的application中存入数据
使用使用ActionMap中的ServletContext域
Map<String, Object> applicationAttribute = context.getApplication();
application.put("applicationMap1", "applicationMap1");
使用原始ServletContext对象
ServletContext application = ServletActionContext.getServletContext();
application.setAttribute("applicationMap2", "applicationMap2");
注: 无论使用ServletActionContext中的存储方式, 还是使用ActionMap.getContext().getXXX()的方式, 他们所指向的存储区域都是一样的, 例如上面的applicationMap1和applicationMap2, 他们二者的数据都是存放在同一片储存区域的, 同理sessionMap1与sessionMap2也是一样的
- 获取ActionMap中数据
可以直接使用Map的get, 或者使用域对象的getAttribute方法来获取ActionMap中数据, 但是大多数使用OGNL表达式在jsp中获取ActionMap数据, 例如:
// 使用OGNL表达式获取域对象数据, OGNL表达式必须写在Struts2的标签中
<%@ taglib uri="/struts-tags" prefix="s" %>
<s:property value="#session.sessionMap1">
//在使用OGNL表达式取用ActionMap的数据的时候, 使用"#"
//借用上面代码的存储方式, 因为sessionMap1,以及sessionMap2是存入session中, 所以直接使用#session.sessionMap1
//数据的查找顺序是: 先去ActionMap中查找session区域, 找到之后在session区域中查找是否具有key为sessionMap1的数据
//同理, 当查找applicationMap1也是直接使用:<s:property value="#application.applicationMap1">
ValueStack数据操作
- 利用ValueStack存储数据
正如前面所说的, ValueStack就是一个Stack结构, 当我们向ValueStack中存入数据的时候就是进行入栈, 出栈的操作
下面先介绍获取ValueStack对象引用的3种常见方式:
1.使用<s:debug></s:debug>之后发现, 在request中存有ValueStack的引用, 所以可以使用get的方式获取:
ActionContext context = ActionContext.getContext();
Map<String, Object> request = (Map<String, Object>) context.get("request");
ValueStack vs = (ValueStack)request.get("struts.valueStack");
2.使用ServletActionContext获取request对象, 然后再通过getAttribute获取:
ServletRequest request = ServletActionContext.getRequest();
ValueStack vs = (ValueStack)request.getAttribute("struts.valueStack");
3.直接使用ActionContext对象的getValueStack方法获取:
ValueStack vs = ActionContext.getContext().getValueStack();
注: 通过上面的3种方式获取的vs都是同一个对象
当我们获取到ValueStack对象之后就可以对ValueStack对象做push, peek/pop操作以此达到存储数据的目的, 例如:
ValueStack vs = ActionContext.getContext().getValueStack();
vs.push(new Student("A", 18)); //对一个Student对象做入栈操作
注: 默认动作类对象会自动存入ValueStack中, 因此可以通过OGNL表达式获取默认动作对象的属性方法(对该属性一定要写set/get方法)
- 获取ValueStack中数据
当使用动作类进行ValueStack存储数据的时候, 一定要记住: 如果直接想通过OGNL表达式获取, 此时数据只具有request的作用域范围, 当使用页面重定向后, 就不能访问到刚才动作方法中存入ValueStack中的数据
1.可以使用pop/peek直接获取ValueStack数据或者jsp中使用标签的方式获取ValueStack数据;
2.jsp中操作: 使用< s:property value="name" />这里的name是一个OGNL表达式, 用于获取Student中的name属性;
3.与获取ActionContext数据不同的是, value后面的参数少了一个"#", **"#"是作为获取ValueStack与ActionMap中数据的一种区别**;
4.在ValueStack中, 查找方式是: 在Stack中依次遍历每个对象, 查看当前对象的属性是否是value后面的参数值,
如果是就代表找到, 就不再往后查找. 例如: 当动作类中也具有一个name属性, 但是现在栈顶是一个Student对象, 当找到Student对象中的name属性的时候就不再往后找, 即使动作类中也具有name;
5.在OGNL表达式中可以多级访问: 比如ValueStack中存入的数据是一个Map对象map, Map对象中存有一个Student对象s1, 当我们想要获取此时Student对象的name属性的时候, 就可以写:< s:property value="s1.name" />
- ValueStack的set与setValue方法的区别
set方法:
ValueStack vs = ActionContext.getContext().getValueStack();
vs.push(new Student("A", 19));
vs.set("s1", new Student("B", 20));
如上述代码, 当vs使用set操作的时候, 操作的元素是ValueStack中的元素.
如果栈顶是一个Map, 就直接将一个key为s1, value为Student对象存入Map中
如果栈顶不是一个Map, 则ValueStack新建Map<String, Object>对象, 然后将key为s1, value为Student对象存入Map中
当连续多次vs.set()的时候, 所有的set的对象都是存入一个Map对象中, 例如:
vs.set("s1", new Student("A", 20));
vs.set("s2", new Student("B", 10));
vs.set("s3", new Student("C", 24));
当使用OGNL表达式访问栈中map中的元素, 采用: <s:property value="s1.name"/>, 而不是<s:property value="#s1.name">, 这里是在ValueStack中取元素, 而不是ActionMap
在获取元素的时候, 元素对象一定要写set/get方法
这里别理解为连续set是建立多个map, 只有在下面这种情况下, 才是建立多个Map
vs.set("s1", new Student("A", 20)); //新建一个map
vs.push(new Student("C", 24));
vs.set("s2", new Student("B", 10)); //又新建一个map
setValue方法:
setValue对ValueStack中存储元素值的修改操作:
vs.setValue("name", "X");
将栈中第一个name元素修改为X, 借用上面代码, 此处的修改是将"C"变为"X"
setValue的第一个参数是一个OGNL表达式
setValue对ActionMap中存储元素值的修改操作:
vs.setValue("#name", "X");
这里修改的name属性, 是对ActionMap中key为name的value进行修改
- ValueStack的findValue
在JSP上使用OGNL表达式获取ActionMap或者ValueStack中的内容的时候, 都是执行如下操作
vs.findValue("name"); //在ValueStack中查找名为name属性的值
vs.findValue("#name"); //在ActionMap中查找key为name的value值
Struts中的EL
JSP中的EL表达式获取的是请求域中对象(page -> request -> session -> application)
Struts2的StrutsRequestWrapper对request重新做了封装, 对getAttribute进行改写, 当获取完request域中对象后, 会先去ValueStack, 然后是ActionMap中寻找需要的对象
Struts2对EL表达式进行了改写使得获取对象变为: page -> request -> ValueStack -> ActionMap -> session -> application
注: 当使用OGNL表达式查找属性值的时候, 如果写做< s:property value=“name” />, 它会先去ValueStack中查找, 当没找到的时候再去ActionMap中, name作为key找一遍.
Struts2中#, $, %的使用
- #
OGNL表达中
获取ActionMap中的Key为key的value值:
<s:property value="#name"> //获取Key为name的value值
创建Map对象的时候, 主要用于CheckBox, radio的多条件选择:
<s:radio list="#{ 'male':'男', 'female':'女' }">
- $
JSP中使用EL表达式:
${name}
在xml中配置文件名, 或者使用静态函数(避免在JSP中写<%%>代码)
${@java.lang.Math.random()}
- %
强制将一个字符串看做是OGNL:
<s:property value="%{name}">
Struts2中常用标签(使用OGNL配合使用)
- iterator标签
主要用于迭代遍历元素, 类似于JSP中的foreach标签, 下面举例说明:
Action类:
//使用list模拟从数据库提取Student数据, 显示在结果视图中
public class DemoAction extends ActionSupport{
private List<Student> students;
public String findAll(){ //action方法
students = new ArrayList<Student>(3);
students.add(new Student("A", 20));
students.add(new Student("B", 30));
students.add(new Student("C", 10));
return SUCCESS;
}
public List<Student> getStudents(){
return students;
}
public void setStudents(List<Student> students){
this.students = students;
}
}
//JSP中iterator操作
<s:iterator value="students" var="s" status="vs">
<tr>
<td><s:property value="#vs.index"/><br/></td>
<td><s:property value="#s.name"/><br/></td>
<td><s:property value="#s.age"/><br/></td>
</tr>
</s:iterator>
注:
1.在遍历的过程中具有两种操作用于暂时存储需要遍历的变量(方便获取该变量的内部属性):
a.使用ActionMap: 当指定在iterator中指定var属性的时候,Struts2会将var作为key, 当前变量作为value存入ActionMap中, 便于后面通过OGNL表达式获取该变量的内部属性, 因为key是固定的, 所以下一个元素存入ActionMap中会将上一个元素的value覆盖, 这样做也是为了减少空间占用,遍历结束后会将最后存在ActionMap中的变量remove
b.使用ValueStack: 如果没有指定var, 那么Struts2会直接将该变量压入栈中, 当使用完该变量进行pop操作, 原因同上
2.status主要是用来记录遍历的数据属性(以status作为key, 数据属性作为value存入ActionMap中):
status具有index, count, odd, even, first, last属性, 表示下标, 遍历的数据和, 奇数位, 事件, 第一个值, 最后一个值, 具体用法自行百度
3.在上面例子中, 可以使用value="name"的OGNL表达式来输出变量属性, 也可以使用EL表达式${name}来输出变量属性
- set标签
与JSP中的set标签一样, 就是声明一个变量, 然后对该变量赋值, 设置访问范围
<s:set value="a" var="1111" scope="session"/>
<s:property value="#1111"/>
声明一个变量"1111", 赋值为"a"的字符串, 设定访问范围为session
set设置的变量是存入Map中的, value代表属性值, var代表Key值, scope取值范围application, session, request, page, action, 当scope不写的时候默认是action, ActionMap与request中各存一份
-
action标签
在JSP中调用action方法: < s:action name=“action1” executeResult=“true”/> ,executeResult表示是否显示执行动作结果, 默认值false -
url和a标签
url标签主要用于获取url地址
a标签用于向一个url地址进行跳转, 就< a href="…"/ >
例:
<s:url action="action1" value="action1" var="url">
<s:param name="username" value=" 'test' "></s:param>
</s:url>
action: 获取值为action1的动作请求地址, 类似于EL表达式: ${pageContext.request.contextPath}/action1.action
value: url标签中的value用于输出value的值, 此时的value后面的参数不是OGNL表达式, 只是一个普通字符串; 而param中的value代表get方式添加参数, 会自动编码 如:username?test
var: 将值为url作为key存入ActionMap中
使用上面的url
<a href="<s:property value='#url'>">点击</a>
当使用<s:a>标签的时候就类似于使用<s:url>标签与<a>标签的结合品
<s:a action="action1">
<s:param name="username" value=" 'test' ">
</s:a>
- if/else标签:
if/else标签的用法与JSP中标签的用法一致
<s:if test=" name=='A' ">A</s:if>
<s:elseif test=" name=='B' ">B</s:if>
<s:else>C
防止表单数据重复提交
Servlet中的解决方式:
- 用户第一次提交表单数据之前先将数据存入Session域中, 当用户数据提交之后就删除Session数据, 当用户重复提交的时候会发现表单中数据与Session中数据无法做比较, 最后判定数据重复提交, 对后面提交的数据不作处理
Struts2中的解决方式
- 使用token/tokenSession拦截器(Struts2提供的一个拦截器)
使用token/tokenSession拦截器的时候, 会产生一个令牌隐藏在Session域中, 当用户提交数据后令牌消失, 下一次提交找不到对应的令牌, 所以最后判定为用户数据重复提交
如上面看到的, Struts2中防止重复提交与Servlet中防止重复提交的思想是一致的
此处说明一个问题就是:
token/tokenSession不在Struts2的defaultStack中, 在使用的时候需要配置defaultStack拦截器
token与tokenSession的区别:
token做拦截的时候, 当用户提交后有一个页面跳转, 有一个错误反馈, 告诉用户数据重复提交
tokenSession做拦截的时候, 不去做页面跳转和错误反馈, 无论用户提交多少次表单数据, tokenSession只取第一次提交的数据
下面使用token举例说明(使用tokenSession的时候, 只需将token改为tokenSession):
index.jsp:
<s:form action="save">
<s:token></s:token> <%此标签用于生成隐藏令牌%>
<s:textfield name="name" label="用户名"></s:textfield>
<s:submit value="保存"></s:submit>
</s:form>
Struts.xml:
<struts>
<constant name="struts.devMode" value="true"></constant>
<package name="p1" extends="struts-default">
<action name="save" class="com.action.DemoAction">
<interceptor-ref name="defaultStack"></interceptor-ref>
<!--配置token拦截器-->
<interceptor-ref name="token"></interceptor-ref>
<result type="redirect">/success.jsp</result>
<!--使用token的时候必须做invalid.token跳转, 跳转到error.jsp中-->
<!--在使用tokenSession的时候不需要做invalid处理-->
<result name="invalid.token">/error.jsp</result>
</action>
</package>
</struts>