Day43-Struts03

来源:互联网 发布:windows开启snmp 编辑:程序博客网 时间:2024/06/05 11:19

一、OGNL - 对象图导航语言

Object-Graph Navigation Language
其实就是给定一个对象,然后可以获取这个对象身上的属性值, 也可以给这个对象身上的属性赋值。 也可以调用这个对象方法…
struts底层就是用OGNL表达式来进行赋值的。

1. OGNL 核心元素

* 表达式:用于表示要执行什么操作* 根元素:root,OGNL会在一定的范围里面查找对象,标注根对象的好处就是便于OGNL区分对象的位置,快速的定位它们的所在,而且OGNL在获取根对象和非根对象的表达式也不尽相同。* 上下文:在OGNL范围里面,就是一个map集合,表示的就是一个范围,限定我们查询的范围。

2. OGNL 入门演示

1)导入Ognl的jar包
其实在struts基本jar包里面包含了Ognl的jar包;
核心jar包:ognl-3.0.19.jar;
依赖的jar包:javassist-3.11.0.GA.jar

2)Ognl的静态方法:
Ognl.setValue(表达式,根对象,值);
Ognl.getValue(表达式,根对象)

参数1:表达式,就是要操作什么东西,属性或者方法名
参数2:根对象:前面的这个name属性值就是从根对象取出来的。

Ognl可以操作属性,也可以操作方法!

     @Test      public void test01() throws OgnlException{           User user1 = new User("张三","123");           User user2 = new User("李四","123");           System.out.println(Ognl.getValue("username", user2));           System.out.println(Ognl.getValue("getUsername()", user2));           //这个代码虽然没有指定map集合,但是猜测。底下会有一个map上下文来包装我们的对象           Ognl.setValue("username", user2, "王五");           System.out.println(Ognl.getValue("getUsername()", user2));           System.out.println(Ognl.getValue("getUsername()", user1));      }
  • 表达式 和 根对象 和 上下文(OGNLContext )
    其实OGNL 里面表示上下文是有一个具体类来表示的。这个类是OGNLContext , 其实这个类也没有多少特别之处,就是一个Map 。
      @Test      public void test02() throws OgnlException{           User user1 = new User("张三","123");           User user2 = new User("李四","123");           Map<String, User> map = new HashMap<String, User>();           map.put("user1", user1);           map.put("user2", user2);        /*         * 参数一: 表达式         * 参数二: 上下文 ,其实就是一个范围         * 参数三: 根对象         *         * #取非根对象上的属性,就必须在前面加 #         * OGNL默认找属性都是在根对象身上找的。如果不加#就表示要在根对象上找属性。         */           System.out.println(Ognl.getValue("username", map, user1));           System.out.println(Ognl.getValue("#user2.username", map, user1));      }

二、值栈(重点)– Value Stack

值栈其实就是一个类 OgnlValueStack
它的作用就是存值,有点类似以前servlet阶段的 request 和 session这些作用域。 主要功能就是存值。我们要向从值栈取值。 配合struts标签来取的话,需要使用OGNL表达式。
这里写图片描述

1. Servlet 和 Action区别

servlet:
只会创建一次实例,以后再过来请求,不会创建实例

action:
action是多实例,来一次请求就创建一次实例。创建一次Action的实例,就创建一次ActionContext实例,并且就创建出来一个值栈的实例,并且是一一对应的。
action是服务器启动或者是项目加载的时候初始化,执行init()方法

2. 值栈创建的时机

  1. 请求到来的时候才会创建值栈。
    翻翻sturts2的源码

    配置核心过滤器的中有个类StrutsPrepareAndExecuteFilter,这个类的doFitler里面有行关键代码

         prepare.createActionContext(request, response);

该方法的背后有几行关键性的代码:

        //从ThreadLocal里面获取ActionContext实例,一开始是没有的,所以该对象是 null         ActionContext oldContext = ActionContext.getContext();        if (oldContext != null) {            // detected existing context, so we are probably in a forward            ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));        } else {            //创建值栈对象  是OgnlValueStack 对象            ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();            //给值栈里面的上下文区域存东西 。 存request \ response \session \application...            stack.getContext().putAll(dispatcher.createContextMap(request, response, null));            //创建ActionContext的对象。 然后跟值栈关联上            ctx = new ActionContext(stack.getContext());        }

3. 值栈的内部结构

值栈存值的时候,是存到了什么地方去的。
值栈其实就是一个类 OgnlValueStack ,我们如果往值栈存值, 其实是存到了这个类中的两个集合去。

    CompoundRoot root;  //这其实是一个集合  list    transient Map<String, Object> context;  //这是OGNL上下文  OGNLContext

源码中有一行代码

  context.setRoot(root);

也就是设置了OGNL上下文的根其实就是一个list集合 。就是那个CompoundRoot对象。

我们使用OGNL表达式取值的时候,都是去上下文里面取值。
这里写图片描述
contextMap(也就是上表中的OgnlContext)内部维护 了值栈的引用,同时,contextMap内部也维护了root的引用。
值栈的contextMap内部维护了值栈本身(OgnlValueStack)以及根(ComponentRoot)的引用.实际上,也可以看出来值栈的contextMap的实际数据结构为OgnlContext.
同时:contextMap内部加入了各个域对象(request,ServletContext,session)的引用.通过contextMap可以获取到各个域对象的引用.

捋一捋关于值栈、ActionContext、及其他:
首先,当服务器创建的时候,web.xml里面配置的过滤器的类StrutsPrepareAndExecuteFilter就执行init方法进行初始化。
每次请求到来的时候,都会经过过滤器,执行过滤器的dofilter方法。每来一个请求,就会创建一个Action实例,就会创建一个ActionContext的实例,在ActionContext创建实例时,又会创建一个值栈ValueStack的实例,ValueStack其实是ActionContext创建的时候创建的。
值栈ValueStack的实例的类是OgnlValueStack,值栈里面主要有CompoundRoot和contextMap,其中,CompoundRoot本质上是一个ArrayList,而contextMap本质上是一个Map。另外,contextMap里面还添加了原来servlet里面的request、response、application等等对象
其中contextMap维护了值栈本身和root的引用.ActionContext内部维护了contextMap的引用
这里写图片描述


4. 值栈的存值 & 取值

方式一:push方式

  • push – 执行的是压栈的动作。 放置的东西永远会在栈顶。
    直接放置在栈顶,没有什么key与之对应。所以取值的话,直接写属性名即可。 但是如果push 了多个,并且还想获取的不是栈顶的值,是栈顶下来的某一个位置的值,那么可以采用[0] \ [1] 这种做法
    [0] : 从栈顶开始包含,在这段范围取值
    [1] : 从第二层开始包含,在这段范围取值
    案例:
    action:(注意,在配置struts.xml的时候不能够采用重定向,只能够采用请求转发的方式,否则存入到值栈的数据会丢失)
public class ActionGetParameter extends ActionSupport{      public String test(){           System.out.println("test方法执行了");           User user01 = new User("zhangsan","123");           User user02 = new User("李四","456");           ValueStack valueStack = ActionContext.getContext().getValueStack();          //执行的是压栈的动作。 放置的东西永远会在栈顶。           valueStack.push(user01);           valueStack.push(user02);           ServletActionContext.getRequest().setAttribute("address", "深圳");           return SUCCESS;      }}

页面:
页面需要引入strtus的标签库

<%@ page language="java" contentType="text/html; charset=UTF-8"      pageEncoding="UTF-8"%><%@ taglib uri="/struts-tags" prefix="s"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>      这是跳转后的页面      <br />    //如果不写value,只写成<s:property />,那么就会直接输出栈上面的第一个对象。应该是地址值      <s:property value="username" />      --      <s:property value="password" />      <br />      <s:property value="[1].username" />      <s:property value="[1].top.username" />      --      <s:property value="[1].password" />     <!-- 如果是多个同样的对象,就采用这样的方式取出-->      <br />      <s:debug></s:debug></body></html>

方式二 – set方式
set 和 push 的区别在于, push 是直接把数据压倒栈顶 , 但是set方式是用一个map集合来包装数据,然后才把map压倒栈顶。 所以取值手法也有点不一样。

存值:action中的写法
        User user = new User("admin" ,"10086");        ValueStack stack = ActionContext.getContext().getValueStack();        stack.set("user01", user);
取值: 页面写法
       取set放的值<br>        <s:property value="user01.username"/> , <s:property value="user01.password"/><br>

第三种 – 属性封装 – 属性驱动方式
属性驱动方式最常用
需要声明成员变量,提供变量的get方法。
注意:这种方式存储的位置:

存值 :

public class ActionGetParameter extends ActionSupport{      private User user;      public User getUser() {           return user;      }      public String test(){           System.out.println("test方法执行了");           user = new User("王五","4444");           return SUCCESS;      }}

取值:页面

<s:property value="user.username"/> , <s:property value="user.password"/><br>

第四种 – 模型驱动封装

注意:
存储的位置
存储的属性名为model

存值:

public class ActionDemo extends ActionSupport implements ModelDriven<User>{   private User user;   @Override   public User getModel() {        return user;   }   public String add(){       user= new User("telangpu","70");       return SUCCESS;   }}

取值:
由于使用模型驱动封装,存值的时候,也还是存到action的范围里面去,但是对应的那个属性名就不是user了 而是 model 。

<s:property value="model.username"/> , <s:property value="model.password"/><br>

5. 使用EL表达式取值栈的值。

EL表达式也可以取到值栈的值,本来EL表达式是用于取作用域的值,但是值栈和作用域是两个东西。 为什么EL 表达式也能去值栈的值呢?

原因是 : struts对EL 表达式取值的代码进行了扩展(装饰者模式),如果从作用域取不到值就会去值栈找。当调用request.getAttribute()时,首先会从request范围的数据里找,然后从ValueStack(值栈的root对象里面)里找,最后到StackContext(OgnlContext里面)里找。

    reuqest.setAttribute("user" , user);    ${user.name} ------> pageContext.findAttrbute();  --> 先从page范围找, 没有,就找request, 还没有就找session。    request类型被包装成了 StrutsRequestWrapper 在里面的getAttribute 做了判定,如果从作用域中去不到值,就去值栈取值。

6. OGNL表达式符号

6.1) # 取非根对象的属性,就需要用到#了。
action:

ServletActionContext.getRequest().setAttribute("user", new User("非根下面的对象","2222"));页面:<s:property value="#request.user.username"/> ---<s:property  value="#request.user.password"/>

6.2)% 强制解析字符串成OGNL表达式 %{表达式}

//强制不解析<s:textfield value="%{'#request.user.username'}"></s:textfield><br/>//强制解析<s:textfield value="%{#request.user.username}"></s:textfield>

原创粉丝点击