Day43-Struts03

xiaoxiao2021-02-28  44

一、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. 值栈创建的时机

请求到来的时候才会创建值栈。 翻翻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>
转载请注明原文地址: https://www.6miu.com/read-54235.html

最新回复(0)