ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Struts 2学习笔记:OGNL

Struts 2学习笔记:OGNL

OGNL

OGNL全称:Object-Graph Navigation Language(对象 图 导航 语言)
工作在视图层,用来取代页面中的Java脚本简化数据的访问操作
与JSP2.0中内置的EL相比它们同属于表达式语言:用于进行数据访问,但是OGNL的功能更为强大.
提供了很多日所不具备的功能,如强大的类型转换功能, 访问方法, 操作集合对象等…
OGNL是一 种强大的技术,被集成在Struts2框架中用来帮助实现数据的传输和类型转换
OGNL是基于字符串的HTTP输入/输出和基于Java 对象的内部处理之间的“黏合剂它的功能非常强大。
学习了OGNL这个强大的工具,开发效率会成倍地提升。
(但目前SSH架构不常用, 一般用于老项目的维护) OGNL目前只能在Struts2中使用...所以..有点难受
OGNL在框架中主要做两件事情:
1. 表达式语言

  1. 在视图层将 数据——页面—传入——框架且从框架获取输出数据生成页面的过程中:
    OGNL提供了简单的语法:将表单 或 Struts2标签 与Java各种类型的数据绑定起来。
  2. 如:我们前面学习的页面中< input type=" textname= “user userName” />的输入操作:
    对应Action 类中User对象的name属性。在OGNL的帮助下:
    输入提交时,数据从请求参数转移到了Action 的JavaBean属性上:
    输出时,数据又从这些属性转移到页面上展示;<s:property value=“user.userName”/>

2. 类型转换器
除了表达式语言,我们还会用到OGNL类型转换器。
每次数据流入和流出框架,页面中字符串类型的数据和Java数据类型之间都会发生转换。

OGNL流程详解:

图解1.0

数据流入

如上图解:
数据最开始来自页面InputForm.html表单已 neme OGNL形式提交数据,
请求以HTTPServletRequest进入框架(Struts2):请求参数 name-value 以字符串类型进行传递的..
Struts2 和 OGNL对请求参数进行处理:类型转换并将数据匹配赋值最后保存至值栈。

数据流出

数据流出 和 流入类似, 只不过顺序是反过来的.
当Action 完成业务处理后, 最终会返回一个结果…并生成一个页面。在处理请求过程中,所以业务对象都保留在值栈中

ValueStack:值栈

值栈类似于一个容器:存储区域,用来保存Mdel(模型)对象:用于在页面控制器之间数据传递交互展示
值栈具有栈的特征:可以存储,多个对象, 以先入后出原则存储;从上到下的顺序读取;

框架在处理每个请求时都会 创建该请求对应的运行环境。并将请求对应的Action对象放入其中。
如上图:
Action 对象被放在一个叫作值栈(ValueStack) 的对象中。先入后出存放
params拦截器(后面章节将会学习)会负责将来自HtSerleRequest对象的数据传到值栈上。


在生成页面的过程中,页面标签可以访问值栈,并通过OGNL表达式获取对象的属性值。
图解1.0 ResultPage.jsp页面:
property 标签利用表达式user age,在值栈中取到了user 对象age 属性的值。
同样地。这时也需要进行类型转换,将整型转换为字符串。年龄:20”
在查找OGNL表达式对应的属性时,会从栈顶开始依次往下, 查找一直到栈底…从上往下原则查找..

OGNL表达式:

其实并没有什么, 特别之前其实就用过:点击

访问bean属性:

<s:property value=“Action中属性名” />
对象类型:<s:property value=“Action中对象名.对象属性” /> 即可~

访问集合对象:

<s:iterator value=” ” />

<!--表达式对于集合/数组类型遍历方式: 之前已经学习过了... --><!-- 迭代 集合/数组--><s:iteratorvalue="集合/数组"><s:property/></s:iterator><!-- 迭代对象集合 --><s:iteratorvalue="对象集合"><s:propertyvalue="对象属性名1"/><s:propertyvalue="对象属性名2"/><s:propertyvalue="对象属性名.."/></s:iterator>

使用 Struts 2的iterator标签送代一个集合或者数组、其中value属性用于指定要迭代的集合属性。
iteator标签在迭代过程中,会把选代的每一个对象暂时压入值栈的栈顶。
这样在该标签内部可以直接访问元素对象的属性和方法。
即可以像示例中那样使用不带value属性的propery标签输出数据。
对于集合对象的访问,在开发过程中往往还有以下几种情况。
访问列表或者数组的某一个元素,可以用属性名[index]的方式。
访问Map的某一个元素,可以用 属性名[‘Key’] 的方式,
通过size 或者length来访问集合的长度

访问ActionContext中的数据:

OGNL 表达式是面向值栈中的对象来解析的
OGNL表达式可以针对运行环境中的任意一个对象进行解析,值栈只是运行环境中存放的对象;
ActionContext
也称为运行上下文。AitionContext 中包含了框架处理一个请求时会用到的所有的数据。session aplication…
通常与业务相关的数据是放在值栈内的。ActionContext 中的数据存放如 requset作用域中数据
下面对ActionContex中的对象进行说明。

  1. aplication,用于访问aplication属性。例如,#application.username ,相当于调用aplication.getAtribute(“username”);

  2. session,用于访问session属性。例如,#session.username ,相当于调用session.getAtribute(“username”);

  3. request,用于访问request属性。例如,#request.username ,相当于调用request.getAtribute(“username”);

  4. parameters,用于访问parameters属性。例如,#parameters.username ,相当于调用parameters.getAtribute(“username”);

  5. attr: 按照 pageContext-request-session-aplication 顺序访问其属性。 #attr.username

作用域存储的对象并不是在值栈中, 取值时候, 通过 #作用域.对象名 访问;
对于已经声明的值栈对象如果字符展示:
如果页面中想要输出 username但是值栈中存储了对于的值, 而想到的只是 username字符;
可以使用:<s: property value=“%{username}” >
%{…可以使值栈/栈文本中关键字字符输出.}

查看ActionContext中的数据:

Struts2特意提供了<s:debug/>标签。
会在页面中生成一个超链接, 单击会出现如下图的 ValueStack(值栈) 和 StackContext(栈文本/作用域) 资源。
方便用户开发者,查看参数…数据方便找错。

类型转换:

在基于HTTP协议的We应用中,客户端请求的所有内容(如在表单中输入的姓名、年龄、生日等)
均以文本编码的方式传输到服务器端,但服务器端的编程语言(如 Java)有着丰富的数据类型:int boolean Date…
在Servlet 中,类型转换工作是由开发者自己完成的。
例如:可以通过下面的语句完成字将串类型 和 整型,字符串类型和日期类型之间的类型转换。

  1. //Servlet 原生写法, 获取表单数据…学过JDBC Servlet 都知道的贼难受~
    String age = request.getParameter(“age”);
    int age = Integer.parseInt(age); //还要做强制类型转换.

可以看出,类型转换的工作是必不可少的、重复性的。
加果有一个优秀的类型转换机制, 必将大大节省开发时间,提高开发效率。
Struts2 提供了非常强大的类型转换器, 功能;提供了多种内置类型转换器完全透明的对请求数据进行转换
Struts2 扩展性: 如果内置的转换器不能满足应用需要, 开发者可以简单开发出自己的类型转换器

内置转换器:

对于大部分的常用类型,开发人员在开发过程中不需要自己编码实现类型转换。
这是因为在Struts2框架中可以完成大多数常用的类转换。
实现字符串类型和常用类型之间的转换。


具体的转换器种类包括

  1. String: 将int, long. double, boolean, String类型的数组或者java, util. Date类型转换为字串。
  2. boolean/Boolean:在字符串和布尔值之间进行转换。
  3. char/Character:在字符串和字符之间进行转换。
  4. int/Integer. float/Float. long/Long. double/Double:在字符串和数值型数据之间进行转换。
  5. Date:在字符串和日期类型之间进行转换。具体输入/输出格式与当前的Local( 中:当地的 格式的 )相关。
  6. 数组和集合:在字符串数组和数组对象、集合对象之间进行转换。
    …

实例代码接着以往项目扩展:学习

1.原始类型和包装类

这种情况比较简单,我们一直使用的也是这种方式。
HelloWord.jsp

<!-- 省略JSP其它部分... --><body><p>${msg }</p><!-- 单纯直接使用EL是获取不到滴...EL是获取作用域中的数据,这里都没有存哦~ --><h1><s:propertyvalue="msg"default="msg为null展示默认数据"/><!-- 使用Struts2标签库:在值栈中从上往下找到,对应属性并输出其值... --><!-- 如果没有default 且msg为null 则空字符展示--></h1><!-- 表单: name="name" 可以用于传递参数.. --><formaction="hello"method="post">名字:<inputtype="text"name="name"><inputtype="submit"value="提交"></form></body>

HelloWordAction.java

importcom.opensymphony.xwork2.Action;publicclassHelloWordActionimplementsAction{//获取页面表单名字;privateStringname;//等会向页面输出的变量...privateStringmsg;//实现方法@OverridepublicStringexecute()throwsException{System.out.println(name);msg="hello:"+name;//拼接向页面输出的信息;return"hello";//返回 "逻辑视图名",struts.xml根据:<result name="返回的逻辑视图名" >找到最后的响应页面</result>}//省略get/set...}

2.多值类型请求参数的处理…

如果在请求中出理现多个值对应同一个请求参数的情况。补充一下:SpringMVC中参数传递也可通过类似方式…
Struts 2会将这样的数据转换为面向集合的数据类型:数组、集合…

数组

添加工程:params.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags" %><!-- 别忘了引入Struts2标签库 --><% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %><!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><basehref="<%=basePath%>"><title>My JSP 'params.jsp' starting page</title><metahttp-equiv="pragma"content="no-cache"><metahttp-equiv="cache-control"content="no-cache"><metahttp-equiv="expires"content="0"><metahttp-equiv="keywords"content="keyword1,keyword2,keyword3"><metahttp-equiv="description"content="This is my page"></head><body><!-- 多值类型请求参数的处理.. --><s:formaction="params"method="post"><!-- 一个表单中,多个同名name 的表单--><!-- 数组:SZ1 --><s:textfieldname="sz1"value="A"></s:textfield><s:textfieldname="sz1"value="B"></s:textfield><s:textfieldname="sz1"value="C"></s:textfield><!-- 数组:SZ2 还可以在,在声明时候指定数组长度及下标! --><s:textfieldname="sz2[0]"value="0"></s:textfield><s:textfieldname="sz2[4]"value="4"></s:textfield><s:textfieldname="sz2[1]"value="1"></s:textfield><!-- 对象类型:貌似不支持对象数组... --><s:textfieldname="users.userName"value="ABC"></s:textfield><s:textfieldname="users.userName"value="ACB"></s:textfield><s:textfieldname="users.userName"value="BCA"></s:textfield><s:submitvalue="提交"></s:submit></s:form></body></html>

ParamsAction.java

importcom.opensymphony.xwork2.Action;publicclassParamsActionimplementsAction{//全局变量..privateString[]sz1;privateint[]sz2=newint[4];//对于指定数组长度的数组,页面可以指定给数组下标赋值,需要手动实例化!//相当于:Action中定义好了一个数组及长度,页面将请求数据匹配的根据下标一个个往里放;//对于页面参数 数组元素越界的是放不进的, 页面中的 sz2[4]放不进去... 除非改变数组长度为5;@OverridepublicStringexecute()throwsException{//请求进入execute();//循环遍历数组..//SZ1for(inti=0;i<sz1.length;i++){System.out.println(sz1[i]);}//SZ2for(inti=0;i<sz2.length;i++){System.out.println(sz2[i]);}return"params";}//省略get/set...}
集合

按照与数组类似的方式,
Struts 2支持将一系列请求参数自动转换到各种集合类型的属性。
以List为例来讲解使用List 与使用数组几乎相同仅有的不同是集合类型可以借助泛型实现;

修改ParamsAction.java

importjava.util.List;importcom.opensymphony.xwork2.Action;importcom.wsm.entity.User;publicclassParamsActionimplementsAction{//全局变量..privateListsz1;privateList<Integer>sz2;privateList<User>users;//对于集合类型,不需要进行初始化.//如果没有泛型说明,集合保存的类型都是String//如果泛型指定了类型为:JavaBean类型,Struts2会自动创建指定类型的对象为List元素存储..@OverridepublicStringexecute()throwsException{//SZ1for(Objectsz:sz1){System.out.println(sz);}//SZ2for(Integersz:sz2){System.out.println(sz);}for(Useru:users){System.out.println(u.getUserName());}return"params";}//省略get/set...}

自定义转换器:

随着互联网的不断普及,用户体验已经成为网站吸引用户的主要手段。
如:在日期输入框中输入“1993/12/24”或者“ 193 年12月24日”:都可以而无须指定某种特定的时间格式。


对于Java 的基本数据类型及一些系统类(如Date 类、集合类), Sruts 2提供了内置类型转换功能。
但也有一定的限制 :
很显然,对于内置的日期类型转换对输入/输出格式是有要求的。如果希望 Struts 2可以更智能;

1. 创建自定义类型转换器

Struts 2提供了一个开发人员编写自定义类型转换器使用的基类:
继承org.apache.struts2.util.StrutsTypeConverter抽象类
定义了两个抽象方法,用于不同的转换方向分别如下:

  1. 将一个或多个字符串值转换为指定的类型
    public Object convertFromString(Map context, String[] values, Class toType);

  2. 将指定对象转化为字符串
    public String convertToString(Map context, Object object);

参数:context 是表示OGNL上下文的Map对象
参数:values是要转换的字符串值;
参数:toType是要转换的目标类型;
参数:object是要转换的对象。

如果继承StrutsTypeConverter抽象类编写自定义类型转换器,需要重写以上两个抽象方法

2. 配置自定义类型转换器

自定义类型转换器后,还必须进行配置,将类型转换器和某个类或属性通过.properties文件建立关联;
Struts 2提供了两种方式来配置自定义类型转换器

  1. 应用于全局范围的类型转换器:(整个项目的指定类型都会进行类型转换…)
    要指定应用于全局范围的类型转换器,需要在
    classpath的根目录下(通常是WEB目录,对应开发时的 src目录)下创建一个名为:
    xwork-conversion.properties属性文件,其内容为:要转换类全名=类型转换其类全名

  2. 应用于特定类的类型转换器:(仅在该Action下的类型作类型转换)
    要指定应用于特定类的类型转换器、需要在特定类(一般都是Action)的相同目录下创建一个名为:
    ClassName--conversion.properties属性文件, (ClassName代码实际类名)
    其内容为:特定转换类的对应属性名=类型转换其类全名

实现

本章以自定义的来进行演示…日期格式多种格式

没有标注的, 不用管是上面的其它案例…


页面Date.jsp

<!-- 省略JSP --><body><formaction="godate"method="post"><p>请输入日期:<inputname="birthday"></p><inputtype="submit"value="提交"></form><!-- 别忘了Struts2类库 --><s:propertyvalue="birthday"/></body>

DateAction.java

publicclassDateActionimplementsAction{privateDatebirthday;@OverridepublicStringexecute()throwsException{System.out.println(birthday);return"success";//别忘了配置struts.xml}//省略get/set..}

创建一个 com.zb.converter 包专门存放类型转换器:
转换器:DateConverter.java

importjava.text.DateFormat;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.Map;importorg.apache.struts2.util.StrutsTypeConverter;importcom.opensymphony.xwork2.conversion.TypeConversionException;publicclassDateConverterextendsStrutsTypeConverter{//支持多种日期格式,常量数组[]privatefinalDateFormat[]dfs={newSimpleDateFormat("yyyy年MM月dd日"),newSimpleDateFormat("yyyy/MM/dd"),newSimpleDateFormat("yyyy-MM-dd"),//省略其它...};@Override//将指定格式字符串转换为日期类型publicObjectconvertFromString(Mapcontext,String[]value,Classtotype){StringdateStr=value[0];//获取日期字符串for(inti=0;i<dfs.length;i++){//遍历日期支持格式,进行转换..try{returndfs[i].parse(dateStr);//SimpleDateFormat中的parse方法,把String型的字符串转换成特定格式的date类型}catch(Exceptione){continue;//转换失败,结束本次循环..}}//如果遍历完毕后仍没有转换成功,表示出现转换异常thrownewTypeConversionException();}@Override//将日期转换为指定的字符串格式publicStringconvertToString(Mapcontext,Objectobject){Datedate=(Date)object;//输出格式是yyyy-MM-ddreturnnewSimpleDateFormat("yyyy-MM-dd").format(date);}}

配置.properties
DateAction-conversion.properties

birthday=com.zb.converter.DateConverter
#birthday 特定类中的要转换类型的属性名;
#com.zb.converter.DateConverter 指定转换器类的全路径…

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进