值栈和ognl表达式
1.只要是一个MVC框架,必须解决数据的存和取的问题
2.struts2利用值栈来存数据,所以值栈是一个存储数据的内存结构
1、 ValueStack是一个接口,在struts2中使用OGNL表达式实际上是使用实现了ValueStack接口的类 OgnlValueStack,这个类是OgnlValueStack的基础。
2、 ValueStack贯穿整个action的生命周期。每一个action实例都拥有一个ValueStack对象。其中保 存了当前action对象和其他相关对象。
3、 Struts2把ValueStack对象保存中名为struts.valueStack的request域中。
3.把数据存放在值栈中,在页面上利用ognl表达式显示出来
4.ognl框架对象导航语言
5.ognl类图:
值栈的生命周期
1.一次请求;
值栈的内存结构
1.获取值栈路径:
2.说明:
1、 值是一样的,说明只有一个对象
2、 因为有一种是从request域中获取的,所以是一次请求
3内存结构:
1.大致图:
2.详细图:
说明:
_root:(CompoundRoot) _values:(HashMap)这里存放request、response、session、application等servlet容器
2._root图的放大:
说明:和ValueStack中的root是一致的
值栈的内存总图:
说明:从上图中可以看出valueStack总共分为两个部分: 对象栈:root Map栈:_values
1.1.1 OgnlContext组织结构
1.1.1.1 _values
从上述可以看出,OgnlContext实际上有一部分功能是Map。所以可以看出_values就是一个Map属性。而运行一下下面的代码就可以看到:
|
在_values的map中:
key |
value |
application |
ApplicationMap |
request |
RequestMap |
action |
自己写的action |
com.opensymphony.xwork2.ActionContext.session |
SessionMap |
而request中的值为:
|
可以看出在程序中存放在request作用域的值被放入到了_values的request域中。
而com.opensymphony.xwork2.ActionContext.session的值为:
|
从上图中可以看出在程序中被加入到session的值在_values中也体现出来。
1.1.1.2 _root
从图中可以看出_root实际上CompoundRoot类,从类的组织结构图中可以看出,这个类实际上是继承了ArrayList类,也就是说这个类具有集合的功能。而且在默认情况下,集合类的第一个为ValueStackAction,也就是我们自己写的action。
存放数据到值栈
1.1对象栈的存放:
方法1:
Push方法:
方法二:
add方法:
1.2.对象栈的提取(栈顶):
从上图可以看出,peek方法为获取对象栈的栈顶元素;
这行代码也可以获取对象栈的栈顶元素;
1.3.对象栈元素的弹出:
1.4.操作对象栈中的对象:
说明:可以利用valueStack的setParameter方法改变对象栈中的属性值;
关于对象栈:
从图中可以看出来,对象栈的属性来自于对象栈中的每一个对象的属性的get方法;
把一个对象放进对象栈中:
在<s:dubug></s:dubug>中:
如果在对象栈中存在两个相同的属性:案例:两个name
上述的两个类中都同时有name属性,而且当前请求的action在对象栈中
利用该方法就把person对象放入到栈顶了
可以利用上述的方法改变对象栈中的name属性,但是从上往下找,只要找到一个,改变了值就完事了。
2.Map栈的存放:
改方法将一个字符串放到map栈中的request域中;
通过该方法可以把一个字符串放入到map栈中的application域中
通过该方法可以把一个字符串放入到map栈中
Threadlocal
1.本地线程类;
2.可以存放一个对象;
3.Threadlocal对象只能当前线程访问,不能被其他线程访问;
4.传递参数;
案例:
说明:通过参数的传递,一个字符串很容易的从客户端传递到tl1中的tl1方法,再传递到tl2中的tl2方法
2.
说明:
在TL1Super中用到一个字符串
在TL2Super中用到同样的字符串,但是这两个类是完全松耦合,这个时候要用
Threadlocal传递数据,因为不管是否是送耦合的,但是可以肯定在同一个线程中。所以可以得到同一个线程threadlocal对象中的数据
显示值栈中的数据
6.1:Ognl标签:
1.导入标签库:
2.标签库位置:
3.action类:
4.作用:可以通过网页的形式把valueStack中值提取出来;
6.1.2:Property标签:输出标签
属性分析:value: 指定要输出的内容;如果在对象栈中直接指定属性也可以直接调用方法(该对象的方法);如果在Map栈中,用 # 指定,如果value属性不写,则默认输出栈顶的元素;
一:访问对象栈中的元素:
页面上:
把一个对象放入到对象栈中,可以利用s:prototype标签的value属性直接访问
该对象的属性。如果对象栈中出现两个相同的属性,则prototype标签的value属性会
从栈顶往下查找,直到找到为止。赋值的为第一个。
把一个对象放入到map中,再把map放入到对象栈中,在页面上可以利用
二:利用该标签访问Map栈中的元素:
注意:加 # 号访问
Map栈元素的访问:
6.1.3:Iterator标签:迭代器标签
1.用途:主要用于迭代,可以迭代List,Set,Map,Array
2.案例:
1.把集合放到对象栈中:
开始迭代:
2.把List放到map中;
开始迭代:
3.把List放到request域中:
把Map放入到Map栈中:
开始迭代:
5、把List<Map<String,Person>>放入到map栈中
标签属性:
Value:
可选属性,指定被迭代的集合。如果没有设置该属性,则使用对象栈顶的集合。
Var:
可选属性,引用变量的名称
Status:
可选属性,该属性指定迭代时的IteratorStatus实例。该实例包含如下的方法:
int getCount() 返回当前迭代的元素个数 int getIndex() 返回当前迭代元素的索引 boolean isEven() 返回当前迭代元素的索引是否是偶数 boolean isOdd() 返回当前迭代元素的索引是否是奇数 boolean isFirst() 返回当前迭代元素是否为第一个元素 boolean isLast() 返回当前迭代元素是否为最后一个元素
案例:
更多参考api
总结:
1、 如果要迭代一个集合,当前迭代的元素在栈顶
2、 如果要迭代一个map,则当前迭代的元素是entry
3、 Iterator标签中有一个属性status
6.1.4:UI标签:
1、 struts2提供了一套标签机制,由struts2框架把struts2的标签解析成html标签。
2、 在struts2的配置文件中,增加
在struts2的配置文件中加入如下的内容:
由struts2容器反应过来的HTML代码和struts2标签能保持一致;
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head> <body>
<s:form action="">
<table>
<tr>
<td>用户名</td>
<!--
name为翻译成html的name
cssClass为html的class
-->
<td><s:textfield name="username" cssClass=""></s:textfield></td>
</tr>
<tr>
<td>密码</td>
<td><s:password name="password"/></td>
</tr>
<tr>
<!--
select标签
list 是下拉列表框的数据源
value是默认被选中的值
-->
<td>选择省</td>
<td><s:select list="{'山西省','河南省','四川省'}" value="{'山西省'}"></s:select></td>
<td><s:select list="#{1:'山西省',2:'河南省',3:'四川省'}" value="#{2:'河南省'}"></s:select></td>
<td><s:select list="#provinces" listKey="pid" listValue="name"/></td>
<!--
listKey 为option元素的value属性的值
listValue option元素的文本内容
name
该属性的值用来获取选中的元素
用于回显
--> </tr>
<tr>
<td>选择爱好</td>
<td><s:checkboxlist list="{'足球','篮球','羽毛球'}" value="{'足球'}" name="pid"/></td>
<td><s:checkboxlist list="#{1:'足球',2:'篮球',3:'羽毛球'}" value="#{1:'足球'}" name="pid"/></td>
<!--
list代表数据源
listKey为value值
listValue为显示的值
-->
<td><s:checkboxlist list="#hobbies" listKey="hid" listValue="name" name="hid"/></td>
</tr>
<tr>
<td></td>
<td><s:submit/></td>
</tr>
</table>
</s:form>
</body>
</html>
UI.jsp
6.1.4.2、Select标签:
6.1.4.2、Checkbox标签:
1.1 Form标签
1.1.1 说明
1、 id属性为s:form的唯一标识。可以用document.getElementById()获取。
2、 name属性为s:form的名字,可以用document.getElementsById()获取。
3、 在默认情况下,s:form将显示表格的形式。
1.2 Textfield标签
1.2.1 说明
实际上相当于在表格中多了一行,在这行中多了两列。其变化从上述图中可以很明显的看出。
1.3 Password标签
1.3.1 说明
如果不加showPassword属性,则密码不会显示,把showPassword属性的值设置为true,就能显示密码。
1.4 Hidden标签
1.4.1 说明
Hidden标签并没有加tr和td
1.5 Submit标签
1.5.1 情况一
1.5.2 说明一
这种情况为submit的type属性为submit类型。
1.5.3 情况二:
1.5.4 说明二
这种情况submit的type属性为button.
1.5.5 情况三
1.5.6 说明三
该type类型为image。
1.5.7 综合
以上三种情况说明,当type为submit、button、image都能完成提交。
1.6 Reset标签
1.7 Textarea标签
1.8 Checkbox标签
1.9 Checkboxlist标签
1.9.1 集合为list
1.9.2 集合为map
上述的checkboxlist标签也可以表示为:
listKey相当于<input type=”checkbox”>中的value,listValue相当于label的显示值。
回显:
一. 概念:
在数据提交出现错误的时候, 已填写的信息仍在文本框中, 比如用户登录, 当用户输入错误的密码之后, 用户名仍在文本框, 只是密码框清空
二. 意义:
对于一些要填写很多信息的表单, 如果因为一些错误导致已经填写的整个表单信息重新填写, 对于用户非常地不友好
三. 回显方法:
1. 默认情况:根据属性进行回显
注意:如果把要回显的数据放入到map栈中、则必须根据value进行回显:Value="%{ognl表达式}"
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head> <body>
<s:form action="">
<table>
<tr>
<td>用户名</td>
<!--
s:select s:checkboxlist
这些标签中的list属性的值可以直接跟ognl表达式
一般情况下,表单中的元素的value属性都不能直接跟ognl表达式
可以使用%{ognl表达式}
如果要回显的内容在map栈中,不能根据name属性进行回显,必须用value属性进行回显
<td><s:textfield name="username" value="%{#user.username}"></s:textfield></td>
如果把要回显的对象放入到对象栈中,则页面上可以根据name进行回显
<s:textfield name="username"></s:textfield>
一般情况下,如果要回显一个对象,则把该对象放入到对象栈中
-->
<td><s:textfield name="username"></s:textfield></td>
</tr>
<tr>
<td>爱好</td>
<!--
用name进行回显
-->
<td><s:checkboxlist list="#hobbies" listKey="hid" listValue="name" name="hids"/></td>
</tr>
</table>
</s:form>
</body>
</html>
backview.jsp
1.回显Map栈中数据:
一般情况下,应该把回显的数据放入到对象栈中,因为页面上可以直接根据name进行回显
2. 回显对象栈中数据:
3.回显checkbox:
上述图表示要回显的id值
根据name属性进行回显
代码贴上:
package com.itheima09.struts.action; import java.util.ArrayList;
import java.util.List; import com.itheima09.struts.bean.Hobby;
import com.itheima09.struts.bean.Province;
import com.itheima09.struts.bean.User;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport; public class UIAction extends ActionSupport{
private Long[] hids; public Long[] getHids() {
return hids;
} public void setHids(Long[] hids) {
this.hids = hids;
} private Long pid; public Long getPid() {
return pid;
} public void setPid(Long pid) {
this.pid = pid;
} public String testSelect(){
List<Province> provinces = new ArrayList<Province>();
Province province1 = new Province();
province1.setPid(1L);
province1.setName("山西省");
Province province2 = new Province();
province2.setPid(2L);
province2.setName("河南省"); Province province3 = new Province();
province3.setPid(3L);
province3.setName("四川省"); provinces.add(province1);
provinces.add(province2);
provinces.add(province3); this.pid = 3L; ActionContext.getContext().put("provinces", provinces);
return "ui";
} public String testCheckbox(){
List<Hobby> hobbies = new ArrayList<Hobby>();
Hobby hobby1 = new Hobby();
hobby1.setHid(1L);
hobby1.setName("足球");
Hobby hobby2 = new Hobby();
hobby2.setHid(2L);
hobby2.setName("蓝球"); Hobby hobby3 = new Hobby();
hobby3.setHid(3L);
hobby3.setName("荷兰球"); hobbies.add(hobby1);
hobbies.add(hobby2);
hobbies.add(hobby3); ActionContext.getContext().put("hobbies", hobbies); return "ui";
} /**
* 回显用户名
*/
public String testBackViewUsername(){
User user = new User();
user.setUsername("王二麻子");
user.setPassword("123456");
//ActionContext.getContext().put("user", user);
//ActionContext.getContext().put("username", "aaa");
ActionContext.getContext().getValueStack().push(user);
return "backview";
} /**
* 回显爱好中的值
*/
public String testBackViewHobby(){
/**
* 在页面上把checkbox的内容显示出来
*/
List<Hobby> hobbies = new ArrayList<Hobby>();
Hobby hobby1 = new Hobby();
hobby1.setHid(1L);
hobby1.setName("足球");
Hobby hobby2 = new Hobby();
hobby2.setHid(2L);
hobby2.setName("蓝球");
Hobby hobby3 = new Hobby();
hobby3.setHid(3L);
hobby3.setName("荷兰球");
hobbies.add(hobby1);
hobbies.add(hobby2);
hobbies.add(hobby3);
ActionContext.getContext().put("hobbies", hobbies); /**
* 用于回显
*/
this.hids = new Long[2];
this.hids[0] = 1L;
this.hids[1] = 2L;
return "backview";
}
}
UIaction
拦截器
需求:如果要访问某一个action类中的某一个方法的内容,如果是admin用户则访问,如果不是admin用户则不能访问。
实现:
7.1:编写一个拦截器(步骤):
7.1.1:编写interceptor:
7.1.2:写一个pvilegeSuperAction:
7.1.3:在配置文件中进行配置:
解析:
说明:
通过该图可以看出:当提交一个url请求时,先执行所有的拦截器(按照声明的从上到下的顺序),再执行action。
声明一个拦截器
声明一个拦截器栈
既可以引用一个拦截器
也可以引用一个拦截器栈
真正的做法:
如果p包继承了privilege包就把所有的新的拦截器栈继承过来了,如果没有继承,则执行默认的拦截器栈
缺点
把权限判断的代码和业务逻辑的代码混合在一起了
1.1 实现方案一
|
说明:
1、 这样做,程序的结构不是很好。原因是权限的判断和业务逻辑的方法紧密耦合在了一起。如果权限的判断很复杂或者是业务逻辑很复杂会造成后期维护的非常困难。所以结构不是很好
2、 这种形式只能控制一个action中的一个方法。如果很多action中的很多方法都需要这种控制。会导致大量的重复代码的编写。
1.2 实现方案二
动态代理可以实现。请参见cn.itcast.struts.jdkproxy包下的类。
1.3 实现方案三
在struts2中,用拦截器(interceptor)完美的实现了这一需求。在struts2中,
内置了很多拦截器,在struts-default.xml文件中可以看出。用户还可以自定义 自己的拦截器。自定义拦截器需要以下几点:
1、 在配置文件中:
包括两个部分:声明拦截器栈和使用拦截器栈
|
2、 在拦截器类中
一个类如果是拦截器,必须实现Interceptor接口。
|
案例2.在执行action的方法之前,
1、 开启日志
2、 权限的检查
1步骤:声明两个拦截器:
2.在执行的拦截器栈中,按照执行的先后顺序引入拦截器
注意:1.等我解决了在写QAQ
7.2,拦截器的应用:
7.2.1.属性驱动:
注意事项:
1、 必须使用struts2默认的拦截器栈中的ParametersInterceptor
2、 Action中的属性和表单中的name属性的值保持一致
3、 利用valueStack.setValue方法可以赋值了
模型驱动:
背景:
假设你正在完成一个网站的注册功能。在后台需要得到20多个属性,才能完成注册。如果用action中的属性获取值,就是这样的情况:
1、 在action中会写20个属性
2、 这20个属性添加set和get方法。
说明:这样会导致action中的代码结构不是很好。模型驱动很好的解决了这个问题。
从上图可以看出,ModelDriverInterceptor有两个作用:
1、 当前请求的action必须实现ModelDriver接口
2、 把model对象放入到了栈顶
1.验证:
1.防止表单的重复提交:
2.文件上传:
3.类型转换: