day03
上传下载
1 上传下载组件介绍
l jspSmartUpload(model1的年代);
l apache-commons-fileupload,Struts2默认上传组件;
l Servlet3.0使用的Part,但Servlet3.0还没有普及;
l COS,Struts2支持,不过已经停止更新很久了;
l pell,Struts2支持。
2 fileUpload的拦截器
Struts2默认使用的是commons-fileUpload组件完成上传的,使用Struts2会大量简化上传文件的开发。这一工作由fileUpload拦截器来完成。它会查看当前请求的enctype是否为multipart/form-data,如果不是就会直接“放行”;如果是,那么它会去解析表单,然后把解析的结果传递给Action的属性!
fileUpload拦截器对会对Action提供很大的“帮助”,同时它也会对Action提出一些“小小的要求”。Action需要提供3个属性:
l File fieldName
l String fieldNameContentType
l String fieldNameFileName;
三个属性的前缀都(fieldName)必须与文件表单项名称一致,例如有文件表单项内容为:<input type=”file” name=”myUpload”/>,其中表单项名称为:myUpload,那么Action就必须要有如下3个属性:
l private File myUpload
l private String myUploadContentType
l private String myUploadFileName
3 演示上传
上传文件首先我们需要给出一个页面,页面中要有一个表单:
upload.jsp
<form action="<c:url value='/UploadAction.action'/>" method="post" enctype="multipart/form-data"> 用户名: <input type="text" name="username"/><br/> 文 件:<input type="file" name="myUpload[崔1] "/><br/> <input type="submit" value="Submit"/> </form> |
UploadAction
public class UploadAction extends ActionSupport { private String username;
public void setUsername(String username) { this.username = username; }
private File myUpload; private String myUploadContentType; private String myUploadFileName; [崔2] private String savepath = "/WEB-INF/uploads";
public void setMyUpload(File myUpload) { this.myUpload = myUpload; }
public void setMyUploadContentType(String myUploadContentType) { this.myUploadContentType = myUploadContentType; }
public void setMyUploadFileName(String myUploadFileName) { this.myUploadFileName = myUploadFileName; }
public String execute() throws Exception { System.out.println(username); System.out.println(this.myUploadContentType); System.out.println(this.myUploadFileName); System.out.println(this.myUpload.getAbsolutePath()); this.savepath = ServletActionContext.getServletContext().getRealPath(savepath); File destFile = new File(savepath, myUploadFileName); FileUtils.copyFile(myUpload, destFile); return NONE; } } |
struts.xml
<package name="s8" namespace="/" extends="struts-default"> <action name="UploadAction" class="cn.itcast.upload.action.UploadAction"> </action> </package> |
4 上传配置
可以通过Struts2的常量来完成对上传的配置,下面是与上传相关的常量:
l struts.multipart.parser:指定使用的上传组件,默认值为jakarta,表示使用commons-fileupload组件,Struts2还支持cos和pell;
l struts.multipart.saveDir:临时目录,如果没有指定临时目录,那么临时文件会在Tomcat的work目录中;
l struts.multipart.maxSize:整个大小限制,默认值为2097152,即2M。注意,这个限制是整个请求的大小,而不是单一文件的大小。
当上传的表单超出了限制时,拦截器会向actionError中添加错误信息!当执行wokflow拦截器时,会发现actionError中存在错误,这时就会跳转到input视图,所以我们需要为Action指定input视图。
fileUpload拦截器也有3个参数,我们可以给fileUpload拦截器配置这3个参数:
l maximumSize:上传的单个文件的大小限制;
l allowedTypes:允许上传文件的类型,多个类型以逗号隔开;
l allowedExtensions:允许上传文件的扩展名,多个扩展名以逗号隔开;
<struts> <constant name="struts.devMode" value="true" /> <constant name="struts.multipart.maxSize" value="1048576[崔3] " /> <package name="s8" namespace="/" extends="struts-default"> <action name="UploadAction" class="cn.itcast.upload.action.UploadAction"> <result name="input">/demo1/upload.jsp</result> <param name="savepath">/WEB-INF/uploads</param> <interceptor-ref name="defaultStack[崔4] "> <!-- 限制单个文件大小上限为512K --> <param name="fileUpload.maximumSize">524288[崔5] </param> <param name="fileUpload.allowedExtensions">jpg,png,bmp[崔6] </param> </interceptor-ref> </action> </package> </struts> |
5 上传相关的错误信息国际化
在上传文件时如果出现错误,那么在input视图显示的错误信息都是英文的。如果想替换这些信息,需要知道这些错误信息的资源key,然后在我们自己的国际化资源文件中指定这些key的新值即可。
与上传相关的错误信息都在org.apache.struts2包下的struts-message.properties文件中。
struts.messages.error.uploading[崔7] =Error uploading: {0} struts.messages.error.file.too.large[崔8] =The file is to large to be uploaded: {0} "{1}" "{2}" {3} struts.messages.error.content.type.not.allowed[崔9] =Content-Type not allowed: {0} "{1}" "{2}" {3} struts.messages.error.file.extension.not.allowed[崔10] =File extension not allowed: {0} "{1}" "{2}" {3} struts.messages.upload.error.SizeLimitExceededException[崔11] =Request exceeded allowed size limit! Max size allowed is: {0} but request was: {1}! |
我们可以在src下res.properties文件,在这个文件中对象以上资源key进行替换。然后在struts.xml文件中给出<constant name="struts.custom.i18n.resources" value="res/">即可。
6 多文件上传
当需要上传多个文件时,如果每个<input type=”file”>对应Action的3个属性,那么这会使Action的属性过多的现象。处理这一问题的方法是在表单中设置所有<input type=”file”>的名称为相同名称,然后在Action中给出数组属性即可。
uploads.jsp
<s:fielderror /> <s:actionerror/> <form action="<c:url value='/UploadsAction.action'/>" method="post" enctype="multipart/form-data"> 用户名: <input type="text" name="username"/><br/> 文 件1:<input type="file" name="myUpload"/><br/> 文 件2:<input type="file" name="myUpload"/><br/> 文 件3:<input type="file" name="myUpload"/><br/> [崔12] <input type="submit" value="Submit"/> </form> |
UploadsAction
public class UploadsAction extends ActionSupport { private File[] myUpload; private String[] myUploadContentType; private String[] myUploadFileName; [崔13] private String savepath; public void setSavepath(String savepath) { this.savepath = savepath; } public void setMyUpload(File[] myUpload) { this.myUpload = myUpload; } public void setMyUploadContentType(String[] myUploadContentType) { this.myUploadContentType = myUploadContentType; } public void setMyUploadFileName(String[] myUploadFileName) { this.myUploadFileName = myUploadFileName; } public String execute() throws Exception { savepath = ServletActionContext.getServletContext().getRealPath(savepath); for(int i = 0; i < myUpload.length; i++) { System.out.println("文件名:" + myUploadFileName[i]); System.out.println("文件类型:" + myUploadContentType[i]); FileUtils.copyFile(myUpload[i], new File(savepath, myUploadFileName[i])); } return NONE; } } |
其中Struts2也允许我们使用List类型来代替数组属性。
UploadsAction.java
public class UploadsAction extends ActionSupport { private List<File> myUpload; private List<String> myUploadContentType; private List<String> myUploadFileName; … } |
文件下载
1 回忆JavaWeb文件下载
其实也可以让用户通过浏览器直接访问下载资源,但这样程序无法控制下载流程了。所以在JavaWeb中通过Servlet完成下载。
下载是与响应相关的操作,在JavaWeb中下载要做如下操作:
l 设置Content-type响应头,其实这个头你不设置浏览器也会自己通过文件名来识别;
l 设置Disposition响应头,它默认值是inline,表示在页面中打开,下载时应该设置为attachment,表示让浏览器弹出下载框;
l 处理文件中乱码问题;
l 获取被下载文件,把被下载文件的内容写入到response.getOutputStream()流中;
2 Struts2结果类型之stream
在Struts2中,所有与响应相关的工作都由<result>来处理。我们知道,<result>的默认类型为dispatcher,表示请求转发到JSP,如果设置<result>的type为redirect,那么表示重定向。也就是说所有和响应相关的事情都交给<result>元素指定的类来处理。
Struts2专门提供了一个stream类型的Result,它用来完成下载。也就是说在下载时我们的Action中给出的<result>的type类型应该指定为stream。stream结果对应的结果类型为StreamResult类!这个类来完成设置响应头,以及向response.getOutputStream()中写入数据。
StreamResult类对Content-type提供的默认值为text/plain,这不是我们想要的,所以我们需要给StreamResult设置新的Cotnent-type。
StreamResult类对disposition头提供的默认值为inline,这也不是我们想要的,所以我们也需要给它设置为attachment。
在StreamResult类中提供了两个属性:contentType和contentDisposition,我们需要给这两个属性赋值,来替换默认值。
StreamResult类还要求Action提供一个getInputStream()方法,它会通过这个方法来获取一个InputStream对象,它会把这个流中的内容写入到response.getOutputStream()中。
dowload.jsp
<a href="<c:url value='/DownloadAction.action'/>">我的拍子</a> |
DownloadAction
public class DownloadAction extends ActionSupport { public InputStream getInputStream[崔14] () throws FileNotFoundException { return new FileInputStream("F:\\我的拍子.jpg"); } public String execute() throws Exception { return SUCCESS; } } |
struts.xml
<action name="DownloadAction" class="cn.itcast.download.action.DownloadAction"> <result type="stream"> <param name="contentType">image/jpeg</param>[崔15] <param name="contentDisposition">attachment;filename=a.jpg</param>[崔16] </result> </action> |
3 OGNL表达式获取Action属性值
上例中要下载的文件是硬编码,文件的ContentType类型是硬编码,下载框中的文件名也是硬编码,这些都是需要处理的。
<action name="Download1Action" class="cn.itcast.download.action.Download1Action"> <param name="downloadDir">/WEB-INF/downloads</param> <result type="stream" name="success"> <param name="contentType">${contentType}[崔17] </param> <param name="contentDisposition">attachment;filename=${filename}[崔18] </param> </result> </action> |
Download1Action
public class Download1Action extends ActionSupport { private String downloadDir; private InputStream inputStream; private String filename;
public void setDownloadDir[崔19] (String downloadDir) { this.downloadDir = ServletActionContext.getServletContext().getRealPath(downloadDir)[崔20] ; }
public void setFilename[崔21] (String filename) throws UnsupportedEncodingException { // 处理GET请求中文编码问题 this.filename = new String(filename.getBytes("iso-8859-1"), "utf-8")[崔22] ; }
public String getFilename[崔23] () throws UnsupportedEncodingException { // 在下载框中显示的文件名需要处理编码问题 return new String(filename.getBytes("GBK"), "iso-8859-1")[崔24] ; }
public String getContentType[崔25] () { // 文件的MIME类型 return ServletActionContext.getServletContext().getMimeType(filename);[崔26] }
public InputStream getInputStream[崔27] () throws FileNotFoundException { return this.inputStream; }
public String execute() throws Exception { File file = new File(downloadDir, filename);[崔28] inputStream = new FileInputStream(file); return SUCCESS; } } |
OGNL表达式
1 什么是OGNL语言
你还记得 EL表达式语言么?没错OGNL也是表达式语言,不过它比EL可强大太多了。OGNL的全称为Object Graphic Navigation Language(对象图导航语言)。它是Struts2的默认表达式语言!
使用OGNL需要导入OGNL的Jar包:ognl-3.0.5.jar
OGNL的功能介绍:
l EL一样的JavaBean导航;
l 调用对象方法;
l 调用类的静态方法;
l 索引数组元素;
l 操作集合;
- EL可以读取JavaBean属性,OGNL使用起来与EL表达式有点相似
- EL操作的数据是域对象,OGNL操作的数据OgnlContext,可以在OgnlContext指定一个root对象,在不指定操作的对象时默认为root对象。
2 OGNL入门
为了可以执行OGNL表达式,所以我们必须要先学会怎么来使用Ognl类。为了更好的演示Ognl我们需要先创建一些JavaBean用来做测试。
public class Address { private String country; private String city; private String street; …… } |
public class Employee { private String name; private double salary; private Address address; …… } |
我们可以回忆一下EL的使用:${name},它会在所有域中去查找name这个域属性。也就是说,EL表达式也需要一个查询的范围。相同的道理,OGNL也需要一个查找的范围,它查询的范围是OgnlContext。OgnlContext其实就是一个Map,你可以向其内添加N个键值对。它还可以指定多个键值中的一个为根对象(root)!
OgnlContext cxt = new OgnlContext();[崔29] Address add1 = new Address("中国", "北京", "大北窑"); Employee e1 = new Employee("张三", 10000, add1); Address add2 = new Address("中国", "北京", "西三旗"); Employee e2 = new Employee("李四", 12000, add2); cxt.put("emp1", e1); cxt.put("emp2", e2); |
演示OGNL表达式需要使用Ognl类,我们先从它的getValue()方法开始。这个方法需要三个参数,分别是:
l OGNL表达式;
l 上下文对象;
l 根对象。
String name = (String)Ognl.getValue("#emp2.name", cxt, cxt.getRoot()[崔32] ); System.out.println(name)[崔33] ; |
其中“#emp2.name”是OGNL表达式,查找上下文中的元素必须以“#”开关,其中emp2对应上下文中的键,“.name”表示调用该元素的getName()方法,即JavaBean导航。如果它这个OGNL表达式翻译成Java代码,即:((Employee)cxt.get(“emp2”)).getName()。
String name = (String)Ognl.getValue("name", cxt, cxt.getRoot()); System.out.println(name); |
其中“name”为OGNL表达式,当没有使用“#”开始时,表达在使用根对象。所以“name”表达式可以理解为:cxt.getRoot().getName()。即输出“张三”。
String name = (String)Ognl.getValue("address.street", cxt, cxt.getRoot()); System.out.println(name); |
因为没有使用“#”开头,那么就是在使用根对象,即cxt.getRoot().getAddress().getStreet(),即输出“大北窑”。
- OgnlContext就是一个Map,指定操作的对象时需要使用“#”为前缀,后缀为Map的key为后缀;
- 当没有使用“#”时,表示操作的对象为root对象。
3 OGNL常量
OGNL也可以使用常量:
l 字符串常量:使用双引号,或单引号。如果字符串只有唯一字符时,必须使用双引号;
l 字符常量:使用单引号;
l 数值常量:与Java相同;
l boolean常量:与Java相同。
l OGNL也支持null常量。
- “hello”是字符串常量,’hello’也是字符串常量,’h’是字符常量;
- true、false为boolean类型常量;
- 1.0、100为数值类型常量;
- null也是常量。
4 运算符
OGNL运算符基本与Java相同,但它还有一些特殊的运算符:
l 逗号(,):用逗号分隔多个OGNL表达式,这些表达式会依次被执行,而最后一个表达式的值为整个表达式的值;
l 大括号({}):用来创建列表,例如:{‘zhangSan’,’liSi’,’wangWu’}表示一个List;
l in和not in:in表示判断某个值是否在集合中,not in表达某个值是否不在集合中。
double s = (Double)Ognl.getValue("name,#emp2.name,salary", cxt, cxt.getRoot()); System.out.println(s); |
上面使用了逗号表达式,一共是3个OGNL表达式,它们会被依次运行,但整个整句表达式的值为最后一个OGNL表达式的值,即salary的值。
boolean bool = (Boolean)Ognl.getValue("name in {'张三','李四'}", cxt, cxt.getRoot()); System.out.println(bool); |
表达式“name in {‘张三’,’李四’}”首先需要得到name的结果,即根对象的name属性值,然后判断这个值是否在列表中存在,所以返回为true。
- 逗号运算符很少使用,它的作用是把多个OGNL表达式连接成一个OGNL表达式,每个子表达式会从左到右依次运行,完整表达式的值来最右边表达式的值;
- in和not in,用于判断某个值在集合中是否存在;
- {}可以用来创建List集合。
4 设置JavaBean属性值(重点)
OGNL表达式不只是可以获取JavaBean属性值,不可以设置JavaBean属性值。但这需要使用Ognl类的setValue()方法。
Ognl.setValue("#emp2.address.street", cxt, cxt.getRoot(), "育新"); System.out.println(Ognl.getValue("#emp2.address.street",cxt, cxt.getRoot())); |
表达式指定的是cxt中键为emp2的对象,然后导航到它的address属性,再导航到street属性,最终给该属性设置值为“育新”。
- “name”表示root对象的name属性;
- “address.city”表示root对象的address属性的city属性;
- “#emp.salary”表示上下文中key为emp的对象的salary属性。
5 调用对象方法
OGNL不只可以获取JavaBean属性,还可以调用对象方法。
String s = (String)Ognl.getValue("'OGNL'.toLowerCase()", cxt, cxt.getRoot()); System.out.println(s); |
表达式“’OGNL’.toLowerCase()”表示调用字符串OGNL的toLowerCase()方法,千万要小心,在OGNL上添加单引或双引,指定其为字符串常量,不然会被误会来根对象的属性的。
当然,不只是可以调用字符串的方法,什么对象的方法都可以调用。
6 静态方法和静态属性
OGNL还可以调用类的静态方法,以及读取静态属性。操作静态方法或属性时,需要使用完整的类名称,并且需要使用两个“@”把类名称包含起来!
String s = (String) Ognl.getValue( "@java.lang.String@format('%tF %<tT', new java.util.Date())", cxt, cxt.getRoot()); System.out.println(s); |
上面的表达式可以翻译成:java.lang.String.format(“%tF %<tT”, new java.util.Date())
OGNL对java.lang.Math对有特殊的待遇,如果是操作Math类的静态属性或静态方法,可以不用给出类名称,但“@”不可以省略。
int max = (Integer)Ognl.getValue("@@max(1,2)", cxt, cxt.getRoot()); System.out.println(max); double pi = (Double)Ognl.getValue("@@PI", cxt, cxt.getRoot()); System.out.println(pi); |
7 操作数组
OGNL可以用来操作数组,使用下标法即可:
String[] names = {"zhangSan", "liSi", "wangWu"}; cxt.put("ns", names); String name = (String)Ognl.getValue("#ns[0]", cxt, cxt.getRoot()); System.out.println(name); |
8 操作集合
OGNL可以用来创建List和Map,先来创建一个List它。
List<String> names = (List<String>)Ognl.getValue("{'zhangSan', 'liSi', 'wangWu'}", cxt, cxt.getRoot()); System.out.println(names); |
这段代码创建一个List,类型为ArrayList。
创建Map必须以“#”开头
Map<String,String> map = (Map<String, String>)Ognl.getValue("#{'a':'A','b':'B'}", cxt, cxt.getRoot()); System.out.println(map); |
创建的Map默认类型为HashMap类型。还可以创建Map时指定Map的类型:
Map<String, String> map = (Map<String, String>) Ognl.getValue( "#@java.util.LinkedHashMap@{'aa':'AA','bb':'BB'}", cxt, cxt.getRoot()); System.out.println(map); |
获取Map的值。
String s = (String) Ognl.getValue( "#@java.util.LinkedHashMap@{'aa':'AA','bb':'BB'}['bb']", cxt, cxt.getRoot()); System.out.println(s); |
9 投影和选择
我们先来聊聊投影,如果有一个Employee的集合,可以通过投影获取所有的Employee的name属性值。这与数据库操作的查询指定列相似。
List<Employee> list = new ArrayList<Employee>(); list.add(new Employee("zhangSan", 10000)); list.add(new Employee("liSi", 11000)); list.add(new Employee("wangWu", 12000)); cxt.put("empList", list); List<String> nameList = (List<String>) Ognl.getValue("#empList.{name}", cxt, cxt.getRoot()); System.out.println(nameList); |
获取所有Employee的name属性。这相当于循环遍历list集合,然后调用每个Employee对象的getName()方法。
选择是给出一个条件,获取集合中满足条件的元素。这相当与select * from xxx where con。例如我们要获取salary > 10000的Employee对象。
List<Employee> list = new ArrayList<Employee>(); list.add(new Employee("zhangSan", 10000)); list.add(new Employee("liSi", 11000)); list.add(new Employee("wangWu", 12000)); cxt.put("empList", list); List<Employee> empList = (List<Employee>) Ognl.getValue( "#empList.{?salary > 10000}", cxt, cxt.getRoot()); System.out.println(empList); |
其中“{?salary > 10000}”中的“?”表示所有满足条件的元素都查询出来。还可以使用“^”或“$”,其中“^”表示查询第一个满足条件的元素,而“$”表示查询最后一个满足条件的元素。
Struts2中的OGNL
1 Struts2中OGNL操作的上下文和根对象
l 上下文对象是ActionContext;
l 根对象是ValueStack,它是一个栈结构,提供了压栈和弹栈等方法,通常栈顶元素是当前方法的Action对象;
l 每个请求都有自己的ActionContext,每个ActionContext中都有自己的ValueStack;
Struts2中的上下文对象就是ActionContext对象,而根对象是ValueStack对象。但Struts2中根对象ValueStack很特殊,它不是一个对象,而是一组对象(底层是ArrayList的栈结构)。你也可以理解为Struts2的根对象不是一个对象,而是一堆对象。
可以通过ActionContext来获取ValueStack:ActionContext.getContext().getValueStack(),也可以从request中获取ValueStack:request.getAttribute(“struts.valueStack”)。
ActionContext内部的数据:
l 请求参数和域属性:
- parameters:Map类型,对应HttpServletRequest中的请求参数;
- request:Map类型,对应HttpServletRequest中的属性;
- session:Map类型,对应HttpSession中的属性;
- application:Map类型,对应ServletContext中的属性;
- attr:Map类型,对应request、session、application三个域,即全域查询的Map;
l 域对象:
- com.opensymphony.xwork2.dispatcher.HttpServletRequest:HttpServletRequest对象
- com.opensymphony.xwork2.dispatcher.HttpServletResponse:HttpServletResposne对象
- com.opensymphony.xwork2.dispatcher.ServletContext:ServletContext对象;
l 其他信息:
- com.opensymphony.xwork2.ActionContext.name:<action>的name属性值;
- action:当前Action对象;
- com.opensymphony.xwork2.util.ValueStack.ValueStack:ValueStack对象;
- com.opensymphony.xwork2.ActionContext.locale:当前的Locale。
ValueStack内部的数据:
l Action对象;
l 模型对象(ModelDriven)。
不同的请求对应不同的ValueStack,通过在请求一个Action时,都会把当前Action的引用放到ValueStack中。一般情况下,当前Action就在ValueStack的栈顶!
2 Struts2标签使用OGNL表达式
l 使用<s:property>标签可以执行OGNL表达式,例如:<s:property value=”name”/>;
l 当OGNL访问的是根对象时(例如OGNL表达式name),Struts2会把ValueStack中每个元素当成根对象,从栈顶依次向下查找,直到找到某个元素有name属性。
Struts2中有很多标签都支持OGNL表达式,我们也知道OGNL表达式访问的数据是在OgnlContext中。在Struts2中上下文对象就是ActionContext,其中根对象就是ValueStack。
例如<s:property value=”name”/>这个标签的value属性值name就必须是一个OGNL表达式。我们知道,没有使用“#”为前缀的OGNL表达式表示是在获取根对象的name属性值,但在Struts2中根对象是ValueStack,它不是一个对象,而是一组对象,一个栈!Struts2的根对象特殊之处是,它会在ValueStack中从栈顶开始,直接到栈尾遍历每个栈中的对象,查看是否有name属性,如果找到栈中某个对象存在name属性,那么就不会再向下去找!也就是说,如果栈顶元素就有name属性,那么就算第二个元素也有name属性,那么输出的也是栈顶元素的name属性值。
前面我们也说过了,当访问一个Action后,Struts2会把当前Action对象压入到值栈中,即栈顶位置上(当然我们也可以调用ValueStack的push()方法来向其压入对象)。然后在Action跳转的JSP页面中使用<s:property>标签来获取值栈中元素的属性值。
public class OgnlDemo1Action extends ActionSupport { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String execute() throws Exception { return SUCCESS; } } |
<action name="demo1" class="cn.itcast.action.OgnlDemo1Action"> <result>/index.jsp</result> </action> |
<s:property value="name"/> |
当我们在浏览器中访问http://localhost:8080/ognl/demo1.action?name=zhangSan时,给Action的属性name赋值为zhangSan,然后Action跳转到index.jsp页面中,在index.jsp页面中我们使用<s:property>标签打印name属性值。因为“name”是一个OGNL表达式,它表示访问根对象的name属性,即调用根对象的getName()方法!但我们不要忘了,在Struts2中,这个动作表示在ValueStack中从上而下,依次查看每个元素是否有getName()方法,直到找到第一个有getName()方法的元素,然后返回它getName()方法的结果,才会结束。
因为刚刚访问了Action,把当前Action对象会被压入到ValueStack中,它是在ValueStack的栈顶,所以其实就是在获取当前Action的getName()。
我们知道,还可以通过OGNL表达式访问上下文中其他的对象,但这需要以“#”为前缀。例如可以访问parameters、request、session、application、attr这些Map对象。
<s:property value="#parameters.password"/><br/> <s:property value="#request.hello"/><br/> |
在ActionContext中存放着很多个Map:
key |
value |
request |
Map类型,对应request中的属性,但它不是request对象本身 |
session |
Map类型,对应session中的属性 |
application |
Map类型,对应application中的属性 |
parameters |
Map类型,对应request中请求参数 |
attr |
Map类型,对应所有域对象,依次查找。 |
#request表示一个Map,而#request.hello表示获取#request这个Map中key为hello的值。当然,如果你只有request.setAttribute(“hello”, “xxx”)过,才能获取到值。
3 [N]语法
[N]语法用来获取子栈,例如[3]表示截取从下标3位置开始到栈底的一个子栈。例如[3].name表示从[3]子栈中查找每个元素是否存在name属性,如果下标3元素就存在name属性,那么直接返回即可。
例如我们向ValueStack中压入两个对象:
public class OgnlDemo1Action extends ActionSupport { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String execute[崔34] () throws Exception { Emp emp = new Emp(); emp.setName("emp_zhangSan"); Student stu = new Student(); stu.setName("stu_liSi");
ValueStack vs = ActionContext.getContext().getValueStack(); vs.push(emp); vs.push(stu); return SUCCESS; } } |
<s:property value="[0].name[崔35] "/><br/> <s:property value="[1].name[崔36] "/><br/> <s:property value="[2].name[崔37] "/><br/> |
千万注意,[1]不是表示下标1的元素,而是一个子栈。
4 top关键字
top语法用来获取栈顶元素本身。例如还是上面的例子,值栈中从上到下依次是Student、Emp、Action三个对象,那么top表示的就是Student对象本身。而[1].top表示获取[1]子栈的栈顶元素,即Emp对象。
<s:property value="[0].top[崔38] "/><br/> <s:property value="[1].top"/><br/> <s:property value="[2].top"/><br/> |
5 访问静态成员
我们知道OGNL可以访问类的静态方法和属性,但在Struts2中默认是不允许OGNL访问静态成员的,如果想访问类的静态成员,需要设置一个常量:struts.ognl.allowStaticMethodAccess,该常量默认值为false,我们需要把它设置为true,这样就可以访问静态成员了。
<constant name="struts.ognl.allowStaticMethodAccess" value="true"/> |
<s:property value="@@min(10,20)[崔39] "/><br/> |
除了使用标准的OGNL表达式访问静态属性和静态方法外,Struts2还允许你不指定完整的类名,而是通过“vs”前缀来调用保存在值栈中对象的静态属性和静态方法。
例如:@vs@hello(),表示调用栈顶元素的静态方法hello(),如果栈顶元素是MyAction类型的对象,那么@vs@hello()就表示MyAction.hello()!
还可以指定值栈中元素序号来调用静态成员,例如@vs1@hello()表示调用的是值栈中第一个元素的静态方法,其实它与@vs@hello()是相同的。因为不存在@vs0@,序号是从1开始的。@vs2@hello()表示调用值栈中第2个元素的静态方法hello()。
6 模型驱动与ValueStack
我们知道,Struts2会把当前Action对象压入到值栈中,所以我们在JSP页面中总是可以直接使用OGNL来访问Action元素的属性:<s:property value=”username”/>,如果我们没有自己向值栈中压入其他元素,那么Action就是栈顶元素了,所以上面的标签输出的就是当前Action的username属性值。
当Action使用模型驱动时,ModelDrivenInterceptor拦截器会把当前model对象压入值栈,这时值栈中model对象就是栈顶元素,所以<s:property value=”username”/>访问的是model对象的username属性。
7 EL表达式访问值栈
让人感到奇怪的是,可以通过EL表达式访问到值栈。例如在JSP页面中使用${name},但在域对象中没有name这个域属性,奇怪的是它会去到值栈中找到元素的name属性值。
这是因为Struts2对request对象进行了装饰,这个装饰类是StrutsRequestWrapper,它对getAttribute()方法进行了增强,它会先到域对象中查找名为name的属性值,但如果没有找到它会去到ValueStack中去找,所以EL才会有这个本事。
8 Struts2 OGNL中的#、%、$
在Struts2的标签中,有些标签可以直接使用OGNL表达式,但有些是不能直接使用的。例如我们使用过的<s:property>标签的value属性就是默认为OGNL表达式的。但<s:url>标签的action属性默认是字符串,而不是OGNL表达式。例如<s:url action=”demo1”/>会在页面中输出/day03/damo1.action。如果你想在<s:url>标签的action属性中使用OGNL,那么需要使用%{},这样Struts2就知道你给出的值是一个OGNL表达式了,例如:<s:url action=”%{#request.url}”/>,其中#request.url会被Struts2当做OGNL表达式来解析!
当然,你也可以在<s:property>标签的value属性中使用%{},但对于默认就是OGNL表达式的属性而言,添加%{}是没有意义的:<s:property value=”%{name}”/>
对于OGNL表达式中的“#”,表示的是访问上下文中的非root对象时需要使用以“#”开头。这个应该没什么问题吧。
${}是在国际化资源文件中用来使用OGNL表达式用的,${}还可以在struts.xml文件用来使用OGNL表达式。
例如在res.properties文件中:msg:我的名称叫${#request.name},然后在Action或JSP中就可以使用它了。
<s:i18n name="res">
<s:text name="msg"></s:text>
</s:i18n>
例如在struts.xml文件中使用:
<result type=”stream”>
<param name=”contentType”>${type}</param>
</result>
其中${type}表示OGNL表达式,即调用当前Action的getType()方法(因为Action在值栈的栈顶位置)。
9 从Action中向页面传递数据
1. 一般性数据传递
每个Action都有自己保存信息的地方,这是从ActionSupport类继承而来的!
l ActionSupport#addFieldError(“字段名”, “字段错误信息内容”):它会把信息添加到Map<String,List>中;
l ActionSupport#addActionError(“Action错误信息内容”):它会把信息添加到Collection<String>中;
l ActionSupport#addActionMessage(“Action通知信息内容”):它会把信息添加到Collection<String>中。
因为Action会被压入到值栈中,所以在页面中Struts2的标签就可以来获取这些信息了。
l <s:fielderror fieldame=”字段名”/>:打印指定字段的所有错误信息;
l <s:actionerror/>:打印Action错误信息;
l <s:actioinmessage/>:打印Action通知信息。
2. 复杂数据传递
也可以把复杂类型的数据通过压入值栈来向页面传递:
valueStack.push(new User(“zhangSan”, “123”));
在页面中可以使用Struts2标签来获取值栈的内容:
<s:property value=”username”/>
<s:property value=”password”/>
10 页面中查看ValueStack内部数据
Struts2为开发人员提供了一个标签:<s:debug/>,它会在页面中生成一个超链接,点击链接会在页面中显示ValueStack的内部数据。
Struts2标签
打开struts-2.3.7\docs\WW\tag-reference.html可以看到Struts2提供的所有标签。其中分为“通用标签”和“UI标签”两大部分。我们不可能把所有标签都介绍到,我们只会介绍其中比较有用的几个标签介绍一下。
1 Struts2通用标签之数据标签
l <s:property>(重要)
<s:property>标签用来执行OGNL表达式,最为常用的方式是使用它在页面中输出ValueStack或ActionContext中的数据。
<s:property value=”#request.hello”/>,等于ActionContext.getContext().getRequest().get(“hello”)。
名称 |
必需 |
默认值 |
类型 |
说明 |
default |
否 |
无 |
String |
表示默认值。当结果为null时,输出默认值。 |
escape |
否 |
true |
boolean |
表示是否进行转义。该值为true时,当结果中包含<、>、”、’、&时,对其进行转义。 |
value |
否 |
栈顶对象 |
Object |
Ognl表达式 |
l <s:set>
<s:set>标签用来创建一个变量,保存到指定的范围内。
<s:set var=”myVar” value=”#parameters.score[0]” scope=”page”/>,创建一个变量,保存到page范围,key为myVar,值为“#parameters.score[0]”的运算结果。
名称 |
必需 |
默认值 |
类型 |
说明 |
var |
是 |
无 |
String |
变量的域名字,默认不是Ognl表达式 |
value |
否 |
栈顶对象 |
Object |
Ognl表达式。 |
scope |
否 |
Action |
String |
变量的范围。可选值为:application、session、request、page、action |
scope的可选值中的action是我们陌生的范围,它是scope的默认值。它表示保存到request和OgnlContext两个范围中。即比request范围多出了一个OgnlContext范围。
<s:set var=”myVar” value=”#parameters.score[0]” />
<s:property value=”#myVar”/>等同于ActionContext.getContext().get(“myVar”);
<s:property value=”#request.myVar”/>等同于ActionContext.getContext.getReuqest().get(“myVar”);
l <s:push>
<s:push>标签是把指定值暂时压入到值栈中,当执行完<s:push>标签后,压入到值栈中的值会被弹出。
<s:push value=”’hello’”/>等于把hello字符串压入到值栈中后,马上又弹出了,相当于什么都没做。
<s:push value=”#session.user”>把user压入到值栈中
<s:property value=”username”/>打印值栈元素的username属性
<s:property value=”password”/>打印值栈元素的password属性
</s:push>把user从值栈中弹出
l <s:url>
<s:url>标签通常用来生成action路径,它与<c:url>标签很相似。
<s:url action=”TestAction”/>在页面中打印/contextpath/TestAction.action。也就是说它与<c:url>一样会生成全路径。而且无论给出后缀“.action”!action属性的值只需要与struts.xml文件中<action>元素的name属性值相同即可。
<s:url action=”TestAction” namspace=”/” />还可以指定名称空间
<s:url action=”TestAction”>
<s:param name=”name” value=”’张三’”/>
</s:url>
页面中打印为:/ognl/TestAction.action?name=%E5%BC%A0%E4%B8%89
还可以为URL指定参数,其中参数包含中文时自动使用URL编码。
其中<s:param>是用来作为子标签的,它的作用是指定参数。它的value属性值为Ognl表达式,所以我们需要把“张三”用单引号引起来,表示Ognl表达式的字符串常量。
名称 |
必需 |
默认值 |
类型 |
说明 |
action |
否 |
无 |
String |
指定用于生成URL的action。 |
value |
否 |
无 |
String |
指定用于生成URL的地址值。通常只使用action或value其中一个属性。 |
var |
否 |
无 |
String |
如果指定了该属性,那么生成的URL不会输出到页面中,而是被保存到OgnlContext中。 |
method |
否 |
无 |
String |
指定调用action中的方法。如果使用的是value属性,那么该属性无效。 |
namespace |
否 |
无 |
String |
指定action所属的名称空间。 |
forceAddSchemeHostAndPort |
否 |
false |
Boolean |
当该属性为true时,生成的URL为绝对路径,而且会包含主机名及端口号。 |
l <s:a>
它用来生成超链接,与<s:url>相似!
<s:a action=”TestAction” namespace=”/”>添加用户
<s:param name=”name” value=”’张三’”/>
</s:a>
l <s:debug>
Debug标签用于调试,它在页面中生成一个“[Debug]”超链接,单击这个超链接,可以查看ValueStack和ActionContext中保存的所有对象。
2 Struts2通用标签之控制标签
控制标签很好理解,就是流程控制了。例如if、elseif等,以及iterator等。
l <s:if>、<s:elseif>、<s:else>
这种标签大家应该一看就会用了。我们直接给出个小例子看看。
<!— 在浏览器中输入:http://localhost:8080/tagtest/index.jsp?score=85 -->
<s:set name="score" value="#parameters.score[0]"/> <s:property value="#score"/>: <s:if test="#score > 100 || #score < 0"> <s:property value="'输入错误'"/> </s:if> <s:elseif test="#score >= 90"> <s:property value="'A'" /> </s:elseif> <s:elseif test="#score >= 80"> <s:property value="'B'" /> </s:elseif> <s:elseif test="#score >= 70"> <s:property value="'C'" /> </s:elseif> <s:elseif test="#score >= 60"> <s:property value="'D'" /> </s:elseif> <s:else> <s:property value="'E'"/> </s:else> |
l <s:iterator>
<s:iterator>标签可以用来迭代一个集合,可以迭代的集合有:Collection、Map、Enumeration、Iterator或者是数组。iterator标签在迭代过程中,会把当前对象暂时压入值栈,这样在子标签中就可以直接访问当前对象的属性(因为当前对象在栈顶),在标签体执行完毕后,位于栈顶的对象就会被删除,在循环的第二圈时,把新的当前对象再压入值栈中。
<s:iterator value="{'zhangSan','liSi','wangWu' }"> name: <s:property/><br/> </s:iterator> |
如果为<s:iterator>标签指定了var属性,那么当前对象不只是压入到了值栈中,而且还会被添加到OgnlContext中。
<s:iterator value="{'zhangSan','liSi','wangWu' }" var="name"> name: <s:property value="#name"/><br/> </s:iterator> |
名称 |
必需 |
默认值 |
类型 |
说明 |
var |
否 |
无 |
String |
如果指定了该属性,那么迭代的集合中的元素将被保存到OgnlContext中,可以通过该属性的值来引用集合中的元素。该属性几乎不被使用。 |
value |
否 |
无 |
Coolection、Map、Enumeration、Iterator 或数组 |
指定迭代的集合。如果没有指定该属性,那么iterator标签将把位于值栈栈顶的对象放入一个新创建的List中进行迭代。 |
status |
否 |
无 |
String |
如果指定了该属性,一个IteratorStatus实例将被放入到OgnlContext中,通过该实例可以获取迭代过程中的一些状态信息。 |
IteratorStatus类在org.apahce.struts2.views.jsp包下。下面是对该类常用方法的介绍:
l public int getCount():得到当前已经迭代的元素的总数。
l public int getIndex():得到当前迭代的元素的索引。
l public boolean isEven():判断当前迭代的元素的个数是否是偶数。
l public boolean isOdd():判断当前迭代的元素的个数是否是奇数。
l public boolean isFirest():判断当前迭代的元素是否是第一个元素。
l public boolean isLast():判断当前迭代的元素是否是最后一个元素。
在OGNL表达式中使用IteratorStatus类的方法时,可以直接使用:count、index、even、odd、first、last属性。
<s:iterator value='{"one", "two", "three"}' status="status"> <s:property value="#status.count"/>, <s:property value="#status.index"/>, <s:property value="#status.even"/>, <s:property value="#status.odd"/>, <s:property value="#status.first"/>, <s:property value="#status.last"/><br/> </s:iterator> <hr/> <s:iterator value="#{'1':'one','2':'two','3':'three','4':'four'}" status="st"> <s:property value="key"/>:<s:property value="value"/><br/> </s:iterator> |
3 Struts2标签UI标签之表单标签
Struts2的表单标签还是比较好用的,但它也存在一些败笔,例如主题这一部分就不是很灵活。所以导致开发中没有公司会使用它提供的主题。
Struts2标签的优势:
l 简化代码;
l 自动数据回显;
l 指定主题样式(说是优点,但很多人也会认为这是缺点);
- 表单标签入门
我们首先使用一个登录表单来对比一下html表单和Struts2表单的区别。
<form action="<c:url value='/user/LoginAction.action'/>" method="post"> 用户名 <input type="text" name="username"/><br/> 密 码 <input type="password" name="password"/><br/> <input type="submit" value="登录"/> </form> <hr/> <s:form action="LoginAction" namespace="/user"> <s:textfield name="username" label="用户名" /> <s:password name="password" label="密 码" /> <s:submit value="登录" /> </s:form> |
<form action="/ognl/user/LoginAction.action" method="post"> 用户名 <input type="text" name="username"/><br/> 密 码 <input type="password" name="password"/><br/> <input type="submit" value="登录"/> </form>
<hr/>
<form id="LoginAction" name="LoginAction" action="/ognl/user/LoginAction.action" method="post"> <table class="wwFormTable"> <tr> <td class="tdLabel"><label for="LoginAction_username" class="label">用户名:</label></td> <td ><input type="text" name="username" value="" id="LoginAction_username"/></td> </tr>
<tr> <td class="tdLabel"><label for="LoginAction_password" class="label">密 码:</label></td> <td ><input type="password" name="password" id="LoginAction_password"/></td> </tr>
<tr> <td colspan="2"><div align="right"><input type="submit" id="LoginAction_0" value="登录"/> </div></td> </tr>
</table></form> |
通过上面生成的代码可以看来:
l <s:form>
- 通过action和namespace两部分来指定请求路径,action直接给<action>元素的name值即可,无需给出后缀“.action”;
- method默认为post;
- 会自动添加id属性,值与action属性值相同;
- 整个表单会生成在<table>中。
l <s:textfield>
- 对应<input type=”text”>标签;
- 通过lable来生成<lable>标签;
l <s:password>
- 对应<input type=”password”>标签;
- 通过label来生成<lable>标签
l <s:submit>
- 对应<input type=”submit”>标签;
- 表单主题
我们发现,整个表单都会在<table>中生成,这也就说明无需为每个表单项添加<br/>。因为在表格中就无需再换行了。
生成在表格中是因为<s:form>标签的theme属性的默认值为xhtml,它表示一个主题样式。这个主题样式由Freemarker模板来完成。如果你不希望使用这个xhtml主题,那么有下列3种方法来修改主题:
l 在<s:textfield>的theme属性指定为simple,那么这个表单项就使用简单主题;
l 在<s:form>的theme属性指定为simple,那么整个表单都使用简单主题;
l 设置struts.ui.theme常量为simple,那么所有表单标签的默认主题都是simple。
当表单设置为theme=”simple”之后:
<s:form action="LoginAction" namespace="/user" theme="simple"> <s:textfield name="username" label="用户名" /> <s:password name="password" label="密 码" /> <s:submit value="登录" /> </s:form> |
<form id="LoginAction" name="LoginAction" action="/ognl/user/LoginAction.action" method="post"> <input type="text" name="username" value="" id="LoginAction_username"/> <input type="password" name="password" id="LoginAction_password"/> <input type="submit" id="LoginAction_0" value="登录"/> </form> |
这时没有表格来格式化表单,而且也不能生成<lable>,这说明样式都要自来来设定。好处是你重新获得了*,坏处是一切都要靠自己了。
- 自动回显
我们知道,当表单提交后,再返回到表单页面后,html标签不可能帮我们回显数据。而Struts2的表单标签可以做到这一点。原因是当前Action就在值栈顶,而表单标签会从值栈中获取数据来回显。
4 表单标签之选择性标签
- <s:redio>标签
表单标签中简化比较大的标签可以不在需要写循环,还有就是自动回显。我们都知道在下拉列表、单选、复选中,手动回显是比较麻烦的事。但使用Struts2的表单标签就方便多了。
<s:radio list="#{'male':'男','female':'女'}" name="gender"/> |
list指定的是一个Map,默认key为实际值,而value为显示值。
也可以为list属性指定为list:
<s:radio list="{'男','女'}" name="gender"/> |
这时实际值和显示值是相同的。
- <s:checkboxlist>标签
这个东西也很好用,一下子可以显示很多复选按钮。而且自动回显!
<s:checkboxlist list="#{'read':'看书','netplay':'上网','music':'音乐' }" name="hobby"/> |
- <s:select>标签
下拉列表与上面两个标签也一样,都是选择性的!使用它也是无需循环,无需处理循环。
<s:select name="city" list="#{'bj':'北京','sh':'上海','gz':'广州'}"/> |
Struts2的秘密
1 真实的上下文以及根对象
在struts-2.3.7\docs\WW\ognl.html中可以看到下面的介绍。
大意是:OGNL是对象图导航语言,OGNL的概念是处理一个Map,以及一个根对象,其中根对象的属性可以直接被引用,而其他Map中的对象需要一个“#”标记。
该框架是我们ActionContext中设置的OGNL上下文,ValueStack是OGNL的根对象。(值栈是一组多个对象,但OGNL原来是一个单独的对象)。
其实OgnlContext并不是ActonContext,而是ActionContext中包含了OgnlContext对象。ValueStatck也不是root对象,而是内部有root对象。其实在ActionContext中包含的OgnlContext对象,在ValueStack中也有它的引用。
其实说ValueStack是root对象也没错,因为ValueStack提供的方法就是用来操作内部的CompoundRoot对象的,而CompoundRoot是ArrayList的子类。也就是说,CompoundRoot是用来存储数据的,而ValueStack提供的方法是用来操作数据的。
OgnlContext才是真正的Ognl上下文,它内部包含了root对象,也保存了对request、session、application的解耦对象(Map类型)。外界不能直接访问它,而是通过ActionContext间接访问它。所以对于外界而言,ActionContext就是上下文对象。
其实这些错综复杂的关系你可以不用去记,你还是把ValueStack看成是root对象,而ActoinContext是上下文对象即可。
2 Struts2的核心控制器
想知道Struts2的执行流程,还是看源代码比较好。
请查看day03_res下的图片以及文件。
防止重复提交
1 什么是重复提交
当用户提供一个表单后,然后使用浏览器后退后,再次提交表单!
表单重复提交会给服务器带来访问压力!甚至会出现恶意刷票等!
如何重复提交:
l 转发到Action,这时地址栏中的地址是指向Action的,所以可以刷新页面来重复提交;
l 通过浏览器后退回表单页面,再次提交;
l 因为服务器很慢,在点击了提交后,页面还没有变化时,再次点击提交。
2 防止重复提交的原理
防止表单重复提交的原理就是使用令牌机制!所谓令牌机制就是在请求到达表单页面时,页面会生成令牌值(UUID),然后保存到session和表单的<input type=”hidden”>中。
然后用户提交表单后,令牌值会发送给Action。这时Action获取session中的令牌值与请求参数中的令牌值进行比较,如果相同,说明这是正常提交,Action会执行操作,然后把session中的令牌值删除。
当用户后退后,再次提交时,因为没有再次访问表单页面,那么就不会生成新的令牌值,所以参数中的令牌值还是上一次的,而session中已经没有令牌值了,所以Action会判断为重复提交。
3 Struts2防止重复提交
防止重复提交需要在页面中生成令牌值,然后在表单和session中分别保存,这个任务可以使用Struts2的标签:<s:token/>来未完成。这个标签会生成令牌值,并保存到session和表单的隐藏字段中。
在Action中获取session和请求参数中的令牌值进行比较,然后在令牌值无误时,在session中删除令牌值;在令牌值不同时转发到invalid.token结果。这些任务由token拦截器来完成。
<s:token>标签和token拦截器来完成防止表单重复提交。
当token拦截器确定当前是重复提交后,会跳转到invalid.token结果,这说明我们需要在指定名为invlalid.token的结果。而且token拦截器还会在actionError中添加错误信息。
<s:form action="tokenTest" namespace="/"> <s:token />[崔40] <s:submit value="呵呵" /> </s:form> |
public class TokenAction extends ActionSupport { @Override public String execute() throws Exception { System.out.println("execute()..."); return NONE; } } |
<action name="tokenTest" class="cn.itcast.action.TokenAction"> <result name="invalid.token[崔41] ">/error.jsp</result> <interceptor-ref name="defaultStack" /> <interceptor-ref name="token" />[崔42] </action> |
<body> <s:actionerror/>[崔43] </body> |
我们可以通过查看TokenInterceptor,知道这条错误令牌的key值是struts.messages.invalid.token,所以我们可以在国际化资源文件中覆盖这个key值。
[崔1]文件表单项的名称为myUpload
[崔2]一个文件表单项对应三个Action属性!Action属性名称要按fileupload拦截器的要求来。
[崔3]请求总大小上限为1M
[崔4]指定当前action的拦截器为defaultStack栈,对fileUpload拦截器进行了参数配置。
[崔5]单个文件大小上限为512K
[崔6]文件扩展名限制为jpg、png、bmp
[崔7]上传出错
[崔8]单个文件超出大小
[崔9]非法文件类型
[崔10]非法扩展名
[崔11]请求总大小超出限制
[崔12]所有文件表单项的名称是相同的
[崔13]因为是多个文件表单项,所以需要使得数组属性
[崔14]必须有getInputStream()方法,StreamResult会调用这个方法来获取被下载资源对应的输入流对象。
[崔15]为StreamResult的contentType属性赋值
[崔16]为StreamResult的contentDisposition属性赋值
[崔17]OGNL表达式,相当于调用action对象的getContentType()方法
[崔18]同上
[崔19]获取<action>元素的<param>参数
[崔20]获取真实路径
[崔21]用来设置表单数据到Action中
[崔22]因为是GET请求,所以需要处理一下编码问题
[崔23]在<result>元素下的<param>中使用了${filename},这相当于调用了本方法。
[崔24]处理下载框中的编码问题。
[崔25]在<result>元素下的<param>中使用了${contentType}这相当于调用了本方法
[崔26]通过文件名称获取对应的MIME类型
[崔27]返回被下载对象对应的流对象。这个方法会被StreamResult对象调用。
[崔28]通过下载目录和文件名创建文件流对象
[崔29]创建Ognl上下文
[崔30]向Ognl上下文中添加对象。
[崔31]设置e1为根对象。
[崔32]其中#emp2.name是OGNL表达式,cxt是查找的上下文对象,cxt.getRoot()是指定根对象。
[崔33]输出结果为“李四”
[崔34]因为Struts2会把当前Action压入值栈,所以默认是Action对象在栈顶,因为又向值栈中压入了emp和stu两个对象,那么值栈中元素的顺序从上到下分别是:Student、Emp、Action,当前栈顶元素为Student
[崔35][0]表示从0下标开始到栈底的子栈,这个子栈其实与整个值栈是相同的,所以[0].name与name是相同的效果。即从下标0元素开始查找name属性,即Student对象的name属性会第一个被找到
[崔36][1]表示从1下标开始到栈底的子栈,对于这个子栈而言,其栈顶就是下标1的元素了,即Emp对象,所以[1].name获取的是Emp对象的name属性。
[崔37]同理,这是获取Action的name属性。
[崔38]这个OGNL表达式与top相同!因为[0]子栈与整个值栈是相同的。
[崔39]调用Math类的min()方法
[崔40]该标签可以生成令牌值,并且保存到session中表单隐藏字段中。
[崔41]当重复提交时,拦截器会跳转到invalid.token结果,所以我们需要配置它。
[崔42]指定token拦截器,因为它不在defaultStack中。
[崔43]error.jsp页面中打印actionError。
[崔44]我们发现打印的错误令牌是英文的!