Struts2_ognl表达式&操作值栈

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对象的直接访问 :
        
2. 实例方法调用
3. 赋值操作或表达式串联
4. 静态方法调用(类的静态方法或静态变量的访问) 不推荐 语法:@[类全名(包括包路径)]@[方法名|值名] 例如:
但是!!!在使用静态方法之前必须在struts.xml中开启静态方法调用功能 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集合键值对的形式存放对象
Struts2_ognl表达式&操作值栈_第1张图片
img12.png

值栈是在请求对象引用了一块存储空间。

值栈包括两部分: 对象栈(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 配置跳转页面 :
    
            /a_ognl/result.jsp
    


result.jsp : 

<%@ taglib prefix="s" uri="/struts-tags"%>





|
Struts2_ognl表达式&操作值栈_第2张图片
img14.png

点击Debug查看值栈的内容:

页面中直接查看值栈的元素的方法。
struts2为我们提供了一个标签:,只要将其加在页面中即可。

Root栈:

数据从栈顶往下压,push方法直接压入String对象,此时它是栈顶,索引为0
然后使用set方法,创建了一个HashMap存储数据的值,作为对象存入Root栈,此时栈顶是这个HashMap对象,String对象的索引变为1
Struts2_ognl表达式&操作值栈_第3张图片
img15.png

Map栈:

put方法,以键值对形式存储数据
Struts2_ognl表达式&操作值栈_第4张图片
img16.png

在这里介绍值栈的创建过程:

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对象一个引用)

哪些对象在栈顶!因为后面我们从值栈取值,都从栈顶往下取。所以,一定要知道哪个对象在栈上面。
Struts2_ognl表达式&操作值栈_第5张图片
img13.png

值栈的默认搜索机制

测试代码:

    
    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页面:

    
    
    
    
    


    此时运行取出username的值是 唐嫣 。

    为什么呢?这是由于值栈的默认查找机制。
    当我们使用 对象名称 直接查找时,底层使用的API是valuestack.find(ognl)
    1. 他会优先从Root栈中开始查找(从上而下),因为Action属性存入值栈的是在执行前压入的,因此取的时候,它在值栈的下方,
    因此首先找到上面的执行时 set 方法放入的 username。

    2. 如果Root栈找不到,才会找Map栈中的key.如果想直接获取Map栈中的数据,加个#号即可
        
    

OGNL操作值栈的几个符号:

直接使用对象名 , # , % , $

1. 直接使用对象名,使用值栈的默认查找机制

2. #+key 从Map栈中查找对象的key,从而找到该数据
    1. 通过#request等可以获取存储在Servlet域对象中的数据

3. % , 控制解析或者不解析

    添加%{'对象名'}或者'对象名'让其变成普通字符串而不解析 。
    
     
    
    

    使用%{对象名}强制解析
    
    
    
    

4. $用于在配置文件中获取值栈的值
    
    1.URL请求重定向时,携带(动态传递)参数 –struts.xml中使用
        result type="redirect">/c_expr/ognl2.jsp?name=${myname}

值栈的存取汇总小结

值栈的主要作用就是数据的保存和获取(可以在任何地方获取)。

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页面获取
    • 先搜索root栈对象属性(getter方法: getXxx-->xxx),再搜索map的key
    • 搜索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“ )//存入值栈
页面代码:
   -----  值栈默认查找机制,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的配置关系

    
    
    

    
        
            customer_list.action
            /jsp/customer/list.jsp
            
            /jsp/customer/edit.jsp
        
    

2.4 Action动作类处理请求
1. 提供模型驱动,用以封装参数。
2. 查询到的数据,存入值栈转发。
3. 下面是三个功能:新增客户,查询所有客户,修改客户

public class CustomerAction extends ActionSupport implements ModelDriven{

    //提供模型对象
    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 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 findListCustomer() {
        // 获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        // 开启事务
        Transaction transaction = session.beginTransaction();
        // 业务逻辑
        List 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 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获取值栈数据


    
    ${customer.cust_name }
    ${customer.cust_level }
    ${customer.cust_source }
    ${customer.cust_linkman }
    ${customer.cust_phone }
    ${customer.cust_mobile }
    
    修改
    
2.8 edit.jsp获取存入值栈的客户信息,提交修改
form表单action请求地址:
    action="${pageContext.request.contextPath }/customer_update.action"

//隐藏域提交客户id,从而找到该客户进行数据更新


客户名称:
客户级别:
信息来源:
联系人:
固定电话 :
移动电话 :
2.9 功能完成,测试

结果图:

Struts2_ognl表达式&操作值栈_第6张图片
img17.png
img18.png
Struts2_ognl表达式&操作值栈_第7张图片
img19.png

你可能感兴趣的:(Struts2_ognl表达式&操作值栈)