Struts2_OGNL表达式
一、初步使用ongl表达式
OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,是一个使用简单、功能强大的、开源的表达式语言(框架),可以方便地操作任何的对象属性、方法等。
Struts2框架使用OGNL作为默认的表达式语言,主要用于页面的取值。它类似于EL表达式语言,但比EL语法强大很多。
EL Expression Language 表达式语言, 主要用来获取 JSP页面四个域范围数据 (page、 request、 session、 application )
使用ongl表达式
1. 导入的jar包:
ognl-xxx.jar : ognl的核心环境
javassist-xxx.jar : ognl的依赖,提供字节码修改能力。
2. 使用ognl表达式的jsp页面需要导入struts2的标签库
<%@ taglib prefix="s" uri="/struts-tags"%>
3. 几种基本的使用方式:
1. Java对象的直接访问 :
<s:property value="'name'"/><br/>
2. 实例方法调用
<s:property value="'name'.length()"/><br/>
3. 赋值操作或表达式串联
<s:property value="1+1" /><br/>
4. 静态方法调用(类的静态方法或静态变量的访问) 不推荐
语法:@[类全名(包括包路径)]@[方法名|值名]
例如:<s:property value="@java.lang.Math@max(10,20)"/><br/>
但是!!!在使用静态方法之前必须在struts.xml中开启静态方法调用功能
<!-- 开启ognl表达式静态方法调用 -->
<constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant>
5. OGNL上下文(OGNL Context)和ActionContext的访问。(值栈相关访问-重要)
6. 集合对象的操作
二、值栈
值栈的概念
值栈(ValueStack),是Struts2的数据中转站,其中自动保存了当前Action对象和其他相关对象(包括常用的Web对象的引用,如request等),也可以手动保存自己的数据对象,同时也可以随时随地将对象从值栈取出或操作(通过OGNL表达式)
了解:值栈的生命周期,和request一样或action,生命周期都一样
获取值栈对象
在Action动作类中获取值栈对象有两种方式:
@Override
public String execute() throws Exception {
//目标 :获取值栈对象
//方式一 : 通过request对象直接获取
ValueStack valueStack = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
//方式二 : 通过struts2提供的api来获取(底层还是1方式)
ValueStack valueStack2 = ActionContext.getContext().getValueStack();
//测试是否是同一个对象
System.out.println(valueStack == valueStack2);
return NONE;
}
值栈的数据存储结构
在值栈的内部有两个逻辑部分:
- ObjectStack(对象栈):保存了Action的相关对象和动作,数据存储结构是List。
- ContextMap(上下文栈):保存了各种映射关系,如常用的web对象的引用,数据存储结构是Map。
值栈ValueStack(OgnlValueStack实现类)中有两块区域 :
一块是CompoundRoot对象栈(也称为Root栈),它实质上是使用List集合来存放对象,存入对象压栈从栈顶往下压。
一块是OgnlContext上下文栈(也称为Map栈)。
Map栈中有两个区域,_root用于存放对象栈(Root栈)的引用
_values实质上使用Map集合键值对的形式存放对象
值栈是在请求对象引用了一块存储空间。
值栈包括两部分: 对象栈(CompoundRoot,继承List接口)和OGNL上下文栈(OgnlContext,实现Map接口)
而OGNL上下文栈内部又分为两部分:对象栈的引用和一个HashMap,这个HashMap会引用常用web对象和其他映射关系。
三、 操作值栈
栈的概念:
栈是一种数据结构,它按照先进后出的原则存储数据,即先进入的数据被压入栈底,
最后进入的数据在栈顶,需要读取数据的时候,从栈顶开始弹出数据(即最后一个数据被第一个读出来)。
栈也被成为先进后出表,可进行插入和删除操作,插入称之为进栈(压栈)(push),
删除称之为退栈(pop),允许操作的两端分别称为栈顶(top)和栈底(bottom)。栈底固定,而栈顶浮动。
对于栈就只能每次访问它的栈顶元素,从而可以达到保护栈顶元素以下的其他元素的目的。
值栈数据的保存:向root栈和map栈放入数据
@Override
public String execute() throws Exception {
// 目标 :向root栈和map栈中分别存入数据
String msg = "我爱java";
// 获取值栈对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
//向root栈中存值(压栈),匿名,
valueStack.push(msg);
//root栈中存值(压栈),有名字
valueStack.set("msg2", "我爱C++");
//向Root栈中存值(键值对)
ActionContext.getContext().put("msg3", "我爱Python");
return SUCCESS;
}
通过ognl表达式取出存储在值栈中的数据显示在页面
struts.xml 配置跳转页面 :
<action name="valueStack2" class="com.itdream.a_valueStack.ValueStack2Action">
<result>/a_ognl/result.jsp</result>
</action>
result.jsp :
<%@ taglib prefix="s" uri="/struts-tags"%>
<!-- 取出存在值栈中的值 -->
<!-- push方法,匿名压入Root栈的数据,第一个被压入 -->
<s:property value="[1].top"/><br/>
<!-- set方法,Struts2帮我们创建一个map集合存储数据压入Root栈的数据,map的key就是数据的名字,通过key取值 -->
<s:property value="msg2"/><br/>
<!-- put方法,放入Map栈中,通过加#号定向到Map栈中找 -->
<!-- 也可以不加#号使用默认查找机制,但这样先从Root栈先找,再到Map栈找key,如果在Root栈找到就找不到Map栈的数据了 -->
<s:property value="#msg3"/>|<s:property value="msg3"/><br/>
<!-- 用来查看值栈的内容 -->
<s:debug/>
点击Debug查看值栈的内容:
页面中直接查看值栈的元素的方法。
struts2为我们提供了一个标签:<s :debug/>,只要将其加在页面中即可。
Root栈:
数据从栈顶往下压,push方法直接压入String对象,此时它是栈顶,索引为0
然后使用set方法,创建了一个HashMap存储数据的值,作为对象存入Root栈,此时栈顶是这个HashMap对象,String对象的索引变为1
Map栈:
put方法,以键值对形式存储数据
在这里介绍值栈的创建过程:
1. 每次请求都会创建一个ActionContext对象和一个OnglValueStack值栈对象,这时在Root栈中放入了一个textProvider.
2. 紧接着,Struts2将与Servket有关的Response,Request,Session等对象放入到Map栈中,此时Map栈中有Servlet的对象引用.
3. 之后就准备执行Action动作类 ,创建Action动作的代理对象。Action是在执行的时候才进行初始化,初始化完成后,
将Action对象压入Root栈栈顶,并在Map栈的_root中放入一个引用:key - action
到这里,Root栈中有一个textProvider与一个Action对象,Map栈中有Servlet相关的数据以及Action对象的引用,
两个Action是同一个
4. 初始化完成,ActionProxy代理对象在执行时,会先执行拦截器,再执行Action,
这个时候,如果有Action实现了ModelDriven接口,模型驱动拦截器就将模型对象压入栈顶。
了解原理的目的:
知道值栈的初始化时机,访问action的时候,才创建(这个过程中会创建actionProxy对象,并且同时创建值栈,并且,将一些对象放入值栈。)
值栈初始化之后,里面主要默认有:root栈(textProvider、action类对象、model(user)),map栈(servlet相关对象、action对象一个引用)
哪些对象在栈顶!因为后面我们从值栈取值,都从栈顶往下取。所以,一定要知道哪个对象在栈上面。
值栈的默认搜索机制
测试代码:
public class ValueStack3Action extends ActionSupport {
// 目标 : 测试值栈的默认查找机制
// 提供Action的属性,Action初始化完成时,会将Action的对象属性压入Root栈
private String username = "林志玲";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String execute() throws Exception {
ValueStack valueStack = ActionContext.getContext().getValueStack();
//在创建一个有名字的对象压入Root栈
valueStack.set("username", "唐嫣");
//往Map栈存入数据
ActionContext.getContext().put("username", "高圆圆");
return SUCCESS;
}
}
jsp页面:
<!-- 取出存在值栈中的值 -->
<s:property value="username"/>
<!-- 用来查看值栈的内容 -->
<s:debug/>
此时运行取出username的值是 唐嫣 。
为什么呢?这是由于值栈的默认查找机制。
当我们使用 对象名称 直接查找时,底层使用的API是valuestack.find(ognl)
1. 他会优先从Root栈中开始查找(从上而下),因为Action属性存入值栈的是在执行前压入的,因此取的时候,它在值栈的下方,
因此首先找到上面的执行时 set 方法放入的 username。
2. 如果Root栈找不到,才会找Map栈中的key.如果想直接获取Map栈中的数据,加个#号即可
<s:property value="#username"/>
OGNL操作值栈的几个符号:
直接使用对象名 , # , % , $
1. 直接使用对象名,使用值栈的默认查找机制
2. #+key 从Map栈中查找对象的key,从而找到该数据
1. 通过#request等可以获取存储在Servlet域对象中的数据
3. % , 控制解析或者不解析
添加%{'对象名'}或者'对象名'让其变成普通字符串而不解析 。
<!-- %强制不转换 -->
<s:property value="username"/> <!-- 唐嫣 -->
<s:property value="'username'"/><!-- username -->
<s:property value="%{'username'}"/><!-- username -->
使用%{对象名}强制解析
<!-- 这里s:textfield标签类似与input的text标签 -->
<s:textfield value="username"/>
<!-- textfield标签的value中直接写对象名称不会进行解析,需要%强制解析 -->
<s:textfield value="%{username}"/>
4. $用于在配置文件中获取值栈的值
1.URL请求重定向时,携带(动态传递)参数 –struts.xml中使用
result type="redirect">/c_expr/ognl2.jsp?name=${myname}</result>
值栈的存取汇总小结
值栈的主要作用就是数据的保存和获取(可以在任何地方获取)。
1.如何向值栈保存数据
-
ValueStack.put(obj)
:保存数据到Root栈(栈顶),(对象本身)匿名,通过索引取出 -
ValueStack.set(key,value)
:保存数据到Root栈(栈顶),数据被自动封装成一个Map集合存储,栈顶保存的是一个Map集合,Map的value就是对象,这种存储方式有名字.ognl通过名称获取数据的值 -
ActionContext.getContext().put(key,value)
: 保存数据到Map栈中 - 提供Action成员变量,提供getter方法(Action就在root栈中,Action属性可以被搜索)
模型驱动封装参数优先于属性驱动的原理:
第四种方式在初始化完成时压入Root栈,而ModelDriven过滤器待封装的参数,在初始化完成后,过滤器中,将其压入栈顶。
而封装的参数从Root栈中取值是自上而下取值的,因此模型驱动优先于属性驱动。
2. ognl表达式如何获取值栈的数据
- JSP页面获取
-
<s :property value= “对象名”/>
先搜索root栈对象属性(getter方法: getXxx-->xxx),再搜索map的key -
<s :property value= “#对象名”/>
搜索map栈的key - 通过
[index].top
指定访问Root栈的某个对象,例如[0].top
访问栈顶对象
-
- Action代码获取
-
ValueStack.findValue(ognl表达式)
; 获取值栈数据
-
3. 值栈的生命周期
* 贯穿整个Action的生命周期,每个Action类的对象实例都拥有一个ValueStack对象。
* Struts2框架将ValueStack对象保存在名为“struts.valueStack”的请求(request)属性中,
即值栈是request中的一个对象,一个请求对应一个Action实例和一个值栈对象。
因此:值栈的生命周期,就是request生命周期。值栈、action、request生命周期一样,不存在线程问题!
重定向跳转页面时,目标页面无法获取到Action中放入值栈的值。
4. 补充,通过EL表达式获取值栈的值
EL 表达式原理:
在page、request、session、application 四个范围,调用getAttribute 获取数据。为什么也可以获取值栈的值呢?
原因:Struts2提供StrutsRequestWrapper包装类,对request 的 getAttribute 方法增强 。
它优先使用 request.getAttribute取值,如果取不到,执行 valueStack的findValue方法使用值栈的默认查找机制。
问题思考 :
后台代码:
request.setAttribute(“name“, ”aaa“ ) ;//存入request域中
valueStack.set(“name“,”bbb“ )//存入值栈
页面代码:
<s:property name=”name” /> ----- 值栈默认查找机制,bbb
${name} ------ 优先从request中查找,aaa
四、CRM案例:添加,显示,修改客户
1. 搭建环境
1.1 搭建Struts2环境
- 拷贝前端页面
- 导入Struts2的jar包
- 配置web.xml的前端控制器
- 配置
struts.xml
文件 - Struts2环境搭建完成
1.2 搭建Hibernate环境
-
导入
Hibernate
的jar包- required包下的所有jar包
- c3p0的jar包
- 另,log4j-1.x的jar包
删除
Struts2与Hibernate
冲突的jar包R : 数据库表的编写
O : 持久化类的编写
M : 映射文件的编写
导入日志
log4l.properties
配置文件,创建核心配置文件hibernate.cfg.xml
准备工具类
HibernateUtils
创建包结构,测试Hibernate是否搭建成功
Hibernate
搭建完成
2. 代码实现功能
2.1 修改menu.jsp
新增客户 : 跳转到增加新客户的页面
href="${pageContext.request.contextPath }/jsp/customer/add.jsp"
客户列表 : 查询所有客户
href="${pageContext.request.contextPath }/customer_list.action"
2.2 修改add.jsp
form 表单提交路径 :
action="${pageContext.request.contextPath }/customer_save.action"
2.3 struts.xml
请求与Action的配置关系
<struts>
<constant name="struts.devMode" value="true" />
<!-- 简单样式 -->
<constant name="struts.ui.theme" value="simple"/>
<package name="default" namespace="/" extends="struts-default">
<action name="customer_*" class="com.itdream.crm.web.action.CustomerAction" method="{1}">
<result name="flushListCustomer" type="redirectAction">customer_list.action</result>
<result name="listCustomer">/jsp/customer/list.jsp</result>
<!-- 默认转发,传递customer数据 -->
<result name="editjsp">/jsp/customer/edit.jsp</result>
</action>
</package>
</struts>
2.4 Action动作类处理请求
1. 提供模型驱动,用以封装参数。
2. 查询到的数据,存入值栈转发。
3. 下面是三个功能:新增客户,查询所有客户,修改客户
public class CustomerAction extends ActionSupport implements ModelDriven<Customer>{
//提供模型对象
private Customer customer = new Customer();
@Override
//Struts2获取模型对象封装
public Customer getModel() {
return customer;
}
//添加客户
public String save() {
//获取参数,模型驱动自动封装
//调用业务层
CustomerService service = new CustomerServiceImpl();
service.addCustomer(customer);
return "flushListCustomer";
}
//查询所有客户
public String list() {
//调用业务层
CustomerService service = new CustomerServiceImpl();
List<Customer> listCustomer = service.findListCustomer();
//将所有Customer的数据存入值栈,以set方式存入Root栈
ValueStack valueStack = ActionContext.getContext().getValueStack();
valueStack.set("list", listCustomer);;
//跳转页面
return "listCustomer";
}
//查询要修改的客户,并跳转到修改页面
public String showEdit() {
//获取参数
Long cust_id = customer.getCust_id();
//调用Service层
CustomerService service = new CustomerServiceImpl();
Customer customer2 = service.findCustomerById(cust_id);
//将查询到的客户数据压入值栈
ActionContext.getContext().getValueStack().set("customer", customer2);
//跳转页面
return "editjsp";
}
//修改客户信息
public String update() {
//获取参数(模型驱动)
//调用业务层
System.out.println("hehe");
CustomerService service = new CustomerServiceImpl();
service.updateCustomer(customer);
//跳转
return "flushListCustomer";
}
}
2.5 Service层处理业务逻辑
public class CustomerServiceImpl implements CustomerService {
@Override
// 添加客户
public void addCustomer(Customer customer) {
// 获取Session对象
Session session = HibernateUtils.getCurrentSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 业务逻辑
try {
CustomerDAO dao = new CustomerDAOImpl();
dao.addCustomer(customer);
} catch (Exception e) {
// struts2默认异常回滚事务,可以不写
transaction.rollback();
e.printStackTrace();
} finally {
// 无论是否异常都提交事务结束事务
transaction.commit();
}
}
@Override
// 查询所有客户
public List<Customer> findListCustomer() {
// 获取Session对象
Session session = HibernateUtils.getCurrentSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 业务逻辑
List<Customer> listCustomer = null;
try {
CustomerDAO dao = new CustomerDAOImpl();
listCustomer = dao.findListCustomer();
} catch (Exception e) {
// struts2默认异常回滚事务,可以不写
transaction.rollback();
e.printStackTrace();
} finally {
// 无论是否异常都提交事务结束事务
transaction.commit();
}
return listCustomer;
}
@Override
// 通过id查找客户
public Customer findCustomerById(Long cust_id) {
// 获取Session对象
Session session = HibernateUtils.getCurrentSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 业务逻辑
Customer customer = null;
try {
CustomerDAO dao = new CustomerDAOImpl();
customer = dao.findCustomerById(cust_id);
} catch (Exception e) {
e.printStackTrace();
} finally {
transaction.commit();
}
return customer;
}
@Override
// 更新客户信息
public void updateCustomer(Customer customer) {
// 获取Session对象
Session session = HibernateUtils.getCurrentSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 业务逻辑
try {
CustomerDAO dao = new CustomerDAOImpl();
dao.updateCustomer(customer);
} catch (Exception e) {
// Struts2默认异常回滚
transaction.rollback();
e.printStackTrace();
} finally {
// 提交事务
transaction.commit();
}
}
}
2.6 dao层操作数据库
1. 添加客户需要先找到其关联的联系人建立关系
2. 持久态的使用
public class CustomerDAOImpl implements CustomerDAO {
@Override
//添加客户
public void addCustomer(Customer customer) {
//获取Session对象
Session session = HibernateUtils.getCurrentSession();
//首先找到客户的联系人
//创建Criteria查询对象
Criteria criteria = session.createCriteria(Linkman.class);
//创建限制条件
Criterion criterion = Restrictions.eq("lkm_name", customer.getCust_linkman());
//添加限制条件
criteria.add(criterion);
//查询联系人
Linkman linkman = (Linkman) criteria.uniqueResult();
if(linkman == null) {
linkman = new Linkman();
linkman.setLkm_name(customer.getCust_linkman());
}
//保存客户到数据库
//建立关系:客户表达与联系人的关系:一个客户有多个联系人
customer.getLinkmans().add(linkman);
//建立关系:联系人表达与客户的关系:一个联系人对应一个客户
linkman.setCustomer(customer);
//将customer变为持久态,配置了级联,会自动级联将linkman也变为持久态
session.save(customer);
}
@Override
//查询所有客户
public List<Customer> findListCustomer() {
//单表查询使用Criteria查询更方便
//获取Session对象
Session session = HibernateUtils.getCurrentSession();
//创建Criteria查询对象
Criteria criteria = session.createCriteria(Customer.class);
//执行查询
return criteria.list();
}
@Override
//通过ID查询客户
public Customer findCustomerById(Long cust_id) {
//获取Session
Session session = HibernateUtils.getCurrentSession();
//get方法获取的Customer已经是持久态无需保存
return session.get(Customer.class, cust_id);
}
@Override
//更新客户信息
public void updateCustomer(Customer customer) {
//获取Session对象
Session session = HibernateUtils.getCurrentSession();
//修改客户
session.update(customer);
}
}
2.7 显示所有客户list.jsp
获取值栈数据
<!-- 神奇的request先从request中找,找不到就走值栈的默认查找机制,因此也能查找到存入值栈的list -->
<c:forEach items="${list }" var="customer">
<TR style="FONT-WEIGHT: normal; FONT-STYLE: normal; BACKGROUND-COLOR: white; TEXT-DECORATION: none">
<TD>${customer.cust_name }</TD>
<TD>${customer.cust_level }</TD>
<TD>${customer.cust_source }</TD>
<TD>${customer.cust_linkman }</TD>
<TD>${customer.cust_phone }</TD>
<TD>${customer.cust_mobile }</TD>
<TD>
<a href="${pageContext.request.contextPath }/customer_showEdit.action?cust_id=${customer.cust_id}">修改</a>
</TD>
2.8 edit.jsp
获取存入值栈的客户信息,提交修改
form表单action请求地址:
action="${pageContext.request.contextPath }/customer_update.action"
//隐藏域提交客户id,从而找到该客户进行数据更新
<s:textfield name="cust_id" value="%{customer.cust_id}" type="hidden" />
客户名称:<s:textfield name="cust_name" value="%{customer.cust_name}" />
客户级别:<s:textfield name="cust_level" value="%{customer.cust_level}" />
信息来源:<s:textfield name="cust_source" value="%{customer.cust_source}" />
联系人:<s:textfield name="cust_linkman" value="%{customer.cust_linkman}" />
固定电话 :<s:textfield name="cust_phone" value="%{customer.cust_phone}" />
移动电话 :<s:textfield name="cust_mobile" value="%{customer.cust_mobile}" />
2.9 功能完成,测试
结果图: