一、OGNL是什么?

OGNL(Object-Graph Navigation Language)对象图导航语言,是一种表达式语言,它可以

1.遍历对象的结构图

2.存取对象的属性(实例属性和静态属性)

3.调用对象的方法(实例方法和静态方法)

4.操作集合对象(数组、List、Map)

5.new对象 (自动初始化,需要无参构造器)

6.自动进行类型转换

……

总的来说这货就是提供一种简便的方式来进行数据转移类型转换

1.数据转移
数据从前端页面过来,参数拦截器将它们拍到Action对应的属性中,但是,参数拦截器是怎么知道哪个参数名对应着哪个Action属性的呢?

2.类型转换

请求参数都是一个<String,String>类型的<K,V>对,那么它们是如何从String类型完成到各种类型(比如int,double,Date)类型的转换的呢?

二、OGNL的提供的功能

OGNL是一个对象导航语言,既然涉及到对象(这里的对象大多指的是JavaBean,而对于JavaBean访问成员属性一般都是通过getter/setter,所以这里提一下OGNL和很多其它的表达式语言一样也是通过getter/setter来访问成员属性的)那对象就要有一个存储的地方,这个存储对象的地方就是OGNL上下文(OGNLContext),对象存储在OGNLContext上,而OGNL表达式去OGNLContext上找对象,就这样通过OGNLContext将参数和对象关联到了一起。OGNLContext是一个Map,它里面可以存放JavaBean对象,并且有一个根对象,这个对象称为root(在Struts2中是CompoundRoot来充当root),我们只需要了解OGNL上下文由一个Map结构的context和一个对象结构的root组成就可以了,但是Strtus2对OGNL做了扩展,扩展为了支持多root,是通过将很多个对象摞起来成为栈结构来实现的(总结:标准的OGNL是单root,Sturts2通过栈扩展为了多root),其中root栈上的对象会暴露自己的属性也就是说可以直接使用OGNL表达式访问它的属性,从栈顶依次往下找,找到的第一个同名属性即返回,所以root栈高层的对象的属性会屏蔽低层对象的同名属性。

OGNLContext由context和root组成,访问context中的元素需要加上#前缀,比如#foo[bar],这是因为如果不加的话默认是从root开始找的,如果root找不到才会再去context中找,而如果context和root中有同名属性的话不加#就惨了,明明想取context中的结果取出了root中的,所以一个好习惯就是如果明确知道在context中的话就加上#。总结一下:不加#的意思就是先去root中找,找不到再去context,加上#的意思就是先去context中找,找不到再去root中找。 (此处存疑?多次相同实验结果竟然不一致???一定是智子在搞鬼…)

注意:OGNL是用来在不方便或者不合适写java代码的地方用来当做java代码使用的,就像jsp里面的标签一样,所以不要滥用OGNL。

ActionContext中有一个context的成员变量,但是这个变量存放的是Action的context并不是OGNL的context,需要注意(大雾) ,当然作为Action的一个属性,ValueStack也存放在这个context中,可以这么获取ValueStack:

ValueStack valueStack=ActionContext.getContext().getValueStack();

什么时候用context?什么时候用root?

因为在OGNL中不支持多个root对象,所以当需要在表达式中访问多个毫不相干的对象的时候就需要有一种方法将它们整合在一起,什么方法呢,就是用一个Map把装到一起,这个Map就叫做context,所以总结一下:单个对象—>root,多个对象—>context 。但是又因为Struts2对OGNL的单root进行了扩展,使得root也是多个了,所以吶,我们平时就把东西往栈里边使劲塞就对了。

一个小实验说明何时应该加上#号:

现在有这么一个Action:

public class FooAction extends ActionSupport {

    private User user;

    @Override
public String execute() throws Exception { // 这个user对象处于root栈顶Action中,使用root的话一下就能找到
user = new User("username_root", "passwd_root");
        // 往context中放了一个名为user的User对象,使用context的话会找到这个
User u2 = new User("username_context", "passwd_context");
ActionContext.getContext().getValueStack().getContext().put("user", u2); return SUCCESS;
} public User getUser() {
return user;
} public void setUser(User user) {
this.user = user;
} }

在结果JSP页面中访问:

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

结果:

可以看到不加#的话会找到root中的user,加了#的话会找到context中的user,这里只是说重名的情况下,不重名的话,比如。

Action:

public class FooAction extends ActionSupport {

    private User user;

    @Override
public String execute() throws Exception { // 这个user对象处于root栈顶Action中,使用root的话一下就能找到
user = new User("username_root", "passwd_root"); return SUCCESS;
} public User getUser() {
return user;
} public void setUser(User user) {
this.user = user;
} }

JSP:

<s:property value="#user.username" />

#找不到放在root中的user,反过来呢?

Action:

public class FooAction extends ActionSupport {

    @Override
public String execute() throws Exception { User u2 = new User("username_context", "passwd_context");
// 往context中放了一个名为user的User对象,使用context的话会找到这个
ActionContext.getContext().getValueStack().getContext().put("user", u2); return SUCCESS;
} }

JSP:

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

卧槽竟然也找不到,郁闷了我就,看来是放在root中的不加#,放在context的加#。

自动初始化  & 构造对象

当尝试导航到目标属性时,如果发现更深层的OGNL表达式中任何中间属性不存在(null),就会自动实例化它们,这里的实例化需要一个无参构造器。

1.构造普通对象

直接使用构造方法new即可,但是记得要用全路径类名,比如:

<s:property value="new struts_practice_001.User('username','passwd')" />

使用User的这个构造器创建了一个对象:

public class User {

    ...    
    public User(String username, String passwd) {
this.username = username;
this.passwd = passwd;
}
    ...
 
 }

总结:使用new+全路径类名+可访问的构造器就可以创建对象了。

2.构造List对象

使用{},中间使用,进行分割,比如:

<s:property value="{'a','b'}" />

构造了一个List容器,放置了两个字符串'a'和'b';

<s:property value="{new struts_practice_001.User('username','passwd'),'b'}" />

构造了一个List容器里面放置了一个User对象和一个字符串。

3.构造Map对象

使用#{},类似于JSON的格式,比如:

<s:property value='#{"Tom":new struts_practice_001.User("Tom","qwerty"),"Sam":new struts_practice_001.User("Sam","qwerty")}' />

Struts2中的OGNL

在Struts2中使用ValueStack对OGNL的封装,ValueStack是一个接口,OgnlValueStack是ValueStack的默认实现,ValueStack中的数据分两部分存放root和context(符合OGNL的概念),但是Struts2做了一个扩展,它实现了多root,是使用一个栈结构来实现的,当要使用某个属性的时候从栈顶往下搜索直到找到位置,就相当于栈中的每个元素都是root了,同时暴露了每个栈中元素的属性,不足之处时这样处理还是会让栈高层的对象屏蔽低层对象的同名属性,这样所谓的多root就实现的不完美喽哦

来看一下默认的ValueStack接口的实现OgnlValueStack的部分代码:

public class OgnlValueStack implements Serializable, ValueStack, ClearableValueStack, MemberAccessValueStack {

    .......    

    CompoundRoot root; //root实现
transient Map<String, Object> context; //context实现 ....... }

context是用Map实现的,没什么好说的,但是发现root是一个CompoundRoot ,这是个什么东西呢,来看一下它的源代码:

/**
* A Stack that is implemented using a List.
* 这是一个借助于List实现的栈结构。
*/
public class CompoundRoot extends ArrayList { public CompoundRoot() {
} public CompoundRoot(List list) {
super(list);
} public CompoundRoot cutStack(int index) {
return new CompoundRoot(subList(index, size()));
} //把列表的0下标当做栈顶,取得栈顶元素就返回List的0下标元素
public Object peek() {
return get(0);
} //同理,pop()就是将0下标移除
public Object pop() {
return remove(0);
} //插入到栈顶实际上就是插入到List的表头,但是这里我有个疑惑的地方就是既然是使用数组实现的,
//那么频繁的插入删除必然导致数组元素移动、空间扩展之类的,这样难道不会影响到效率吗?
//一直觉得这样子设计很撒比.....
public void push(Object o) {
add(0, o);
}
}

原生的OGNL的概念就是传入一个对象(这里的对象指的是JavaBean这种东西),然后在这个对象上导航,而Struts2的扩展就是将这个对象纵向(理解为竖着)展开变为从一个栈从上向下找,以此来实现多root。

ValueStack暴露了两个方法来实现OGNL取值设值的操作:

setValue(String expr, Object value); 

findValue(String expr);

查找值的时候会先从root中找,如果找不到的话就再去context中找:

private Object tryFindValue(String expr) throws OgnlException {
Object value;
expr = lookupForOverrides(expr);
if (defaultType != null) {
value = findValue(expr, defaultType);
} else {
//先尝试从root中找
value = getValueUsingOgnl(expr);
if (value == null) {
//然后root找不到再尝试从context中找
value = findInContext(expr);
}
}
return value;
}

OGNL的使用(原始类型或可以自动转换的类型):

首先要明确的是OGNL一般在前端页面中使用(至少是应该在不方便写Java代码的地方使用,那些直接拿Java代码测试的让人感觉好……),比如设置值的时候:

前端:

<form action="loginAction" method="post">
用户名:<input type="text" name="user.username" /><br/>
密&nbsp;&nbsp;码:<input type="password" name="user.passwd" /><br/>
<input type="submit" value="登录" />
</form>

Action:

public class LoginAction extends ActionSupport {

    private User user;

    @Override
public String execute() throws Exception { return SUCCESS;
} public User getUser() {
return user;
} public void setUser(User user) {
this.user = user;
} }

注意前端页面中input的name,即参数的名字就是一个OGNL表达式,它会去ValueStack上匹配,实际上ValueStack总是使用的root栈,因为在请求到来之前就会把Action对象压入栈顶,当拿着OGNL表达式来栈找的时候,结合暴露属性的特点,在栈顶的Action实例上一下就找到了user这个属性,又因为这个属性也是一个对象,所以可以继续使用user.username来访问user对象的属性,这样可以有一个路径走啊走,这就是对象导航的意思。

取值和设置都是这样子,只要在合适的地方写字符串类型的OGNL表达式就可以啦。

来看几个栗子:

1. 访问对象属性

访问对象的属性,使用foo.bar的格式,如果是context里的话,使用#前缀(不使用也能找得到,因为默认的行为是会先去root中找再去context中找,但是我们明明知道在context还让它去root中效率就低了不是,而且万一root中有重名的就不会去找context中的了,这就得到错误结果了,但是呢,因为context几乎用不到(我个人是这样子认为的),所以下面的探讨都只基于root。

一个访问对象属性的例子:

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

注:使用<s:property  />标签需要先导入标签库,如下:

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

<s:property  />的value属性会被解析为OGNL表达式,user.username这个表达式的意思是取值栈上的Action中的一个叫user的对象的username属性。

也可以直接设置值,比如:

<s:property value="user.username='Tom'" />

直接这样设置值使用的比较少,更多的时候是通过导航来精确地从对象中取出所需要的属性,所以下面的例子就重点说访问了。 

2. 访问类(静态)属性

格式:

@packageName.ClassName@staticPropertyName

@包名.类名@类属性名

比如:

<s:property value="@java.lang.Math@PI" />

3.调用对象方法

格式:

foo.bar(args…)

对象名.方法名(参数…)

比如:

<s:property value="foo()" />

是调用了Action的实例方法,Action的代码如下:

public class LoginAction extends ActionSupport {

    ...

    public String foo(){
return "FOO";
} ... }

再比如:

<s:property value="'foo'.length()" />

这个是调用了了String对象的length()方法(直接写一个字符串就是一个String对象的哦)

再比如:

<s:property value="'foo'.substring(0,1)" />

调用了String对象的substring()方法截取[0,1)区间上的字符串。

4.调用类(静态)方法

一般工具类的方法都是静态的,这里一个可能的应用场景就是从前端页面调用工具类的静态方法来处理一些东西,当然这种逻辑最好还是在后台完成,最好保证前端页面和后台只是单纯的交换数据。

格式:

@packageName.ClassName@staticMethodName(args…)

@包名.类名@静态方法名(参数…)

在Struts2.1之后的版本默认不支持静态方法调用,需要在sturts.xml文件中设置一个常量属性:

<!-- 允许OGNL调用静态方法 -->
<constant name="struts.ognl.allowStaticMethodAccess" value="true" />

然后就可以调用静态方法啦:

<s:property value="@vs@foo()" />

上面的写法看起来很奇怪,这是因为全路径类名一般都很长很长,会很麻烦,如果是调用的值栈中的对象的话干脆就给它一个比较简单的标识,这个标识就是@vs,vs=ValueStack,直接写@vs是引用栈顶元素,@vs+数字是引用栈中相应位置的元素。

@vs是用来引用栈中元素的简洁写法。可以@vs@staticProperty访问静态属性,可以@vs@staticMethod(…args)访问静态方法

再比如:

<s:property value="@java.lang.Math@sqrt(10)" />

总结一下:

1.调用格式@全路径类名@静态方法名(参数…)

2.可以用@vs的简写方式引用栈中元素。 

5.数组类型

对数组按下标访问,是foo[0]、foo[1]这种格式,比如现在有这么一个Action:

public class FooAction extends ActionSupport {

    private User users[];

    @Override
public String execute() throws Exception { users=new User[5];
for(int i=0;i<users.length;i++){
users[i]=new User("username_"+i,"passwd_"+i);
} return SUCCESS;
} public User[] getUsers() {
return users;
} public void setUsers(User[] users) {
this.users = users;
} }

在结果JSP页面这么访问:

<s:property value="users[0].username" /><br/>
<s:property value="users[0].passwd" /><br/> <s:property value="users[1].username" /><br/>
<s:property value="users[1].passwd" /><br/>

结果:

考虑下标越界的情况:

<s:property value="users[0].username" /><br/>
<s:property value="users[0].passwd" /><br/> <s:property value="users[100].username" /><br/>
<s:property value="users[100].passwd" /><br/>

结果:

只是不能访问到,但是并不会抛异常,说明这里有一个错误兼容的机制啊。

总结:数组使用下标访问对应位置的元素,越界不抛异常。

6.List类型

先模拟一个从前端页面往后台传递参数的情景,比如注册的时候:

VO对象:

/**
* VO,用来接收注册数据的
*
* @author CC11001100
*
*/
public class RegisterUser { private String username;
private String passwd;
private String repasswd; private String address;
private List<String> hobby; @Override
public String toString() {
return "RegisterUser [username=" + username + ", passwd=" + passwd
+ ", repasswd=" + repasswd + ", address=" + address
+ ", hobby=" + hobby + "]";
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPasswd() {
return passwd;
} public void setPasswd(String passwd) {
this.passwd = passwd;
} public String getRepasswd() {
return repasswd;
} public void setRepasswd(String repasswd) {
this.repasswd = repasswd;
} public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
} public List<String> getHobby() {
return hobby;
} public void setHobby(List<String> hobby) {
this.hobby = hobby;
} }

Action:

public class RegisterAction extends ActionSupport {

    private RegisterUser user;

    @Override
public String execute() throws Exception { for(int i=0;i<user.getHobby().size();i++){
System.out.println(user.getHobby().get(i));
} return SUCCESS;
} public RegisterUser getUser() {
return user;
} public void setUser(RegisterUser user) {
this.user = user;
} }

前端页面:

<form action="registerAction" method="post">
用户名:<input type="text" name="user.username" /><br/>
密码:<input type="password" name="user.passwd" /><br/>
确认密码:<input type="password" name="user.repasswd" /><br/>
爱好:<input type="checkbox" name="user.hobby" value="读书">读书</input>
<input type="checkbox" name="user.hobby" value="看电影">看电影</input>
<input type="checkbox" name="user.hobby" value="听音乐">听音乐</input><br/>
地址:<input type="text" name="user.address" /><br/>
<input type="submit" value="注册" />
</form>

比较关键的几个地方:

1.在往里面放的时候可以不指定下标,List有append这种功能啊。

2.泛型的List一定不要预先初始化化(《Struts2 in action》)。

List的访问与数组基本一致,通过下标就可以访问得到了,不再赘述。

(备注:jdk1.5之前不支持类型指定,基本用不到不浪费精力研究了,如需要解决办法自行百度) 

7.Map类型

模拟一个场景,需要输入父母亲的联系方式:

前端页面:

<form action="setContactPersonAction" method="post">
父亲联系方式:<input type="text" name="contacts['father']" /><br/>
母亲联系方式:<input type="text" name="contacts['mother']" /><br/>
<input type="submit" value="登录" />
</form>

Action:

public class SetContactPersonAction extends ActionSupport {

    private Map<String, String> contacts;

    @Override
public String execute() throws Exception { for(String key:contacts.keySet()){
System.out.println(key+":"+contacts.get(key));
} return SUCCESS;
} public Map<String, String> getContacts() {
return contacts;
} public void setContacts(Map<String, String> contacts) {
this.contacts = contacts;
} }

输入:

结果:

8.集合类型的遍历

9.集合运算

? 获得所有符合的元素

^ 只获取第一个

$ 只获取最后一个

串联表达式:

OGNL支持表达式串联,之前一直不知道这种写法该叫做什么,就是可以连着写好几个表达式同时将最后一个表达式的值作为个串联表达式的值返回,串联表达式如下:

a++,b++,c++;

整个表达式的结果是c++的结果,注意该死的Java不支持串联表达式,不知道最新版会不会加上这功能。

Lambda表达式:

略。

三个符号(#、%、$):

1. #号

1.1访问非根对象属性,#相当于ActionContext.getContext();

context中的一些常用的命名对象:

#parameters 用于访问http请求参数

#request 用于访问request中的属性

#session 用于访问session中的属性

#application 用于访问application中的属性

#attr 会依次搜索PageContext、HttpServletRequest、HttpSession、ServletContext

1.2.用于过滤和投影,比如

person.{?#this.age>20}

1.3.用于构造Map,比如:

<s:property value="{new struts_practice_001.User('username','passwd'),'b'}" />

2.%号

1.将字符串强制解析为OGNL表达式 ,比如

%{#foo[“bar”]}

3.$号

1.在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:

2.在Struts2框架的配置文件中引用OGNL表达式,例如

OGNL表达式和EL表达式的区别

1.OGNL表达式必须配合Struts2的标签使用,而EL可以单独使用。

类型转换器:

八种基本类型和String、Date、array、List、Map会自动进行转换(因为内置了类型转换器)

更复杂的类型需要自定义类型转换器。

写得太长了,类型转换器另开一篇单独写吧。

参考:(此处替换为类型转换器博文地址)

三、OGNL工作原理?

暂无、

Struts2之OGNL的更多相关文章

  1. Struts2的OGNL表达式语言

    一.OGNL的概念 OGNL是Object-Graph Navigation Language的缩写,全称为对象图导航语言,是一种功能强大的表达式语言,它通过简单一致的语法,可以任意存取对象的属性或者 ...

  2. Struts2对Ognl的支持

                                                      Struts2对Ognl的支持 一. 写作背景 由于工作性质的变化,最近一直在研究struts2,从 ...

  3. Struts2的OGNL标签详解

    一.Struts2可以将所有标签分成3类: UI标签:主要用于生成HTML元素的标签. 非UI标签:主要用于数据库访问,逻辑控制等标签. Ajax标签:用于Ajax支持的标签. 对于UI标签,则有可以 ...

  4. struts2之OGNL和struts2标签库和ValueStack对象

    OGNL简介: (1)OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一个开源项目.  struts2框架默认就支持Ognl表达式语言(所以 ...

  5. 2018.11.25 struts2与OGNL表达式的结合(高级)

    两者的结合原理 底层源码分析 栈原理 先进后出 我们的valuestack其实是一个接口 在实现类中有这个参数 CompoundRoot的类继承的是ArrayList,具体实现弹栈和压栈的方法具体实现 ...

  6. 【Struts2】Ognl与ValueStack

    一.OGNL 1.1 概述 1.2 OGNL 五大类功能 1.3 演示 二.ValueStack 2.1 概述 2.2 ValueStack结构 2.3 结论 2.3 一些问题 三.OGNL表达式常见 ...

  7. JavaWeb_(Struts2框架)Ognl小案例查询帖子

    此系列博文基于同一个项目已上传至github 传送门 JavaWeb_(Struts2框架)Struts创建Action的三种方式 传送门 JavaWeb_(Struts2框架)struts.xml核 ...

  8. Struts2与OGNL的联系

    1.Struts与OGNL的结合原理 (1)值栈: OGNL表达式要想运行就要准备一个OGNLContext对象,Struts2内部含有一个OGNLContext对象,名字叫做值栈. 值栈也由两部分组 ...

  9. Struts2之OGNL与ValueStack

    时间:2017-1-12 12:02 --OGNL1.OGNL表达式是什么    OGNL的全称是Object-Graph Navigation Language的缩写,中文名是对象图导航语言,它是一 ...

随机推荐

  1. iOS开发——UI基础-KVO

    KVO == Key Value Observing 作用: 可以监听某个对象属性的改变 一.使用KVO Person *p = [Person new]; p.name = @"chg&q ...

  2. Unity 视频播放杂谈

    http://www.cnblogs.com/zsb517/p/4060814.html 背景:      游戏机中想加入舞蹈元素,最先的想法是开发舞蹈游戏,然后通过动画来表现舞蹈,给用户提供舞蹈教学 ...

  3. Android学习笔记(十七)——数据库操作(下)

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 这一次我们来试一试升级数据库,并进行数据库的CRUD操作,其中, C 代表添加(Create) ,R 代表查询 ...

  4. [COJ0985]WZJ的数据结构(负十五)

    [COJ0985]WZJ的数据结构(负十五) 试题描述 CHX有一个问题想问问大家.给你一个长度为N的数列A,请你找到两个位置L,R,使得A[L].A[L+1].…….A[R]中没有重复的数,输出R- ...

  5. [KOJ95603]全球奥运

    [COJ95603]全球奥运 试题描述 一个环形的图中有N个城市,奥运会重要项目就是传递圣火,每个城市有A[i]个圣火,每个城市可以向它相邻的城市传递圣火(其中1号城市可以传递圣火到N号城市或2号城市 ...

  6. 2016年10月12日--string、Math类、Random随机数、DateTime、异常保护

    string string.length; //得到string长度 string.Trim(); //去掉string前后的空格 string.TrimStart(); //去掉string前的空格 ...

  7. Python自动化之pickle和面向对象初级篇

    pickle模块扩展 1 pickle之文件操作 示例1 with open("test", 'rb') as f: lines = f.readlines() print(pic ...

  8. Linux的目录结构

    学习Linux这么久,对Linux的目录的目录结构进行整理总结一下. 以下是对这些目录的解释: /bin:bin是Binary的缩写, 这个目录存放着最经常使用的命令. /boot:这里存放的是启动L ...

  9. MySQL 编程的6个重要的技巧

    一.每一行命令都是用分号(;)作为结束 对于MySQL,第一件你必须牢记的是它的每一行命令都是用分号(;)作为结束的,但当一行MySQL被插入在PHP代码中时,最好把后面的分号省略掉,例如:   二. ...

  10. 【GoLang】panic defer recover 深入理解

    唉,只能说C程序员可以接受go的错误设计,相比java来说这个设计真的很差劲! 我认为知乎上说的比较中肯的: 1. The key lesson, however, is that errors ar ...